@silvery/examples 0.17.3 → 0.17.4
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/dist/UPNG-Cy7ViL8f.mjs +5074 -0
- package/dist/__vite-browser-external-2447137e-BML7CYau.mjs +4 -0
- package/dist/_banner-DLPxCqVy.mjs +44 -0
- package/dist/ansi-CCE2pVS0.mjs +16397 -0
- package/dist/apng-HhhBjRGt.mjs +68 -0
- package/dist/apng-mwUQbTTF.mjs +3 -0
- package/dist/apps/aichat/index.mjs +1299 -0
- package/dist/apps/app-todo.mjs +139 -0
- package/dist/apps/async-data.mjs +204 -0
- package/dist/apps/cli-wizard.mjs +339 -0
- package/dist/apps/clipboard.mjs +198 -0
- package/dist/apps/components.mjs +864 -0
- package/dist/apps/data-explorer.mjs +483 -0
- package/dist/apps/dev-tools.mjs +397 -0
- package/dist/apps/explorer.mjs +698 -0
- package/dist/apps/gallery.mjs +766 -0
- package/dist/apps/inline-bench.mjs +115 -0
- package/dist/apps/kanban.mjs +280 -0
- package/dist/apps/layout-ref.mjs +187 -0
- package/dist/apps/outline.mjs +203 -0
- package/dist/apps/paste-demo.mjs +189 -0
- package/dist/apps/scroll.mjs +86 -0
- package/dist/apps/search-filter.mjs +287 -0
- package/dist/apps/selection.mjs +355 -0
- package/dist/apps/spatial-focus-demo.mjs +388 -0
- package/dist/apps/task-list.mjs +258 -0
- package/dist/apps/terminal-caps-demo.mjs +315 -0
- package/dist/apps/terminal.mjs +872 -0
- package/dist/apps/text-selection-demo.mjs +254 -0
- package/dist/apps/textarea.mjs +178 -0
- package/dist/apps/theme.mjs +661 -0
- package/dist/apps/transform.mjs +215 -0
- package/dist/apps/virtual-10k.mjs +422 -0
- package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
- package/dist/backends-Bahh9mKN.mjs +1179 -0
- package/dist/backends-CCtCDQ94.mjs +3 -0
- package/dist/{cli.mjs → bin/cli.mjs} +15 -19
- package/dist/chunk-BSw8zbkd.mjs +37 -0
- package/dist/components/counter.mjs +48 -0
- package/dist/components/hello.mjs +31 -0
- package/dist/components/progress-bar.mjs +59 -0
- package/dist/components/select-list.mjs +85 -0
- package/dist/components/spinner.mjs +57 -0
- package/dist/components/text-input.mjs +62 -0
- package/dist/components/virtual-list.mjs +51 -0
- package/dist/flexily-zero-adapter-UB-ra8fR.mjs +3374 -0
- package/dist/gif-BZaqPPVX.mjs +3 -0
- package/dist/gif-BtnXuxLF.mjs +71 -0
- package/dist/gifenc-CLRW41dk.mjs +728 -0
- package/dist/jsx-runtime-dMs_8fNu.mjs +241 -0
- package/dist/key-mapping-5oYQdAQE.mjs +3 -0
- package/dist/key-mapping-D4LR1go6.mjs +130 -0
- package/dist/layout/dashboard.mjs +1204 -0
- package/dist/layout/live-resize.mjs +303 -0
- package/dist/layout/overflow.mjs +70 -0
- package/dist/layout/text-layout.mjs +335 -0
- package/dist/node-NuJ94BWl.mjs +1083 -0
- package/dist/plugins-D1KtkT4a.mjs +3057 -0
- package/dist/resvg-js-C_8Wps1F.mjs +201 -0
- package/dist/src-BTEVGpd9.mjs +23538 -0
- package/dist/src-CUUOuRH6.mjs +5322 -0
- package/dist/src-CzfRafCQ.mjs +814 -0
- package/dist/usingCtx-CsEf0xO3.mjs +57 -0
- package/dist/yoga-adapter-BVtQ5OJR.mjs +237 -0
- package/package.json +18 -13
- package/_banner.tsx +0 -60
- package/apps/aichat/components.tsx +0 -469
- package/apps/aichat/index.tsx +0 -220
- package/apps/aichat/script.ts +0 -460
- package/apps/aichat/state.ts +0 -325
- package/apps/aichat/types.ts +0 -19
- package/apps/app-todo.tsx +0 -201
- package/apps/async-data.tsx +0 -196
- package/apps/cli-wizard.tsx +0 -332
- package/apps/clipboard.tsx +0 -183
- package/apps/components.tsx +0 -658
- package/apps/data-explorer.tsx +0 -490
- package/apps/dev-tools.tsx +0 -395
- package/apps/explorer.tsx +0 -731
- package/apps/gallery.tsx +0 -653
- package/apps/inline-bench.tsx +0 -138
- package/apps/kanban.tsx +0 -265
- package/apps/layout-ref.tsx +0 -173
- package/apps/outline.tsx +0 -160
- package/apps/panes/index.tsx +0 -203
- package/apps/paste-demo.tsx +0 -185
- package/apps/scroll.tsx +0 -80
- package/apps/search-filter.tsx +0 -240
- package/apps/selection.tsx +0 -346
- package/apps/spatial-focus-demo.tsx +0 -372
- package/apps/task-list.tsx +0 -271
- package/apps/terminal-caps-demo.tsx +0 -317
- package/apps/terminal.tsx +0 -784
- package/apps/text-selection-demo.tsx +0 -193
- package/apps/textarea.tsx +0 -155
- package/apps/theme.tsx +0 -515
- package/apps/transform.tsx +0 -229
- package/apps/virtual-10k.tsx +0 -405
- package/apps/vterm-demo/index.tsx +0 -216
- package/components/counter.tsx +0 -49
- package/components/hello.tsx +0 -38
- package/components/progress-bar.tsx +0 -52
- package/components/select-list.tsx +0 -54
- package/components/spinner.tsx +0 -44
- package/components/text-input.tsx +0 -61
- package/components/virtual-list.tsx +0 -56
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs.map +0 -1
- package/layout/dashboard.tsx +0 -953
- package/layout/live-resize.tsx +0 -282
- package/layout/overflow.tsx +0 -51
- package/layout/text-layout.tsx +0 -283
|
@@ -0,0 +1,1299 @@
|
|
|
1
|
+
import { t as _usingCtx } from "../../usingCtx-CsEf0xO3.mjs";
|
|
2
|
+
import { t as require_jsx_runtime } from "../../jsx-runtime-dMs_8fNu.mjs";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Box, Link, ListView, Spinner, Text, TextInput, fx, useTea, useTerminalFocused, useWindowSize } from "silvery";
|
|
5
|
+
import { run, useExit, useInput as useInput$1 } from "silvery/runtime";
|
|
6
|
+
//#region apps/aichat/script.ts
|
|
7
|
+
const CONTEXT_WINDOW = 2e5;
|
|
8
|
+
const TOOL_COLORS = {
|
|
9
|
+
Read: "$info",
|
|
10
|
+
Edit: "$warning",
|
|
11
|
+
Bash: "$error",
|
|
12
|
+
Write: "$accent",
|
|
13
|
+
Glob: "$muted",
|
|
14
|
+
Grep: "$success"
|
|
15
|
+
};
|
|
16
|
+
/** Random user commands for Tab-to-inject feature. */
|
|
17
|
+
const RANDOM_USER_COMMANDS = [
|
|
18
|
+
"Can you add unit tests for the auth module?",
|
|
19
|
+
"Refactor the database queries to use prepared statements.",
|
|
20
|
+
"Add TypeScript strict mode and fix any errors.",
|
|
21
|
+
"Set up CI/CD with GitHub Actions.",
|
|
22
|
+
"The search feature is slow — can you optimize it?",
|
|
23
|
+
"Add dark mode support to the UI.",
|
|
24
|
+
"We need input validation on the registration form.",
|
|
25
|
+
"Create a migration script for the new schema.",
|
|
26
|
+
"Add WebSocket support for real-time updates.",
|
|
27
|
+
"The CSV export is broken — dates are wrong."
|
|
28
|
+
];
|
|
29
|
+
/** Random agent responses for Tab-injected turns. */
|
|
30
|
+
const RANDOM_AGENT_RESPONSES = [{
|
|
31
|
+
role: "agent",
|
|
32
|
+
thinking: "Let me analyze the codebase to understand the current structure.",
|
|
33
|
+
content: "I'll look at the relevant files and make the changes.",
|
|
34
|
+
toolCalls: [{
|
|
35
|
+
tool: "Read",
|
|
36
|
+
args: "src/index.ts",
|
|
37
|
+
output: ["export function main() { /* ... */ }"]
|
|
38
|
+
}, {
|
|
39
|
+
tool: "Edit",
|
|
40
|
+
args: "src/index.ts",
|
|
41
|
+
output: ["+ // Updated implementation"]
|
|
42
|
+
}],
|
|
43
|
+
tokens: {
|
|
44
|
+
input: 12400,
|
|
45
|
+
output: 890
|
|
46
|
+
}
|
|
47
|
+
}, {
|
|
48
|
+
role: "agent",
|
|
49
|
+
content: "Done! I've made the changes and verified everything works.",
|
|
50
|
+
tokens: {
|
|
51
|
+
input: 15200,
|
|
52
|
+
output: 340
|
|
53
|
+
}
|
|
54
|
+
}];
|
|
55
|
+
/** Regex matching https/http URLs in output text. */
|
|
56
|
+
const URL_RE = /https?:\/\/[^\s)]+/g;
|
|
57
|
+
const SCRIPT = [
|
|
58
|
+
{
|
|
59
|
+
role: "user",
|
|
60
|
+
content: "Fix the login bug in auth.ts — expired tokens throw instead of refreshing.",
|
|
61
|
+
tokens: {
|
|
62
|
+
input: 84,
|
|
63
|
+
output: 0
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
role: "agent",
|
|
68
|
+
thinking: "The user reports expired tokens throw instead of refreshing. This is likely in the token validation flow. I should read auth.ts to see the current expiry check logic. The bug is probably comparing jwt.exp (seconds) with Date.now() (milliseconds).",
|
|
69
|
+
content: "Let me look at the auth module.",
|
|
70
|
+
toolCalls: [{
|
|
71
|
+
tool: "Read",
|
|
72
|
+
args: "src/auth.ts",
|
|
73
|
+
output: [
|
|
74
|
+
"export async function login(token: string) {",
|
|
75
|
+
" const decoded = jwt.decode(token)",
|
|
76
|
+
" if (decoded.exp < Date.now()) {",
|
|
77
|
+
" throw new Error(\"Token expired\")",
|
|
78
|
+
" }",
|
|
79
|
+
" return decoded.user",
|
|
80
|
+
"}"
|
|
81
|
+
]
|
|
82
|
+
}],
|
|
83
|
+
tokens: {
|
|
84
|
+
input: 3694,
|
|
85
|
+
output: 624
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
role: "agent",
|
|
90
|
+
thinking: "Found it — decoded.exp is in seconds (Unix timestamp) but Date.now() returns milliseconds. Every token appears expired because exp (e.g. 1700000000) is always less than Date.now() (e.g. 1700000000000). I need to divide Date.now() by 1000, and change the throw to a refresh call.",
|
|
91
|
+
content: "Found it. The expiry check compares seconds (jwt.exp) to milliseconds (Date.now()). Fixing now.",
|
|
92
|
+
toolCalls: [{
|
|
93
|
+
tool: "Edit",
|
|
94
|
+
args: "src/auth.ts",
|
|
95
|
+
output: [
|
|
96
|
+
" const decoded = jwt.decode(token)",
|
|
97
|
+
"- if (decoded.exp < Date.now()) {",
|
|
98
|
+
"- throw new Error(\"Token expired\")",
|
|
99
|
+
"+ if (decoded.exp < Date.now() / 1000) {",
|
|
100
|
+
"+ return refreshToken(token)",
|
|
101
|
+
" }"
|
|
102
|
+
]
|
|
103
|
+
}],
|
|
104
|
+
tokens: {
|
|
105
|
+
input: 4312,
|
|
106
|
+
output: 574
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
role: "agent",
|
|
111
|
+
content: "Running tests to verify the fix.",
|
|
112
|
+
toolCalls: [{
|
|
113
|
+
tool: "Bash",
|
|
114
|
+
args: "bun test src/auth.test.ts",
|
|
115
|
+
output: [
|
|
116
|
+
"auth.test.ts",
|
|
117
|
+
" ✓ login with valid token (2ms)",
|
|
118
|
+
" ✓ login with expired token refreshes (3ms)",
|
|
119
|
+
" ✓ login with invalid token rejects (1ms)",
|
|
120
|
+
"",
|
|
121
|
+
"3 passed | 0 failed"
|
|
122
|
+
]
|
|
123
|
+
}],
|
|
124
|
+
tokens: {
|
|
125
|
+
input: 4978,
|
|
126
|
+
output: 312
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
role: "agent",
|
|
131
|
+
content: "Fixed! The bug was comparing jwt.exp (seconds since epoch) with Date.now() (milliseconds). Expired tokens now trigger a refresh instead of throwing.",
|
|
132
|
+
tokens: {
|
|
133
|
+
input: 5202,
|
|
134
|
+
output: 178
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
role: "user",
|
|
139
|
+
content: "Nice. Can you also add rate limiting to the login endpoint?",
|
|
140
|
+
tokens: {
|
|
141
|
+
input: 116,
|
|
142
|
+
output: 0
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
role: "agent",
|
|
147
|
+
thinking: "The user wants rate limiting on login. I should check if there's existing rate limiting infrastructure before building from scratch. Let me search for rate-limit patterns in the codebase.",
|
|
148
|
+
content: "I'll check what rate limiting infrastructure exists.",
|
|
149
|
+
toolCalls: [{
|
|
150
|
+
tool: "Grep",
|
|
151
|
+
args: "rateLimit|rate-limit",
|
|
152
|
+
output: [
|
|
153
|
+
"src/middleware/rate-limit.ts",
|
|
154
|
+
"src/config.ts",
|
|
155
|
+
"See https://docs.example.com/api/rate-limiting for API docs"
|
|
156
|
+
]
|
|
157
|
+
}, {
|
|
158
|
+
tool: "Read",
|
|
159
|
+
args: "src/middleware/rate-limit.ts",
|
|
160
|
+
output: [
|
|
161
|
+
"import { RateLimiter } from '@lib/rate-limit'",
|
|
162
|
+
"",
|
|
163
|
+
"export function createLimiter(opts: { window: number; max: number }) {",
|
|
164
|
+
" return new RateLimiter(opts)",
|
|
165
|
+
"}"
|
|
166
|
+
]
|
|
167
|
+
}],
|
|
168
|
+
tokens: {
|
|
169
|
+
input: 6490,
|
|
170
|
+
output: 846
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
role: "agent",
|
|
175
|
+
thinking: "Good — there's already a RateLimiter abstraction. I just need to create an instance with appropriate settings (5 attempts per 60s window seems reasonable for login) and wire it into the auth route as middleware.",
|
|
176
|
+
content: "Good, there's already a rate limiter. I'll wire it into the auth route.",
|
|
177
|
+
toolCalls: [{
|
|
178
|
+
tool: "Edit",
|
|
179
|
+
args: "src/routes/auth.ts",
|
|
180
|
+
output: [
|
|
181
|
+
"+ import { createLimiter } from '../middleware/rate-limit'",
|
|
182
|
+
"+",
|
|
183
|
+
"+ const loginLimiter = createLimiter({ window: 60_000, max: 5 })",
|
|
184
|
+
"+",
|
|
185
|
+
" router.post('/login', async (req, res) => {",
|
|
186
|
+
"+ const limited = await loginLimiter.check(req.ip)",
|
|
187
|
+
"+ if (limited) return res.status(429).json({ error: 'Too many attempts' })",
|
|
188
|
+
" const { token } = req.body"
|
|
189
|
+
]
|
|
190
|
+
}],
|
|
191
|
+
tokens: {
|
|
192
|
+
input: 7782,
|
|
193
|
+
output: 756
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
role: "agent",
|
|
198
|
+
content: "Running the full test suite.",
|
|
199
|
+
toolCalls: [{
|
|
200
|
+
tool: "Bash",
|
|
201
|
+
args: "bun test",
|
|
202
|
+
output: [
|
|
203
|
+
"auth.test.ts ........... 3 passed",
|
|
204
|
+
"rate-limit.test.ts ..... 5 passed",
|
|
205
|
+
"routes/auth.test.ts .... 7 passed",
|
|
206
|
+
"",
|
|
207
|
+
"15 passed | 0 failed | 3 files"
|
|
208
|
+
]
|
|
209
|
+
}],
|
|
210
|
+
tokens: {
|
|
211
|
+
input: 8204,
|
|
212
|
+
output: 334
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
role: "agent",
|
|
217
|
+
content: "Rate limiting added: 5 attempts per minute per IP on the login endpoint. All 15 tests pass.",
|
|
218
|
+
tokens: {
|
|
219
|
+
input: 8468,
|
|
220
|
+
output: 156
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
role: "user",
|
|
225
|
+
content: "Now add i18n support for error messages. We need 日本語 (Japanese) and Deutsch (German). 🌍",
|
|
226
|
+
tokens: {
|
|
227
|
+
input: 146,
|
|
228
|
+
output: 0
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
role: "agent",
|
|
233
|
+
thinking: "i18n for error messages — I need to create translation JSON files for ja and de, then build a simple t() function that resolves message keys by locale. I'll also need to update the error responses in auth.ts to use t() with the request's locale.",
|
|
234
|
+
content: "I'll create the translation files and update the error handling.",
|
|
235
|
+
toolCalls: [{
|
|
236
|
+
tool: "Write",
|
|
237
|
+
args: "src/i18n/ja.json",
|
|
238
|
+
output: [
|
|
239
|
+
"{",
|
|
240
|
+
" \"token_expired\": \"トークンの有効期限が切れました 🔧\",",
|
|
241
|
+
" \"rate_limited\": \"リクエストが多すぎます。後でもう一度お試しください\",",
|
|
242
|
+
" \"invalid_token\": \"無効なトークンです 🐛\",",
|
|
243
|
+
" \"login_success\": \"ログイン成功 ✅\"",
|
|
244
|
+
"}"
|
|
245
|
+
]
|
|
246
|
+
}, {
|
|
247
|
+
tool: "Write",
|
|
248
|
+
args: "src/i18n/de.json",
|
|
249
|
+
output: [
|
|
250
|
+
"{",
|
|
251
|
+
" \"token_expired\": \"Token abgelaufen\",",
|
|
252
|
+
" \"rate_limited\": \"Zu viele Anfragen. Bitte versuchen Sie es später\",",
|
|
253
|
+
" \"invalid_token\": \"Ungültiges Token\",",
|
|
254
|
+
" \"login_success\": \"Anmeldung erfolgreich\"",
|
|
255
|
+
"}"
|
|
256
|
+
]
|
|
257
|
+
}],
|
|
258
|
+
tokens: {
|
|
259
|
+
input: 17868,
|
|
260
|
+
output: 1134
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
role: "agent",
|
|
265
|
+
thinking: "Now I need the i18n loader module that imports the locale files and exports a t() function. The function should look up the key in the requested locale, falling back to English defaults. Then I'll wire it into the auth error responses.",
|
|
266
|
+
content: "Now I'll create the i18n loader and wire it into the error responses.",
|
|
267
|
+
toolCalls: [{
|
|
268
|
+
tool: "Write",
|
|
269
|
+
args: "src/i18n/index.ts",
|
|
270
|
+
output: [
|
|
271
|
+
"import ja from './ja.json'",
|
|
272
|
+
"import de from './de.json'",
|
|
273
|
+
"",
|
|
274
|
+
"const locales = { ja, de, en: { /* defaults */ } }",
|
|
275
|
+
"",
|
|
276
|
+
"export function t(key: string, lang = 'en'): string {",
|
|
277
|
+
" return locales[lang]?.[key] ?? locales.en[key] ?? key",
|
|
278
|
+
"}"
|
|
279
|
+
]
|
|
280
|
+
}, {
|
|
281
|
+
tool: "Edit",
|
|
282
|
+
args: "src/auth.ts",
|
|
283
|
+
output: [
|
|
284
|
+
"+ import { t } from './i18n'",
|
|
285
|
+
"",
|
|
286
|
+
"- return res.status(429).json({ error: 'Too many attempts' })",
|
|
287
|
+
"+ return res.status(429).json({ error: t('rate_limited', req.lang) })"
|
|
288
|
+
]
|
|
289
|
+
}],
|
|
290
|
+
tokens: {
|
|
291
|
+
input: 19752,
|
|
292
|
+
output: 890
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
role: "agent",
|
|
297
|
+
content: "Running tests with locale coverage.",
|
|
298
|
+
toolCalls: [{
|
|
299
|
+
tool: "Bash",
|
|
300
|
+
args: "bun test src/i18n/",
|
|
301
|
+
output: [
|
|
302
|
+
"i18n.test.ts",
|
|
303
|
+
" ✓ English fallback for unknown key",
|
|
304
|
+
" ✓ Japanese: トークンの有効期限が切れました 🔧",
|
|
305
|
+
" ✓ German: Token abgelaufen",
|
|
306
|
+
" ✓ Emoji preserved in translations 🐛✅",
|
|
307
|
+
"",
|
|
308
|
+
"4 passed | 0 failed"
|
|
309
|
+
]
|
|
310
|
+
}],
|
|
311
|
+
tokens: {
|
|
312
|
+
input: 20468,
|
|
313
|
+
output: 356
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
role: "agent",
|
|
318
|
+
content: "i18n support added with Japanese (日本語) and German (Deutsch) translations. Error messages are now locale-aware. 🌍✅",
|
|
319
|
+
tokens: {
|
|
320
|
+
input: 20802,
|
|
321
|
+
output: 134
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
role: "user",
|
|
326
|
+
content: "Add a health check endpoint at /health that returns the service version.",
|
|
327
|
+
tokens: {
|
|
328
|
+
input: 104,
|
|
329
|
+
output: 0
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
role: "agent",
|
|
334
|
+
thinking: "Simple task — read the version from package.json and expose it on GET /health alongside uptime. Quick implementation.",
|
|
335
|
+
content: "Simple addition — reading package.json for the version, then adding the route.",
|
|
336
|
+
toolCalls: [{
|
|
337
|
+
tool: "Read",
|
|
338
|
+
args: "package.json",
|
|
339
|
+
output: [
|
|
340
|
+
"{",
|
|
341
|
+
" \"name\": \"auth-service\",",
|
|
342
|
+
" \"version\": \"2.4.1\",",
|
|
343
|
+
" ...",
|
|
344
|
+
"}"
|
|
345
|
+
]
|
|
346
|
+
}, {
|
|
347
|
+
tool: "Edit",
|
|
348
|
+
args: "src/routes/health.ts",
|
|
349
|
+
output: [
|
|
350
|
+
"+ import { version } from '../../package.json'",
|
|
351
|
+
"+",
|
|
352
|
+
"+ router.get('/health', (req, res) => {",
|
|
353
|
+
"+ res.json({ status: 'ok', version, uptime: process.uptime() })",
|
|
354
|
+
"+ })"
|
|
355
|
+
]
|
|
356
|
+
}],
|
|
357
|
+
tokens: {
|
|
358
|
+
input: 21578,
|
|
359
|
+
output: 468
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
role: "agent",
|
|
364
|
+
content: "Running final tests.",
|
|
365
|
+
toolCalls: [{
|
|
366
|
+
tool: "Bash",
|
|
367
|
+
args: "bun test",
|
|
368
|
+
output: [
|
|
369
|
+
"auth.test.ts ........... 3 passed",
|
|
370
|
+
"rate-limit.test.ts ..... 5 passed",
|
|
371
|
+
"routes/auth.test.ts .... 7 passed",
|
|
372
|
+
"routes/health.test.ts .. 2 passed",
|
|
373
|
+
"i18n.test.ts ........... 4 passed",
|
|
374
|
+
"",
|
|
375
|
+
"21 passed | 0 failed | 5 files"
|
|
376
|
+
]
|
|
377
|
+
}],
|
|
378
|
+
tokens: {
|
|
379
|
+
input: 22046,
|
|
380
|
+
output: 290
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
role: "agent",
|
|
385
|
+
content: "All done! Summary of changes:\n• Fixed token expiry bug (seconds vs milliseconds)\n• Added rate limiting (5 req/min per IP)\n• Added i18n support (日本語 + Deutsch) 🌍\n• Added /health endpoint (v2.4.1)\n\nAll 21 tests pass. Ready to commit?",
|
|
386
|
+
tokens: {
|
|
387
|
+
input: 22468,
|
|
388
|
+
output: 224
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
];
|
|
392
|
+
function generateStressScript() {
|
|
393
|
+
const exchanges = [];
|
|
394
|
+
const tools = [
|
|
395
|
+
"Read",
|
|
396
|
+
"Edit",
|
|
397
|
+
"Bash",
|
|
398
|
+
"Write",
|
|
399
|
+
"Grep",
|
|
400
|
+
"Glob"
|
|
401
|
+
];
|
|
402
|
+
const files = [
|
|
403
|
+
"src/auth.ts",
|
|
404
|
+
"src/db.ts",
|
|
405
|
+
"src/routes/api.ts",
|
|
406
|
+
"src/middleware/cors.ts",
|
|
407
|
+
"src/utils/crypto.ts",
|
|
408
|
+
"src/config.ts",
|
|
409
|
+
"tests/integration.test.ts",
|
|
410
|
+
"src/i18n/日本語.json"
|
|
411
|
+
];
|
|
412
|
+
let cumulativeInput = 4e3;
|
|
413
|
+
for (let i = 0; i < 200; i++) {
|
|
414
|
+
if (i % 5 === 0) {
|
|
415
|
+
const prompts = [
|
|
416
|
+
`Fix bug #${100 + i} in ${files[i % files.length]}`,
|
|
417
|
+
`Add feature: ${[
|
|
418
|
+
"caching",
|
|
419
|
+
"logging",
|
|
420
|
+
"retry",
|
|
421
|
+
"batching",
|
|
422
|
+
"バリデーション"
|
|
423
|
+
][i % 5]}`,
|
|
424
|
+
`Refactor ${files[i % files.length]} — it's too complex 🔧`,
|
|
425
|
+
`Why is test #${i} failing? 🐛`,
|
|
426
|
+
`Add 日本語 translations for module ${i}`
|
|
427
|
+
];
|
|
428
|
+
exchanges.push({
|
|
429
|
+
role: "user",
|
|
430
|
+
content: prompts[Math.floor(i / 5) % prompts.length],
|
|
431
|
+
tokens: {
|
|
432
|
+
input: 40 + i % 30,
|
|
433
|
+
output: 0
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
} else if (i % 5 === 4) exchanges.push({
|
|
437
|
+
role: "agent",
|
|
438
|
+
content: `Done with batch ${Math.floor(i / 5) + 1}. ${3 + i % 7} tests pass. ✅`,
|
|
439
|
+
tokens: {
|
|
440
|
+
input: cumulativeInput,
|
|
441
|
+
output: 45 + i % 60
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
else {
|
|
445
|
+
const tool = tools[i % tools.length];
|
|
446
|
+
const file = files[i % files.length];
|
|
447
|
+
cumulativeInput += 200 + i % 300;
|
|
448
|
+
exchanges.push({
|
|
449
|
+
role: "agent",
|
|
450
|
+
thinking: i % 3 === 0 ? `Analyzing ${file} for the reported issue...` : void 0,
|
|
451
|
+
content: `Working on ${file}...`,
|
|
452
|
+
toolCalls: [{
|
|
453
|
+
tool,
|
|
454
|
+
args: tool === "Bash" ? `bun test ${file.replace("src/", "tests/")}` : file,
|
|
455
|
+
output: [
|
|
456
|
+
`// ${tool} output for ${file}`,
|
|
457
|
+
`line ${i * 10 + 1}: processing...`,
|
|
458
|
+
tool === "Edit" ? `- old code at line ${i}` : ` existing line ${i}`,
|
|
459
|
+
tool === "Edit" ? `+ new code at line ${i}` : ` result: ok`,
|
|
460
|
+
i % 10 === 0 ? `✓ テスト合格 🎉` : `✓ done`
|
|
461
|
+
]
|
|
462
|
+
}],
|
|
463
|
+
tokens: {
|
|
464
|
+
input: cumulativeInput,
|
|
465
|
+
output: 120 + i % 200
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
if (i === 80 || i === 160) exchanges.push({
|
|
470
|
+
role: "system",
|
|
471
|
+
content: `📦 Compaction #${i === 80 ? 1 : 2}: context cleared. Scrollback preserved above.`
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
return exchanges;
|
|
475
|
+
}
|
|
476
|
+
const INIT_STATE = {
|
|
477
|
+
exchanges: [{
|
|
478
|
+
id: 0,
|
|
479
|
+
role: "system",
|
|
480
|
+
content: [
|
|
481
|
+
"Coding agent simulation showcasing ListView:",
|
|
482
|
+
" • ListView — unified virtualized list with cache",
|
|
483
|
+
" • Cache mode — completed exchanges cached for performance",
|
|
484
|
+
" • OSC 8 hyperlinks — clickable file paths and URLs",
|
|
485
|
+
" • $token theme colors — semantic color tokens"
|
|
486
|
+
].join("\n")
|
|
487
|
+
}],
|
|
488
|
+
scriptIdx: 0,
|
|
489
|
+
streamPhase: "done",
|
|
490
|
+
revealFraction: 1,
|
|
491
|
+
done: false,
|
|
492
|
+
compacting: false,
|
|
493
|
+
pulse: false,
|
|
494
|
+
ctrlDPending: false,
|
|
495
|
+
contextBaseline: 0,
|
|
496
|
+
offScript: false,
|
|
497
|
+
nextId: 1,
|
|
498
|
+
autoTyping: null
|
|
499
|
+
};
|
|
500
|
+
function formatTokens(n) {
|
|
501
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
502
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
503
|
+
return String(n);
|
|
504
|
+
}
|
|
505
|
+
function formatCost(inputTokens, outputTokens) {
|
|
506
|
+
const cost = (inputTokens * 15 + outputTokens * 75) / 1e6;
|
|
507
|
+
if (cost < .01) return `$${cost.toFixed(4)}`;
|
|
508
|
+
return `$${cost.toFixed(2)}`;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Compute token stats for display and compaction.
|
|
512
|
+
*
|
|
513
|
+
* Token values in the script are CUMULATIVE — each exchange's `input` represents
|
|
514
|
+
* the total context consumed at that point. So:
|
|
515
|
+
* - `currentContext`: the LAST exchange's input tokens (= current context window usage)
|
|
516
|
+
* - `totalCost`: sum of all (input + output) for cost calculation (each API call costs)
|
|
517
|
+
*/
|
|
518
|
+
function computeCumulativeTokens(exchanges) {
|
|
519
|
+
let input = 0;
|
|
520
|
+
let output = 0;
|
|
521
|
+
let currentContext = 0;
|
|
522
|
+
for (const ex of exchanges) if (ex.tokens) {
|
|
523
|
+
input += ex.tokens.input;
|
|
524
|
+
output += ex.tokens.output;
|
|
525
|
+
if (ex.tokens.input > currentContext) currentContext = ex.tokens.input;
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
input,
|
|
529
|
+
output,
|
|
530
|
+
currentContext
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
/** Next scripted user message for footer placeholder. */
|
|
534
|
+
function getNextMessage(state, script, autoMode) {
|
|
535
|
+
if (autoMode || state.done || state.offScript || state.streamPhase !== "done" || state.exchanges.length === 0) return "";
|
|
536
|
+
const entry = script[state.scriptIdx];
|
|
537
|
+
return entry?.role === "user" ? entry.content : "";
|
|
538
|
+
}
|
|
539
|
+
function createDemoUpdate(script, fastMode, autoMode) {
|
|
540
|
+
function addExchange(state, entry) {
|
|
541
|
+
const exchange = {
|
|
542
|
+
...entry,
|
|
543
|
+
id: state.nextId
|
|
544
|
+
};
|
|
545
|
+
return {
|
|
546
|
+
...state,
|
|
547
|
+
exchanges: [...state.exchanges, exchange],
|
|
548
|
+
nextId: state.nextId + 1
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function startStreaming(state, entry) {
|
|
552
|
+
const s = addExchange(state, entry);
|
|
553
|
+
if (entry.role !== "agent" || fastMode) return [{
|
|
554
|
+
...s,
|
|
555
|
+
streamPhase: "done",
|
|
556
|
+
revealFraction: 1
|
|
557
|
+
}, []];
|
|
558
|
+
if (entry.thinking) return [{
|
|
559
|
+
...s,
|
|
560
|
+
streamPhase: "thinking",
|
|
561
|
+
revealFraction: 0
|
|
562
|
+
}, [fx.delay(1200, { type: "endThinking" })]];
|
|
563
|
+
return [{
|
|
564
|
+
...s,
|
|
565
|
+
streamPhase: "streaming",
|
|
566
|
+
revealFraction: 0
|
|
567
|
+
}, [fx.interval(50, { type: "streamTick" }, "reveal")]];
|
|
568
|
+
}
|
|
569
|
+
function autoAdvanceEffects(state) {
|
|
570
|
+
if (state.done || state.compacting || state.streamPhase !== "done") return [];
|
|
571
|
+
const next = script[state.scriptIdx];
|
|
572
|
+
if (!next) return autoMode ? [fx.delay(0, { type: "autoAdvance" })] : [];
|
|
573
|
+
if (autoMode || next.role !== "user") return [fx.delay(fastMode ? 100 : 400, { type: "autoAdvance" })];
|
|
574
|
+
return [];
|
|
575
|
+
}
|
|
576
|
+
function doAdvance(state, extraEffects = []) {
|
|
577
|
+
if (state.done || state.compacting || state.streamPhase !== "done") return state;
|
|
578
|
+
if (state.scriptIdx >= script.length) return autoMode ? {
|
|
579
|
+
...state,
|
|
580
|
+
done: true
|
|
581
|
+
} : state;
|
|
582
|
+
const entry = script[state.scriptIdx];
|
|
583
|
+
let s = {
|
|
584
|
+
...state,
|
|
585
|
+
scriptIdx: state.scriptIdx + 1
|
|
586
|
+
};
|
|
587
|
+
const effects = [...extraEffects];
|
|
588
|
+
let streamFx;
|
|
589
|
+
[s, streamFx] = startStreaming(s, entry);
|
|
590
|
+
effects.push(...streamFx);
|
|
591
|
+
if (fastMode) {
|
|
592
|
+
while (s.scriptIdx < script.length && script[s.scriptIdx].role !== "user") {
|
|
593
|
+
[s, streamFx] = startStreaming({
|
|
594
|
+
...s,
|
|
595
|
+
scriptIdx: s.scriptIdx + 1
|
|
596
|
+
}, script[s.scriptIdx]);
|
|
597
|
+
effects.push(...streamFx);
|
|
598
|
+
}
|
|
599
|
+
effects.push(...autoAdvanceEffects(s));
|
|
600
|
+
} else if (entry.role === "user") {
|
|
601
|
+
if (s.scriptIdx < script.length && script[s.scriptIdx].role === "agent") {
|
|
602
|
+
[s, streamFx] = startStreaming({
|
|
603
|
+
...s,
|
|
604
|
+
scriptIdx: s.scriptIdx + 1
|
|
605
|
+
}, script[s.scriptIdx]);
|
|
606
|
+
effects.push(...streamFx);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return [s, effects];
|
|
610
|
+
}
|
|
611
|
+
return function update(state, msg) {
|
|
612
|
+
switch (msg.type) {
|
|
613
|
+
case "mount": return doAdvance(state, [fx.interval(400, { type: "pulse" }, "pulse")]);
|
|
614
|
+
case "advance":
|
|
615
|
+
case "autoAdvance":
|
|
616
|
+
if (autoMode && !fastMode && state.streamPhase === "done" && !state.done && !state.compacting) {
|
|
617
|
+
const next = script[state.scriptIdx];
|
|
618
|
+
if (next?.role === "user") return [{
|
|
619
|
+
...state,
|
|
620
|
+
autoTyping: {
|
|
621
|
+
full: next.content,
|
|
622
|
+
revealed: 0
|
|
623
|
+
}
|
|
624
|
+
}, [fx.interval(30, { type: "typingTick" }, "typing")]];
|
|
625
|
+
}
|
|
626
|
+
if (autoMode && state.scriptIdx >= script.length && state.streamPhase === "done") return {
|
|
627
|
+
...state,
|
|
628
|
+
done: true
|
|
629
|
+
};
|
|
630
|
+
return doAdvance(state);
|
|
631
|
+
case "typingTick": {
|
|
632
|
+
if (!state.autoTyping) return state;
|
|
633
|
+
const next = state.autoTyping.revealed + 1;
|
|
634
|
+
if (next >= state.autoTyping.full.length) return [{
|
|
635
|
+
...state,
|
|
636
|
+
autoTyping: {
|
|
637
|
+
...state.autoTyping,
|
|
638
|
+
revealed: state.autoTyping.full.length
|
|
639
|
+
}
|
|
640
|
+
}, [fx.cancel("typing"), fx.delay(300, { type: "autoTypingDone" })]];
|
|
641
|
+
return {
|
|
642
|
+
...state,
|
|
643
|
+
autoTyping: {
|
|
644
|
+
...state.autoTyping,
|
|
645
|
+
revealed: next
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
case "autoTypingDone": return doAdvance({
|
|
650
|
+
...state,
|
|
651
|
+
autoTyping: null
|
|
652
|
+
});
|
|
653
|
+
case "endThinking": return [{
|
|
654
|
+
...state,
|
|
655
|
+
streamPhase: "streaming",
|
|
656
|
+
revealFraction: 0
|
|
657
|
+
}, [fx.interval(50, { type: "streamTick" }, "reveal")]];
|
|
658
|
+
case "streamTick": {
|
|
659
|
+
const last = state.exchanges[state.exchanges.length - 1];
|
|
660
|
+
const rate = last?.thinking ? .08 : .12;
|
|
661
|
+
const frac = Math.min(state.revealFraction + rate, 1);
|
|
662
|
+
if (frac < 1) return {
|
|
663
|
+
...state,
|
|
664
|
+
revealFraction: frac
|
|
665
|
+
};
|
|
666
|
+
const tools = last?.toolCalls ?? [];
|
|
667
|
+
if (tools.length > 0) return [{
|
|
668
|
+
...state,
|
|
669
|
+
streamPhase: "tools",
|
|
670
|
+
revealFraction: 1
|
|
671
|
+
}, [fx.cancel("reveal"), fx.delay(600 * tools.length, { type: "endTools" })]];
|
|
672
|
+
const s = {
|
|
673
|
+
...state,
|
|
674
|
+
streamPhase: "done",
|
|
675
|
+
revealFraction: 1
|
|
676
|
+
};
|
|
677
|
+
return [s, [fx.cancel("reveal"), ...autoAdvanceEffects(s)]];
|
|
678
|
+
}
|
|
679
|
+
case "endTools": {
|
|
680
|
+
const s = {
|
|
681
|
+
...state,
|
|
682
|
+
streamPhase: "done"
|
|
683
|
+
};
|
|
684
|
+
return [s, autoAdvanceEffects(s)];
|
|
685
|
+
}
|
|
686
|
+
case "submit": {
|
|
687
|
+
const base = state.streamPhase !== "done" ? {
|
|
688
|
+
...state,
|
|
689
|
+
streamPhase: "done",
|
|
690
|
+
revealFraction: 1,
|
|
691
|
+
autoTyping: null
|
|
692
|
+
} : state.autoTyping ? {
|
|
693
|
+
...state,
|
|
694
|
+
autoTyping: null
|
|
695
|
+
} : state;
|
|
696
|
+
const cancelEffects = state.streamPhase !== "done" ? [fx.cancel("reveal"), fx.cancel("typing")] : [fx.cancel("typing")];
|
|
697
|
+
if (!msg.text.trim()) return [base, cancelEffects];
|
|
698
|
+
if (base.done) return base;
|
|
699
|
+
const s = addExchange(base, {
|
|
700
|
+
role: "user",
|
|
701
|
+
content: msg.text,
|
|
702
|
+
tokens: {
|
|
703
|
+
input: msg.text.length * 4,
|
|
704
|
+
output: 0
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
if (s.scriptIdx < script.length) {
|
|
708
|
+
let nextIdx = s.scriptIdx;
|
|
709
|
+
while (nextIdx < script.length && script[nextIdx].role === "user") nextIdx++;
|
|
710
|
+
return [{
|
|
711
|
+
...s,
|
|
712
|
+
scriptIdx: nextIdx
|
|
713
|
+
}, [...cancelEffects, fx.delay(150, { type: "autoAdvance" })]];
|
|
714
|
+
}
|
|
715
|
+
return [{
|
|
716
|
+
...s,
|
|
717
|
+
offScript: true
|
|
718
|
+
}, [...cancelEffects, fx.delay(150, { type: "respondRandom" })]];
|
|
719
|
+
}
|
|
720
|
+
case "respondRandom": {
|
|
721
|
+
const resp = RANDOM_AGENT_RESPONSES[Math.floor(Math.random() * RANDOM_AGENT_RESPONSES.length)];
|
|
722
|
+
const [s, effects] = startStreaming(state, resp);
|
|
723
|
+
return [{
|
|
724
|
+
...s,
|
|
725
|
+
offScript: true
|
|
726
|
+
}, effects];
|
|
727
|
+
}
|
|
728
|
+
case "compact": {
|
|
729
|
+
if (state.done || state.compacting) return state;
|
|
730
|
+
const cumulative = computeCumulativeTokens(state.exchanges);
|
|
731
|
+
return [{
|
|
732
|
+
...state,
|
|
733
|
+
streamPhase: "done",
|
|
734
|
+
revealFraction: 1,
|
|
735
|
+
compacting: true,
|
|
736
|
+
contextBaseline: cumulative.currentContext,
|
|
737
|
+
exchanges: state.exchanges,
|
|
738
|
+
autoTyping: null
|
|
739
|
+
}, [
|
|
740
|
+
fx.cancel("reveal"),
|
|
741
|
+
fx.cancel("typing"),
|
|
742
|
+
fx.delay(fastMode ? 300 : 3e3, { type: "compactDone" })
|
|
743
|
+
]];
|
|
744
|
+
}
|
|
745
|
+
case "compactDone": return doAdvance({
|
|
746
|
+
...state,
|
|
747
|
+
compacting: false
|
|
748
|
+
});
|
|
749
|
+
case "pulse": return {
|
|
750
|
+
...state,
|
|
751
|
+
pulse: !state.pulse
|
|
752
|
+
};
|
|
753
|
+
case "setCtrlDPending": return {
|
|
754
|
+
...state,
|
|
755
|
+
ctrlDPending: msg.pending
|
|
756
|
+
};
|
|
757
|
+
default: return state;
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
//#endregion
|
|
762
|
+
//#region apps/aichat/components.tsx
|
|
763
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
764
|
+
/** Split content into a short title (first sentence) and the remaining body.
|
|
765
|
+
* Title must be ≤40 chars to fit on the header line with metadata. */
|
|
766
|
+
function splitTitleBody(content) {
|
|
767
|
+
const match = content.match(/^(.+?[.!?])\s+(.+)$/s);
|
|
768
|
+
if (match && match[1].length <= 40) return {
|
|
769
|
+
title: match[1],
|
|
770
|
+
body: match[2]
|
|
771
|
+
};
|
|
772
|
+
if (content.length <= 40) return {
|
|
773
|
+
title: content,
|
|
774
|
+
body: ""
|
|
775
|
+
};
|
|
776
|
+
return {
|
|
777
|
+
title: "",
|
|
778
|
+
body: content
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
/** Render a line with auto-linked URLs. */
|
|
782
|
+
function LinkifiedLine({ text, dim, color }) {
|
|
783
|
+
const parts = [];
|
|
784
|
+
let lastIndex = 0;
|
|
785
|
+
let match;
|
|
786
|
+
URL_RE.lastIndex = 0;
|
|
787
|
+
while ((match = URL_RE.exec(text)) !== null) {
|
|
788
|
+
if (match.index > lastIndex) parts.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
789
|
+
dim,
|
|
790
|
+
color,
|
|
791
|
+
children: text.slice(lastIndex, match.index)
|
|
792
|
+
}, `t${lastIndex}`));
|
|
793
|
+
const url = match[0];
|
|
794
|
+
parts.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
|
|
795
|
+
href: url,
|
|
796
|
+
dim,
|
|
797
|
+
children: url
|
|
798
|
+
}, `l${match.index}`));
|
|
799
|
+
lastIndex = match.index + url.length;
|
|
800
|
+
}
|
|
801
|
+
if (lastIndex < text.length) parts.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
802
|
+
dim,
|
|
803
|
+
color,
|
|
804
|
+
children: text.slice(lastIndex)
|
|
805
|
+
}, `t${lastIndex}`));
|
|
806
|
+
if (parts.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
807
|
+
dim,
|
|
808
|
+
color,
|
|
809
|
+
children: text
|
|
810
|
+
});
|
|
811
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: parts });
|
|
812
|
+
}
|
|
813
|
+
/** Thinking block — shows thinking text preview in the body. */
|
|
814
|
+
function ThinkingBlock({ text, done }) {
|
|
815
|
+
if (done) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
816
|
+
color: "$muted",
|
|
817
|
+
italic: true,
|
|
818
|
+
children: "▸ thought"
|
|
819
|
+
});
|
|
820
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
821
|
+
color: "$muted",
|
|
822
|
+
wrap: "truncate",
|
|
823
|
+
italic: true,
|
|
824
|
+
children: text
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
/** Tool call with lifecycle: spinner -> output -> checkmark. */
|
|
828
|
+
function ToolCallBlock({ call, phase }) {
|
|
829
|
+
const color = TOOL_COLORS[call.tool] ?? "$muted";
|
|
830
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
831
|
+
flexDirection: "column",
|
|
832
|
+
marginTop: 0,
|
|
833
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
|
|
834
|
+
phase === "running" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { type: "dots" }), " "] }) : phase === "done" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
835
|
+
color: "$success",
|
|
836
|
+
children: "✓ "
|
|
837
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
838
|
+
color: "$muted",
|
|
839
|
+
children: "○ "
|
|
840
|
+
}),
|
|
841
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
842
|
+
color,
|
|
843
|
+
bold: true,
|
|
844
|
+
children: call.tool
|
|
845
|
+
}),
|
|
846
|
+
" ",
|
|
847
|
+
call.tool === "Bash" || call.tool === "Grep" || call.tool === "Glob" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
848
|
+
color: "$muted",
|
|
849
|
+
children: call.args
|
|
850
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, {
|
|
851
|
+
href: `file://${call.args}`,
|
|
852
|
+
children: call.args
|
|
853
|
+
})
|
|
854
|
+
] }), phase === "done" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
|
|
855
|
+
flexDirection: "column",
|
|
856
|
+
paddingLeft: 2,
|
|
857
|
+
children: call.output.map((line, i) => {
|
|
858
|
+
if (line.startsWith("+")) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkifiedLine, {
|
|
859
|
+
text: line,
|
|
860
|
+
color: "$success"
|
|
861
|
+
}, i);
|
|
862
|
+
if (line.startsWith("-")) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkifiedLine, {
|
|
863
|
+
text: line,
|
|
864
|
+
color: "$error"
|
|
865
|
+
}, i);
|
|
866
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkifiedLine, { text: line }, i);
|
|
867
|
+
})
|
|
868
|
+
})]
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
/** Streaming text — reveals content word by word. */
|
|
872
|
+
function StreamingText({ fullText, revealFraction, showCursor }) {
|
|
873
|
+
if (revealFraction >= 1) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: fullText });
|
|
874
|
+
const words = fullText.split(/(\s+)/);
|
|
875
|
+
const totalWords = words.filter((w) => w.trim()).length;
|
|
876
|
+
const revealWords = Math.ceil(totalWords * revealFraction);
|
|
877
|
+
let wordCount = 0;
|
|
878
|
+
let revealedText = "";
|
|
879
|
+
for (const word of words) {
|
|
880
|
+
if (word.trim()) {
|
|
881
|
+
wordCount++;
|
|
882
|
+
if (wordCount > revealWords) break;
|
|
883
|
+
}
|
|
884
|
+
revealedText += word;
|
|
885
|
+
}
|
|
886
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [revealedText, showCursor && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
887
|
+
color: "$primary",
|
|
888
|
+
children: "▌"
|
|
889
|
+
})] });
|
|
890
|
+
}
|
|
891
|
+
function ExchangeItem({ exchange, streamPhase, revealFraction, pulse, isLatest, isFirstInGroup, isLastInGroup }) {
|
|
892
|
+
if (exchange.role === "system") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
893
|
+
flexDirection: "column",
|
|
894
|
+
children: [
|
|
895
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }),
|
|
896
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
897
|
+
bold: true,
|
|
898
|
+
children: "AI Chat"
|
|
899
|
+
}),
|
|
900
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }),
|
|
901
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
902
|
+
color: "$muted",
|
|
903
|
+
children: exchange.content
|
|
904
|
+
}),
|
|
905
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " })
|
|
906
|
+
]
|
|
907
|
+
});
|
|
908
|
+
if (exchange.role === "user") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
909
|
+
paddingX: 1,
|
|
910
|
+
flexDirection: "row",
|
|
911
|
+
backgroundColor: "$surface-bg",
|
|
912
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
|
|
913
|
+
bold: true,
|
|
914
|
+
color: "$focusring",
|
|
915
|
+
children: ["❯", " "]
|
|
916
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
|
|
917
|
+
flexShrink: 1,
|
|
918
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: exchange.content })
|
|
919
|
+
})]
|
|
920
|
+
});
|
|
921
|
+
const phase = isLatest ? streamPhase : "done";
|
|
922
|
+
const fraction = isLatest ? revealFraction : 1;
|
|
923
|
+
const toolCalls = exchange.toolCalls ?? [];
|
|
924
|
+
const toolRevealCount = phase === "tools" || phase === "done" ? toolCalls.length : 0;
|
|
925
|
+
const hasOperations = toolCalls.length > 0 || !!exchange.thinking;
|
|
926
|
+
const metaParts = [];
|
|
927
|
+
if (exchange.tokens && phase === "done") metaParts.push(`${formatTokens(exchange.tokens.output)} tokens`);
|
|
928
|
+
if (exchange.thinking && (phase === "done" || phase === "streaming")) metaParts.push("thought for 1s");
|
|
929
|
+
const metaStr = metaParts.length > 0 ? ` (${metaParts.join(" · ")})` : "";
|
|
930
|
+
const { title, body } = splitTitleBody(exchange.content);
|
|
931
|
+
const bulletColor = hasOperations ? "$success" : "$muted";
|
|
932
|
+
const contentText = title ? body : exchange.content;
|
|
933
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
934
|
+
flexDirection: "column",
|
|
935
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
936
|
+
bold: true,
|
|
937
|
+
color: bulletColor,
|
|
938
|
+
dimColor: hasOperations && !pulse && phase !== "done",
|
|
939
|
+
children: "●"
|
|
940
|
+
}), phase === "thinking" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
|
|
941
|
+
color: "$muted",
|
|
942
|
+
italic: true,
|
|
943
|
+
children: [
|
|
944
|
+
" ",
|
|
945
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { type: "dots" }),
|
|
946
|
+
" thinking"
|
|
947
|
+
]
|
|
948
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [title && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [" ", title] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
949
|
+
color: "$muted",
|
|
950
|
+
children: metaStr
|
|
951
|
+
})] })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
952
|
+
flexDirection: "column",
|
|
953
|
+
borderStyle: "bold",
|
|
954
|
+
borderColor: "$border",
|
|
955
|
+
borderLeft: true,
|
|
956
|
+
borderRight: false,
|
|
957
|
+
borderTop: false,
|
|
958
|
+
borderBottom: false,
|
|
959
|
+
paddingLeft: 1,
|
|
960
|
+
children: [
|
|
961
|
+
exchange.thinking && (phase === "thinking" || phase === "streaming") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThinkingBlock, {
|
|
962
|
+
text: exchange.thinking,
|
|
963
|
+
done: phase !== "thinking"
|
|
964
|
+
}),
|
|
965
|
+
(phase === "streaming" || phase === "tools" || phase === "done") && contentText && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StreamingText, {
|
|
966
|
+
fullText: contentText,
|
|
967
|
+
revealFraction: phase === "streaming" ? fraction : 1,
|
|
968
|
+
showCursor: phase === "streaming" && fraction < 1
|
|
969
|
+
}),
|
|
970
|
+
toolRevealCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
|
|
971
|
+
flexDirection: "column",
|
|
972
|
+
children: toolCalls.map((call, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolCallBlock, {
|
|
973
|
+
call,
|
|
974
|
+
phase: phase === "done" ? "done" : i < toolRevealCount - 1 ? "done" : "running"
|
|
975
|
+
}, i))
|
|
976
|
+
})
|
|
977
|
+
]
|
|
978
|
+
})]
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
function StatusBar({ exchanges, compacting, done, elapsed, contextBaseline = 0, ctrlDPending = false }) {
|
|
982
|
+
const cumulative = computeCumulativeTokens(exchanges);
|
|
983
|
+
const cost = formatCost(cumulative.input, cumulative.output);
|
|
984
|
+
const elapsedStr = `${Math.floor(elapsed / 60)}:${(elapsed % 60).toString().padStart(2, "0")}`;
|
|
985
|
+
const CTX_W = 20;
|
|
986
|
+
const ctxFrac = Math.max(0, cumulative.currentContext - contextBaseline) / CONTEXT_WINDOW;
|
|
987
|
+
const ctxFilled = Math.round(Math.min(ctxFrac, 1) * CTX_W);
|
|
988
|
+
const ctxPct = Math.round(ctxFrac * 100);
|
|
989
|
+
const ctxColor = ctxPct > 100 ? "$error" : ctxPct > 80 ? "$warning" : "$primary";
|
|
990
|
+
const ctxBar = "█".repeat(ctxFilled) + "░".repeat(CTX_W - ctxFilled);
|
|
991
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
992
|
+
flexDirection: "row",
|
|
993
|
+
justifyContent: "space-between",
|
|
994
|
+
width: "100%",
|
|
995
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
|
|
996
|
+
color: "$muted",
|
|
997
|
+
wrap: "truncate",
|
|
998
|
+
children: [
|
|
999
|
+
elapsedStr,
|
|
1000
|
+
" ",
|
|
1001
|
+
ctrlDPending ? "Ctrl-D again to exit" : compacting ? "compacting..." : "esc quit"
|
|
1002
|
+
]
|
|
1003
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
|
|
1004
|
+
color: ctxPct > 80 ? ctxColor : "$muted",
|
|
1005
|
+
wrap: "truncate",
|
|
1006
|
+
children: [
|
|
1007
|
+
"ctx ",
|
|
1008
|
+
ctxBar,
|
|
1009
|
+
" ",
|
|
1010
|
+
ctxPct,
|
|
1011
|
+
"%",
|
|
1012
|
+
" ",
|
|
1013
|
+
cost
|
|
1014
|
+
]
|
|
1015
|
+
})]
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
const AUTO_SUBMIT_DELAY = 1e4;
|
|
1019
|
+
function DemoFooter({ controlRef, onSubmit, streamPhase, done, compacting, exchanges, contextBaseline = 0, ctrlDPending = false, nextMessage = "", autoTypingText = null }) {
|
|
1020
|
+
const terminalFocused = useTerminalFocused();
|
|
1021
|
+
const [inputText, setInputText] = useState("");
|
|
1022
|
+
const inputTextRef = useRef(inputText);
|
|
1023
|
+
inputTextRef.current = inputText;
|
|
1024
|
+
const startRef = useRef(Date.now());
|
|
1025
|
+
const [elapsed, setElapsed] = useState(0);
|
|
1026
|
+
useEffect(() => {
|
|
1027
|
+
const timer = setInterval(() => setElapsed(Math.floor((Date.now() - startRef.current) / 1e3)), 1e3);
|
|
1028
|
+
return () => clearInterval(timer);
|
|
1029
|
+
}, []);
|
|
1030
|
+
const [randomIdx, setRandomIdx] = useState(() => Math.floor(Math.random() * RANDOM_USER_COMMANDS.length));
|
|
1031
|
+
const randomPlaceholder = RANDOM_USER_COMMANDS[randomIdx % RANDOM_USER_COMMANDS.length];
|
|
1032
|
+
const effectiveMessage = nextMessage || randomPlaceholder;
|
|
1033
|
+
const placeholder = !terminalFocused ? "Click to focus" : ctrlDPending ? "Press Ctrl-D again to exit" : effectiveMessage;
|
|
1034
|
+
const handleSubmit = useCallback((text) => {
|
|
1035
|
+
if (!text.trim() && effectiveMessage) onSubmit(effectiveMessage);
|
|
1036
|
+
else onSubmit(text);
|
|
1037
|
+
setInputText("");
|
|
1038
|
+
setRandomIdx((i) => i + 1);
|
|
1039
|
+
}, [onSubmit, effectiveMessage]);
|
|
1040
|
+
controlRef.current = { submit: () => handleSubmit(inputTextRef.current) };
|
|
1041
|
+
const autoSubmitRef = useRef(null);
|
|
1042
|
+
useEffect(() => {
|
|
1043
|
+
if (autoSubmitRef.current) clearTimeout(autoSubmitRef.current);
|
|
1044
|
+
if (done || compacting || streamPhase !== "done" || !effectiveMessage || inputText || autoTypingText || !terminalFocused) return;
|
|
1045
|
+
autoSubmitRef.current = setTimeout(() => onSubmit(effectiveMessage), AUTO_SUBMIT_DELAY);
|
|
1046
|
+
return () => {
|
|
1047
|
+
if (autoSubmitRef.current) clearTimeout(autoSubmitRef.current);
|
|
1048
|
+
};
|
|
1049
|
+
}, [
|
|
1050
|
+
done,
|
|
1051
|
+
compacting,
|
|
1052
|
+
streamPhase,
|
|
1053
|
+
effectiveMessage,
|
|
1054
|
+
inputText,
|
|
1055
|
+
autoTypingText,
|
|
1056
|
+
onSubmit
|
|
1057
|
+
]);
|
|
1058
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
1059
|
+
flexDirection: "column",
|
|
1060
|
+
width: "100%",
|
|
1061
|
+
children: [
|
|
1062
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }),
|
|
1063
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
1064
|
+
flexDirection: "row",
|
|
1065
|
+
borderStyle: "round",
|
|
1066
|
+
borderColor: !done && terminalFocused ? "$focusborder" : "$inputborder",
|
|
1067
|
+
paddingX: 1,
|
|
1068
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
|
|
1069
|
+
bold: true,
|
|
1070
|
+
color: "$focusring",
|
|
1071
|
+
children: ["❯", " "]
|
|
1072
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
|
|
1073
|
+
flexShrink: 1,
|
|
1074
|
+
flexGrow: 1,
|
|
1075
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextInput, {
|
|
1076
|
+
value: autoTypingText ?? inputText,
|
|
1077
|
+
onChange: autoTypingText ? () => {} : setInputText,
|
|
1078
|
+
onSubmit: handleSubmit,
|
|
1079
|
+
placeholder,
|
|
1080
|
+
isActive: !done && !autoTypingText && terminalFocused
|
|
1081
|
+
})
|
|
1082
|
+
})]
|
|
1083
|
+
}),
|
|
1084
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
|
|
1085
|
+
paddingX: 2,
|
|
1086
|
+
width: "100%",
|
|
1087
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBar, {
|
|
1088
|
+
exchanges,
|
|
1089
|
+
compacting,
|
|
1090
|
+
done,
|
|
1091
|
+
elapsed,
|
|
1092
|
+
contextBaseline,
|
|
1093
|
+
ctrlDPending
|
|
1094
|
+
})
|
|
1095
|
+
})
|
|
1096
|
+
]
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
//#endregion
|
|
1100
|
+
//#region apps/aichat/index.tsx
|
|
1101
|
+
/**
|
|
1102
|
+
* AI Chat — Coding Agent Demo
|
|
1103
|
+
*
|
|
1104
|
+
* Showcases ListView with streaming, tool calls, context tracking.
|
|
1105
|
+
* TEA state machine drives all animation; ListView caches completed
|
|
1106
|
+
* exchanges while live content stays in the React tree.
|
|
1107
|
+
*
|
|
1108
|
+
* Flags: --auto (auto-advance) --fast (skip animation) --stress (200 exchanges)
|
|
1109
|
+
*/
|
|
1110
|
+
const meta = {
|
|
1111
|
+
name: "AI Coding Agent",
|
|
1112
|
+
description: "Coding agent showcase — ListView, streaming, context tracking",
|
|
1113
|
+
demo: true,
|
|
1114
|
+
features: [
|
|
1115
|
+
"ListView",
|
|
1116
|
+
"cache",
|
|
1117
|
+
"inline mode",
|
|
1118
|
+
"streaming",
|
|
1119
|
+
"OSC 8 links"
|
|
1120
|
+
]
|
|
1121
|
+
};
|
|
1122
|
+
function AIChat({ script, autoStart, fastMode }) {
|
|
1123
|
+
const exit = useExit();
|
|
1124
|
+
const { rows: termRows } = useWindowSize();
|
|
1125
|
+
const [state, send] = useTea(INIT_STATE, useMemo(() => createDemoUpdate(script, fastMode, autoStart), [
|
|
1126
|
+
script,
|
|
1127
|
+
fastMode,
|
|
1128
|
+
autoStart
|
|
1129
|
+
]));
|
|
1130
|
+
const footerControlRef = useRef({ submit: () => {} });
|
|
1131
|
+
useEffect(() => send({ type: "mount" }), [send]);
|
|
1132
|
+
useAutoCompact(state, send);
|
|
1133
|
+
useAutoExit(autoStart, state.done, exit);
|
|
1134
|
+
useKeyBindings(state, send, footerControlRef);
|
|
1135
|
+
const renderExchange = useCallback((exchange, index, _meta) => {
|
|
1136
|
+
const isLatest = index === state.exchanges.length - 1;
|
|
1137
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
1138
|
+
flexDirection: "column",
|
|
1139
|
+
children: [
|
|
1140
|
+
index > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }),
|
|
1141
|
+
state.compacting && isLatest && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CompactingOverlay, {}),
|
|
1142
|
+
state.done && autoStart && isLatest && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SessionComplete, {}),
|
|
1143
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ExchangeItem, {
|
|
1144
|
+
exchange,
|
|
1145
|
+
streamPhase: state.streamPhase,
|
|
1146
|
+
revealFraction: state.revealFraction,
|
|
1147
|
+
pulse: state.pulse,
|
|
1148
|
+
isLatest,
|
|
1149
|
+
isFirstInGroup: exchange.role !== (index > 0 ? state.exchanges[index - 1].role : null),
|
|
1150
|
+
isLastInGroup: exchange.role !== (index < state.exchanges.length - 1 ? state.exchanges[index + 1].role : null)
|
|
1151
|
+
})
|
|
1152
|
+
]
|
|
1153
|
+
});
|
|
1154
|
+
}, [state, autoStart]);
|
|
1155
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
|
|
1156
|
+
flexDirection: "column",
|
|
1157
|
+
paddingX: 1,
|
|
1158
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ListView, {
|
|
1159
|
+
items: state.exchanges,
|
|
1160
|
+
getKey: (ex) => ex.id,
|
|
1161
|
+
height: termRows,
|
|
1162
|
+
estimateHeight: 6,
|
|
1163
|
+
renderItem: renderExchange,
|
|
1164
|
+
scrollTo: state.exchanges.length - 1,
|
|
1165
|
+
cache: {
|
|
1166
|
+
mode: "virtual",
|
|
1167
|
+
isCacheable: (_ex, index) => index < state.exchanges.length - 1
|
|
1168
|
+
},
|
|
1169
|
+
listFooter: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DemoFooter, {
|
|
1170
|
+
controlRef: footerControlRef,
|
|
1171
|
+
onSubmit: (text) => send({
|
|
1172
|
+
type: "submit",
|
|
1173
|
+
text
|
|
1174
|
+
}),
|
|
1175
|
+
streamPhase: state.streamPhase,
|
|
1176
|
+
done: state.done,
|
|
1177
|
+
compacting: state.compacting,
|
|
1178
|
+
exchanges: state.exchanges,
|
|
1179
|
+
contextBaseline: state.contextBaseline,
|
|
1180
|
+
ctrlDPending: state.ctrlDPending,
|
|
1181
|
+
nextMessage: getNextMessage(state, script, autoStart),
|
|
1182
|
+
autoTypingText: state.autoTyping ? state.autoTyping.full.slice(0, state.autoTyping.revealed) : null
|
|
1183
|
+
})
|
|
1184
|
+
})
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
async function main() {
|
|
1188
|
+
try {
|
|
1189
|
+
var _usingCtx$1 = _usingCtx();
|
|
1190
|
+
const args = process.argv.slice(2);
|
|
1191
|
+
const script = args.includes("--stress") ? generateStressScript() : SCRIPT;
|
|
1192
|
+
const mode = args.includes("--inline") ? "inline" : "fullscreen";
|
|
1193
|
+
await _usingCtx$1.u(await run(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AIChat, {
|
|
1194
|
+
script,
|
|
1195
|
+
autoStart: args.includes("--auto"),
|
|
1196
|
+
fastMode: args.includes("--fast")
|
|
1197
|
+
}), {
|
|
1198
|
+
mode,
|
|
1199
|
+
focusReporting: true
|
|
1200
|
+
})).waitUntilExit();
|
|
1201
|
+
} catch (_) {
|
|
1202
|
+
_usingCtx$1.e = _;
|
|
1203
|
+
} finally {
|
|
1204
|
+
_usingCtx$1.d();
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (import.meta.main) await main();
|
|
1208
|
+
function useAutoCompact(state, send) {
|
|
1209
|
+
useEffect(() => {
|
|
1210
|
+
if (state.done || state.compacting) return;
|
|
1211
|
+
const cumulative = computeCumulativeTokens(state.exchanges);
|
|
1212
|
+
if (Math.max(0, cumulative.currentContext - state.contextBaseline) >= 2e5 * .95) send({ type: "compact" });
|
|
1213
|
+
}, [
|
|
1214
|
+
state.exchanges,
|
|
1215
|
+
state.done,
|
|
1216
|
+
state.compacting,
|
|
1217
|
+
state.contextBaseline,
|
|
1218
|
+
send
|
|
1219
|
+
]);
|
|
1220
|
+
}
|
|
1221
|
+
function useAutoExit(autoStart, done, exit) {
|
|
1222
|
+
useEffect(() => {
|
|
1223
|
+
if (!autoStart || !done) return;
|
|
1224
|
+
const timer = setTimeout(exit, 1e3);
|
|
1225
|
+
return () => clearTimeout(timer);
|
|
1226
|
+
}, [
|
|
1227
|
+
autoStart,
|
|
1228
|
+
done,
|
|
1229
|
+
exit
|
|
1230
|
+
]);
|
|
1231
|
+
}
|
|
1232
|
+
function useKeyBindings(state, send, footerControlRef) {
|
|
1233
|
+
const lastCtrlDRef = useRef(0);
|
|
1234
|
+
useInput$1((input, key) => {
|
|
1235
|
+
if (key.escape) return "exit";
|
|
1236
|
+
if (key.ctrl && input === "d") {
|
|
1237
|
+
const now = Date.now();
|
|
1238
|
+
if (now - lastCtrlDRef.current < 500) return "exit";
|
|
1239
|
+
lastCtrlDRef.current = now;
|
|
1240
|
+
send({
|
|
1241
|
+
type: "setCtrlDPending",
|
|
1242
|
+
pending: true
|
|
1243
|
+
});
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
if (lastCtrlDRef.current > 0) {
|
|
1247
|
+
lastCtrlDRef.current = 0;
|
|
1248
|
+
send({
|
|
1249
|
+
type: "setCtrlDPending",
|
|
1250
|
+
pending: false
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
if (key.tab) {
|
|
1254
|
+
if (state.done || state.compacting) return;
|
|
1255
|
+
footerControlRef.current.submit();
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
if (key.ctrl && input === "l") send({ type: "compact" });
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
function CompactingOverlay() {
|
|
1262
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
1263
|
+
flexDirection: "column",
|
|
1264
|
+
borderStyle: "round",
|
|
1265
|
+
borderColor: "$warning",
|
|
1266
|
+
paddingX: 1,
|
|
1267
|
+
overflow: "hidden",
|
|
1268
|
+
children: [
|
|
1269
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
|
|
1270
|
+
color: "$warning",
|
|
1271
|
+
bold: true,
|
|
1272
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { type: "arc" }), " Compacting context"]
|
|
1273
|
+
}),
|
|
1274
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }),
|
|
1275
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
1276
|
+
color: "$muted",
|
|
1277
|
+
children: "Freezing exchanges into terminal scrollback. Scroll up to review."
|
|
1278
|
+
})
|
|
1279
|
+
]
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
function SessionComplete() {
|
|
1283
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
|
|
1284
|
+
flexDirection: "column",
|
|
1285
|
+
borderStyle: "round",
|
|
1286
|
+
borderColor: "$success",
|
|
1287
|
+
paddingX: 1,
|
|
1288
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
|
|
1289
|
+
color: "$success",
|
|
1290
|
+
bold: true,
|
|
1291
|
+
children: ["✓", " Session complete"]
|
|
1292
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
|
|
1293
|
+
color: "$muted",
|
|
1294
|
+
children: "Scroll up to review — colors, borders, and hyperlinks preserved in scrollback."
|
|
1295
|
+
})]
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
//#endregion
|
|
1299
|
+
export { AIChat, CONTEXT_WINDOW, SCRIPT, generateStressScript, main, meta };
|