@agent-link/server 0.1.186 → 0.1.188
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/index.js +13 -15
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/web/dist/assets/index-C9bIrYkZ.js +320 -0
- package/web/dist/assets/index-C9bIrYkZ.js.map +1 -0
- package/web/dist/assets/index-Y1FN_mFe.css +1 -0
- package/web/{index.html → dist/index.html} +2 -19
- package/web/app.js +0 -2881
- package/web/css/ask-question.css +0 -333
- package/web/css/base.css +0 -270
- package/web/css/btw.css +0 -148
- package/web/css/chat.css +0 -176
- package/web/css/file-browser.css +0 -499
- package/web/css/input.css +0 -671
- package/web/css/loop.css +0 -674
- package/web/css/markdown.css +0 -169
- package/web/css/responsive.css +0 -314
- package/web/css/sidebar.css +0 -593
- package/web/css/team.css +0 -1277
- package/web/css/tools.css +0 -327
- package/web/encryption.js +0 -56
- package/web/modules/appHelpers.js +0 -100
- package/web/modules/askQuestion.js +0 -63
- package/web/modules/backgroundRouting.js +0 -269
- package/web/modules/connection.js +0 -731
- package/web/modules/fileAttachments.js +0 -125
- package/web/modules/fileBrowser.js +0 -379
- package/web/modules/filePreview.js +0 -213
- package/web/modules/i18n.js +0 -101
- package/web/modules/loop.js +0 -338
- package/web/modules/loopTemplates.js +0 -110
- package/web/modules/markdown.js +0 -83
- package/web/modules/messageHelpers.js +0 -206
- package/web/modules/sidebar.js +0 -402
- package/web/modules/streaming.js +0 -116
- package/web/modules/team.js +0 -396
- package/web/modules/teamTemplates.js +0 -360
- package/web/vendor/highlight.min.js +0 -1213
- package/web/vendor/marked.min.js +0 -6
- package/web/vendor/nacl-fast.min.js +0 -1
- package/web/vendor/nacl-util.min.js +0 -1
- package/web/vendor/pako.min.js +0 -2
- package/web/vendor/vue.global.prod.js +0 -13
- /package/web/{favicon.svg → dist/favicon.svg} +0 -0
- /package/web/{images → dist/images}/chat-iPad.webp +0 -0
- /package/web/{images → dist/images}/chat-iPhone.webp +0 -0
- /package/web/{images → dist/images}/loop-iPad.webp +0 -0
- /package/web/{images → dist/images}/team-iPad.webp +0 -0
- /package/web/{landing.html → dist/landing.html} +0 -0
- /package/web/{landing.zh.html → dist/landing.zh.html} +0 -0
- /package/web/{locales → dist/locales}/en.json +0 -0
- /package/web/{locales → dist/locales}/zh.json +0 -0
- /package/web/{vendor → dist/vendor}/github-dark.min.css +0 -0
- /package/web/{vendor → dist/vendor}/github.min.css +0 -0
package/web/css/tools.css
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
/* ── Tool use lines (collapsible, compact, subdued) ── */
|
|
2
|
-
.tool-line-wrapper {
|
|
3
|
-
max-width: 100%;
|
|
4
|
-
padding-left: 0.25rem;
|
|
5
|
-
overflow: hidden;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.tool-line {
|
|
9
|
-
display: flex;
|
|
10
|
-
align-items: center;
|
|
11
|
-
gap: 6px;
|
|
12
|
-
padding: 3px 6px;
|
|
13
|
-
border-radius: 4px;
|
|
14
|
-
font-size: 0.8rem;
|
|
15
|
-
cursor: pointer;
|
|
16
|
-
user-select: none;
|
|
17
|
-
transition: background 0.15s;
|
|
18
|
-
color: var(--text-secondary);
|
|
19
|
-
min-width: 0;
|
|
20
|
-
overflow: hidden;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.tool-line:hover {
|
|
24
|
-
background: var(--bg-tertiary);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.tool-icon {
|
|
28
|
-
flex-shrink: 0;
|
|
29
|
-
display: flex;
|
|
30
|
-
align-items: center;
|
|
31
|
-
color: var(--text-secondary);
|
|
32
|
-
opacity: 0.6;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.tool-name {
|
|
36
|
-
font-weight: 500;
|
|
37
|
-
color: var(--text-secondary);
|
|
38
|
-
overflow: hidden;
|
|
39
|
-
text-overflow: ellipsis;
|
|
40
|
-
white-space: nowrap;
|
|
41
|
-
min-width: 0;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.tool-summary {
|
|
45
|
-
color: var(--text-secondary);
|
|
46
|
-
overflow: hidden;
|
|
47
|
-
text-overflow: ellipsis;
|
|
48
|
-
white-space: nowrap;
|
|
49
|
-
flex: 1;
|
|
50
|
-
min-width: 0;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.tool-status-icon {
|
|
54
|
-
color: var(--text-secondary);
|
|
55
|
-
font-weight: normal;
|
|
56
|
-
flex-shrink: 0;
|
|
57
|
-
font-size: 0.75rem;
|
|
58
|
-
opacity: 0.7;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.tool-line.completed .tool-status-icon {
|
|
62
|
-
color: var(--success);
|
|
63
|
-
opacity: 0.6;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.running-dots {
|
|
67
|
-
display: inline-flex;
|
|
68
|
-
gap: 2px;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.running-dots span {
|
|
72
|
-
width: 3px;
|
|
73
|
-
height: 3px;
|
|
74
|
-
border-radius: 50%;
|
|
75
|
-
background: var(--text-secondary);
|
|
76
|
-
animation: typing 1.2s infinite ease-in-out;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.running-dots span:nth-child(2) { animation-delay: 0.2s; }
|
|
80
|
-
.running-dots span:nth-child(3) { animation-delay: 0.4s; }
|
|
81
|
-
|
|
82
|
-
.tool-toggle {
|
|
83
|
-
color: var(--text-secondary);
|
|
84
|
-
font-size: 0.6rem;
|
|
85
|
-
flex-shrink: 0;
|
|
86
|
-
margin-left: auto;
|
|
87
|
-
opacity: 0.5;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.tool-expand {
|
|
91
|
-
margin-top: 2px;
|
|
92
|
-
margin-left: 20px;
|
|
93
|
-
border-left: 1px solid var(--border);
|
|
94
|
-
padding-left: 8px;
|
|
95
|
-
overflow: hidden;
|
|
96
|
-
animation: toolExpand 0.15s ease-out;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
@keyframes toolExpand {
|
|
100
|
-
from { opacity: 0; }
|
|
101
|
-
to { opacity: 1; }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.tool-block {
|
|
105
|
-
background: var(--bg-secondary);
|
|
106
|
-
border-radius: 4px;
|
|
107
|
-
padding: 0.4rem 0.6rem;
|
|
108
|
-
margin-top: 0.25rem;
|
|
109
|
-
overflow-x: auto;
|
|
110
|
-
font-size: 0.75rem;
|
|
111
|
-
line-height: 1.4;
|
|
112
|
-
max-height: 200px;
|
|
113
|
-
overflow-y: auto;
|
|
114
|
-
white-space: pre-wrap;
|
|
115
|
-
word-break: break-all;
|
|
116
|
-
color: var(--text-secondary);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.tool-output {
|
|
120
|
-
border-left: 2px solid var(--success);
|
|
121
|
-
color: var(--text-primary);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/* ── Formatted tool input (Read, Write, Bash, Glob, Grep) ── */
|
|
125
|
-
.tool-input-formatted {
|
|
126
|
-
padding: 0.3rem 0.5rem;
|
|
127
|
-
margin-top: 0.25rem;
|
|
128
|
-
font-size: 0.75rem;
|
|
129
|
-
line-height: 1.5;
|
|
130
|
-
color: var(--text-secondary);
|
|
131
|
-
word-break: break-all;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.tool-input-meta {
|
|
135
|
-
color: var(--text-secondary);
|
|
136
|
-
opacity: 0.6;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.tool-input-cmd {
|
|
140
|
-
background: var(--bg-secondary);
|
|
141
|
-
padding: 0.1rem 0.4rem;
|
|
142
|
-
border-radius: 3px;
|
|
143
|
-
font-size: 0.73rem;
|
|
144
|
-
word-break: break-all;
|
|
145
|
-
white-space: pre-wrap;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/* ── TodoWrite checklist ── */
|
|
149
|
-
.todo-list {
|
|
150
|
-
display: flex;
|
|
151
|
-
flex-direction: column;
|
|
152
|
-
gap: 2px;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.todo-item {
|
|
156
|
-
display: flex;
|
|
157
|
-
align-items: baseline;
|
|
158
|
-
gap: 6px;
|
|
159
|
-
font-size: 0.75rem;
|
|
160
|
-
line-height: 1.4;
|
|
161
|
-
color: var(--text-secondary);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.todo-item.todo-done {
|
|
165
|
-
opacity: 0.55;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.todo-item.todo-active {
|
|
169
|
-
color: var(--text-primary);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.todo-icon {
|
|
173
|
-
flex-shrink: 0;
|
|
174
|
-
width: 1em;
|
|
175
|
-
text-align: center;
|
|
176
|
-
font-size: 0.7rem;
|
|
177
|
-
opacity: 0.5;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.todo-icon.done {
|
|
181
|
-
color: var(--success);
|
|
182
|
-
opacity: 0.8;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.todo-icon.active {
|
|
186
|
-
color: var(--accent);
|
|
187
|
-
opacity: 1;
|
|
188
|
-
font-size: 0.55rem;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.todo-text {
|
|
192
|
-
word-break: break-word;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/* ── Task / WebFetch formatted fields ── */
|
|
196
|
-
.task-field {
|
|
197
|
-
font-size: 0.75rem;
|
|
198
|
-
line-height: 1.5;
|
|
199
|
-
color: var(--text-secondary);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
.task-field + .task-field {
|
|
203
|
-
margin-top: 2px;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
.task-prompt {
|
|
207
|
-
background: var(--bg-secondary);
|
|
208
|
-
border-radius: 3px;
|
|
209
|
-
padding: 0.3rem 0.5rem;
|
|
210
|
-
margin-top: 3px;
|
|
211
|
-
font-size: 0.73rem;
|
|
212
|
-
line-height: 1.4;
|
|
213
|
-
color: var(--text-secondary);
|
|
214
|
-
white-space: pre-wrap;
|
|
215
|
-
word-break: break-word;
|
|
216
|
-
max-height: 150px;
|
|
217
|
-
overflow-y: auto;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.tool-link {
|
|
221
|
-
color: var(--accent);
|
|
222
|
-
font-size: 0.75rem;
|
|
223
|
-
word-break: break-all;
|
|
224
|
-
text-decoration: none;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.tool-link:hover {
|
|
228
|
-
text-decoration: underline;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
[data-theme="light"] .todo-icon.active {
|
|
232
|
-
color: #0969da;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
[data-theme="light"] .tool-link {
|
|
236
|
-
color: #0969da;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/* ── Edit tool diff view ── */
|
|
240
|
-
.tool-diff {
|
|
241
|
-
background: var(--bg-secondary);
|
|
242
|
-
border-radius: 4px;
|
|
243
|
-
padding: 0.4rem 0.6rem;
|
|
244
|
-
margin-top: 0.25rem;
|
|
245
|
-
overflow-x: auto;
|
|
246
|
-
font-size: 0.75rem;
|
|
247
|
-
line-height: 1.5;
|
|
248
|
-
max-height: 300px;
|
|
249
|
-
overflow-y: auto;
|
|
250
|
-
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', monospace;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
.diff-file {
|
|
254
|
-
color: var(--text-secondary);
|
|
255
|
-
margin-bottom: 0.35rem;
|
|
256
|
-
font-weight: 500;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
.diff-replace-all {
|
|
260
|
-
opacity: 0.6;
|
|
261
|
-
font-weight: 400;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
.diff-lines {
|
|
265
|
-
border-radius: 3px;
|
|
266
|
-
overflow: hidden;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
.diff-removed, .diff-added {
|
|
270
|
-
white-space: pre-wrap;
|
|
271
|
-
word-break: break-all;
|
|
272
|
-
padding: 0 0.4rem;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
.diff-removed {
|
|
276
|
-
background: rgba(212, 84, 84, 0.12);
|
|
277
|
-
color: #d4716b;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
.diff-added {
|
|
281
|
-
background: rgba(78, 173, 106, 0.12);
|
|
282
|
-
color: #5cb87a;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
.diff-sign {
|
|
286
|
-
display: inline-block;
|
|
287
|
-
width: 1.2em;
|
|
288
|
-
user-select: none;
|
|
289
|
-
opacity: 0.7;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
[data-theme="light"] .diff-removed {
|
|
293
|
-
background: rgba(208, 46, 38, 0.12);
|
|
294
|
-
color: #b31d28;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
[data-theme="light"] .diff-added {
|
|
298
|
-
background: rgba(34, 134, 58, 0.12);
|
|
299
|
-
color: #22863a;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/* ── Context summary (collapsible continuation notice) ── */
|
|
303
|
-
.context-summary-wrapper {
|
|
304
|
-
margin: 0.75rem 0;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/* ── Plan mode divider ── */
|
|
308
|
-
.plan-mode-divider {
|
|
309
|
-
display: flex;
|
|
310
|
-
align-items: center;
|
|
311
|
-
gap: 12px;
|
|
312
|
-
margin: 8px 0;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
.plan-mode-divider-line {
|
|
316
|
-
flex: 1;
|
|
317
|
-
height: 1px;
|
|
318
|
-
background: var(--border);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
.plan-mode-divider-text {
|
|
322
|
-
font-size: 0.75rem;
|
|
323
|
-
color: var(--text-secondary);
|
|
324
|
-
white-space: nowrap;
|
|
325
|
-
font-weight: 500;
|
|
326
|
-
}
|
|
327
|
-
|
package/web/encryption.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-compatible encryption utilities using TweetNaCl and Pako from CDN.
|
|
3
|
-
* Requires: nacl, nacl.util (tweetnacl-util), and pako globals.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const COMPRESS_THRESHOLD = 512;
|
|
7
|
-
|
|
8
|
-
export function encrypt(data, key) {
|
|
9
|
-
const nonce = nacl.randomBytes(24);
|
|
10
|
-
const jsonStr = JSON.stringify(data);
|
|
11
|
-
|
|
12
|
-
let message;
|
|
13
|
-
let compressed = false;
|
|
14
|
-
|
|
15
|
-
if (jsonStr.length > COMPRESS_THRESHOLD) {
|
|
16
|
-
message = pako.gzip(jsonStr);
|
|
17
|
-
compressed = true;
|
|
18
|
-
} else {
|
|
19
|
-
message = nacl.util.decodeUTF8(jsonStr);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const encrypted = nacl.secretbox(message, nonce, key);
|
|
23
|
-
const result = {
|
|
24
|
-
n: nacl.util.encodeBase64(nonce),
|
|
25
|
-
c: nacl.util.encodeBase64(encrypted),
|
|
26
|
-
};
|
|
27
|
-
if (compressed) result.z = true;
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function decrypt(encrypted, key) {
|
|
32
|
-
try {
|
|
33
|
-
const nonce = nacl.util.decodeBase64(encrypted.n);
|
|
34
|
-
const ciphertext = nacl.util.decodeBase64(encrypted.c);
|
|
35
|
-
const decrypted = nacl.secretbox.open(ciphertext, nonce, key);
|
|
36
|
-
if (!decrypted) return null;
|
|
37
|
-
|
|
38
|
-
if (encrypted.z) {
|
|
39
|
-
const decompressed = pako.ungzip(decrypted, { to: 'string' });
|
|
40
|
-
return JSON.parse(decompressed);
|
|
41
|
-
} else {
|
|
42
|
-
return JSON.parse(nacl.util.encodeUTF8(decrypted));
|
|
43
|
-
}
|
|
44
|
-
} catch (err) {
|
|
45
|
-
console.error('[Decrypt] Failed:', err.message);
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function isEncrypted(msg) {
|
|
51
|
-
return msg && typeof msg === 'object' && typeof msg.n === 'string' && typeof msg.c === 'string';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function decodeKey(encodedKey) {
|
|
55
|
-
return nacl.util.decodeBase64(encodedKey);
|
|
56
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
// ── UI utility functions for the main App component ──────────────────────────
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Create scroll management functions.
|
|
5
|
-
* @param {string} selector - CSS selector for the scrollable element
|
|
6
|
-
* @returns {{ onScroll, scrollToBottom, cleanup }}
|
|
7
|
-
*/
|
|
8
|
-
export function createScrollManager(selector) {
|
|
9
|
-
let _rafId = null;
|
|
10
|
-
let _userScrolledUp = false;
|
|
11
|
-
|
|
12
|
-
function onScroll(e) {
|
|
13
|
-
const el = e.target;
|
|
14
|
-
_userScrolledUp = (el.scrollHeight - el.scrollTop - el.clientHeight) > 80;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function scrollToBottom(force) {
|
|
18
|
-
if (_userScrolledUp && !force) return;
|
|
19
|
-
if (document.hidden) return;
|
|
20
|
-
if (_rafId) return;
|
|
21
|
-
_rafId = requestAnimationFrame(() => {
|
|
22
|
-
_rafId = null;
|
|
23
|
-
const el = document.querySelector(selector);
|
|
24
|
-
if (el) el.scrollTop = el.scrollHeight;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function cleanup() {
|
|
29
|
-
if (_rafId) { cancelAnimationFrame(_rafId); _rafId = null; }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return { onScroll, scrollToBottom, cleanup };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Create a debounced highlight.js scheduler.
|
|
37
|
-
* @returns {{ scheduleHighlight, cleanup }}
|
|
38
|
-
*/
|
|
39
|
-
export function createHighlightScheduler() {
|
|
40
|
-
let _hlTimer = null;
|
|
41
|
-
|
|
42
|
-
function scheduleHighlight() {
|
|
43
|
-
if (_hlTimer) return;
|
|
44
|
-
_hlTimer = setTimeout(() => {
|
|
45
|
-
_hlTimer = null;
|
|
46
|
-
if (typeof hljs !== 'undefined') {
|
|
47
|
-
const root = document.querySelector('.message-list') || document;
|
|
48
|
-
root.querySelectorAll('pre code:not([data-highlighted])').forEach(block => {
|
|
49
|
-
hljs.highlightElement(block);
|
|
50
|
-
block.dataset.highlighted = 'true';
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}, 300);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function cleanup() {
|
|
57
|
-
if (_hlTimer) { clearTimeout(_hlTimer); _hlTimer = null; }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return { scheduleHighlight, cleanup };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Format a token count for display (e.g. 1500 → "1.5k").
|
|
65
|
-
* @param {number} n
|
|
66
|
-
* @returns {string}
|
|
67
|
-
*/
|
|
68
|
-
export function formatTokens(n) {
|
|
69
|
-
if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
|
|
70
|
-
return String(n);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function formatDurationShort(ms) {
|
|
74
|
-
if (!ms && ms !== 0) return '';
|
|
75
|
-
const totalSecs = Math.floor(ms / 1000);
|
|
76
|
-
if (totalSecs < 60) return (ms / 1000).toFixed(1) + 's';
|
|
77
|
-
const m = Math.floor(totalSecs / 60);
|
|
78
|
-
const s = totalSecs % 60;
|
|
79
|
-
if (m < 60) return m + 'm ' + String(s).padStart(2, '0') + 's';
|
|
80
|
-
const h = Math.floor(m / 60);
|
|
81
|
-
const rm = m % 60;
|
|
82
|
-
return h + 'h ' + String(rm).padStart(2, '0') + 'm';
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Format a usage stats object into a human-readable summary string.
|
|
87
|
-
* @param {object|null} u - Usage stats from turn_completed
|
|
88
|
-
* @returns {string}
|
|
89
|
-
*/
|
|
90
|
-
export function formatUsage(u, t) {
|
|
91
|
-
if (!u) return '';
|
|
92
|
-
const pct = u.contextWindow ? Math.round(u.inputTokens / u.contextWindow * 100) : 0;
|
|
93
|
-
const ctx = formatTokens(u.inputTokens) + ' / ' + formatTokens(u.contextWindow) + ' (' + pct + '%)';
|
|
94
|
-
const cost = '$' + u.totalCost.toFixed(2);
|
|
95
|
-
const model = u.model.replace(/^claude-/, '').replace(/-\d{8}$/, '').replace(/-1m$/, '');
|
|
96
|
-
const dur = formatDurationShort(u.durationMs);
|
|
97
|
-
const contextLabel = t ? t('usage.context') : 'Context';
|
|
98
|
-
const costLabel = t ? t('usage.cost') : 'Cost';
|
|
99
|
-
return contextLabel + ' ' + ctx + ' \u00b7 ' + costLabel + ' ' + cost + ' \u00b7 ' + model + ' \u00b7 ' + dur;
|
|
100
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// ── AskUserQuestion interaction ───────────────────────────────────────────────
|
|
2
|
-
|
|
3
|
-
export function selectQuestionOption(msg, qIndex, optLabel) {
|
|
4
|
-
if (msg.answered) return;
|
|
5
|
-
const q = msg.questions[qIndex];
|
|
6
|
-
if (!q) return;
|
|
7
|
-
if (q.multiSelect) {
|
|
8
|
-
const sel = msg.selectedAnswers[qIndex] || [];
|
|
9
|
-
const idx = sel.indexOf(optLabel);
|
|
10
|
-
if (idx >= 0) sel.splice(idx, 1);
|
|
11
|
-
else sel.push(optLabel);
|
|
12
|
-
msg.selectedAnswers[qIndex] = [...sel];
|
|
13
|
-
} else {
|
|
14
|
-
msg.selectedAnswers[qIndex] = optLabel;
|
|
15
|
-
msg.customTexts[qIndex] = '';
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function submitQuestionAnswer(msg, wsSend) {
|
|
20
|
-
if (msg.answered) return;
|
|
21
|
-
const answers = {};
|
|
22
|
-
for (let i = 0; i < msg.questions.length; i++) {
|
|
23
|
-
const q = msg.questions[i];
|
|
24
|
-
const key = q.question || String(i);
|
|
25
|
-
const custom = (msg.customTexts[i] || '').trim();
|
|
26
|
-
if (custom) {
|
|
27
|
-
answers[key] = custom;
|
|
28
|
-
} else {
|
|
29
|
-
const sel = msg.selectedAnswers[i];
|
|
30
|
-
if (Array.isArray(sel) && sel.length > 0) {
|
|
31
|
-
answers[key] = sel.join(', ');
|
|
32
|
-
} else if (sel != null) {
|
|
33
|
-
answers[key] = sel;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
msg.answered = true;
|
|
38
|
-
wsSend({ type: 'ask_user_answer', requestId: msg.requestId, answers });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function hasQuestionAnswer(msg) {
|
|
42
|
-
for (let i = 0; i < msg.questions.length; i++) {
|
|
43
|
-
const sel = msg.selectedAnswers[i];
|
|
44
|
-
const custom = (msg.customTexts[i] || '').trim();
|
|
45
|
-
if (custom || (Array.isArray(sel) ? sel.length > 0 : sel != null)) return true;
|
|
46
|
-
}
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function getQuestionResponseSummary(msg) {
|
|
51
|
-
const parts = [];
|
|
52
|
-
for (let i = 0; i < msg.questions.length; i++) {
|
|
53
|
-
const custom = (msg.customTexts[i] || '').trim();
|
|
54
|
-
if (custom) {
|
|
55
|
-
parts.push(custom);
|
|
56
|
-
} else {
|
|
57
|
-
const sel = msg.selectedAnswers[i];
|
|
58
|
-
if (Array.isArray(sel)) parts.push(sel.join(', '));
|
|
59
|
-
else if (sel) parts.push(sel);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return parts.join(' | ');
|
|
63
|
-
}
|