create-walle 0.9.19 → 0.9.21
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 +2 -2
- package/package.json +1 -1
- package/template/claude-task-manager/db.js +131 -0
- package/template/claude-task-manager/docs/microsoft-dev-tunnel-phone-access-design.md +58 -50
- package/template/claude-task-manager/docs/phone-access-design.md +23 -7
- package/template/claude-task-manager/docs/walle-session-model-preferences.md +119 -0
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +32 -48
- package/template/claude-task-manager/lib/remote-relay-protocol.js +5 -0
- package/template/claude-task-manager/lib/walle-external-actions.js +20 -3
- package/template/claude-task-manager/public/index.html +25 -0
- package/template/claude-task-manager/public/js/setup.js +16 -12
- package/template/claude-task-manager/public/js/walle-session.js +31 -3
- package/template/claude-task-manager/public/js/walle.js +93 -23
- package/template/claude-task-manager/public/m/app.css +417 -21
- package/template/claude-task-manager/public/m/app.js +831 -44
- package/template/claude-task-manager/public/m/claim.html +1 -1
- package/template/claude-task-manager/public/m/index.html +41 -7
- package/template/claude-task-manager/public/m/sw.js +1 -1
- package/template/claude-task-manager/server.js +377 -30
- package/template/claude-task-manager/workers/state-detectors/codex.js +18 -3
- package/template/package.json +1 -1
- package/template/wall-e/chat.js +32 -2
- package/template/wall-e/coding/stream-processor.js +36 -0
- package/template/wall-e/coding-orchestrator.js +45 -0
- package/template/wall-e/deploy.sh +1 -1
- package/template/wall-e/docs/external-action-controller.md +60 -2
- package/template/wall-e/external-action-controller.js +23 -1
- package/template/wall-e/external-action-gateway.js +163 -0
- package/template/wall-e/fly.toml +1 -0
- package/template/wall-e/tools/local-tools.js +122 -4
- package/template/website/index.html +2 -2
|
@@ -25,12 +25,14 @@
|
|
|
25
25
|
remoteOutbox: [],
|
|
26
26
|
remoteOutboxTimer: null,
|
|
27
27
|
remoteOutboxDraining: false,
|
|
28
|
+
expandedTimelineTurnsBySession: new Map(),
|
|
28
29
|
detail: {
|
|
29
30
|
sessionId: null,
|
|
30
31
|
messages: [],
|
|
31
32
|
messageIndexByKey: new Map(),
|
|
32
33
|
liveTail: null,
|
|
33
34
|
walleActivity: null,
|
|
35
|
+
expandedMessageKeys: new Set(),
|
|
34
36
|
viewMode: 'conversation',
|
|
35
37
|
refreshTimer: null,
|
|
36
38
|
refreshInFlight: null,
|
|
@@ -85,6 +87,16 @@
|
|
|
85
87
|
touchStartX: null,
|
|
86
88
|
touchStartY: null,
|
|
87
89
|
},
|
|
90
|
+
modelPicker: {
|
|
91
|
+
open: false,
|
|
92
|
+
sessionId: '',
|
|
93
|
+
inputId: '',
|
|
94
|
+
loading: false,
|
|
95
|
+
error: '',
|
|
96
|
+
models: [],
|
|
97
|
+
loaded: false,
|
|
98
|
+
requestSeq: 0,
|
|
99
|
+
},
|
|
88
100
|
push: {
|
|
89
101
|
loaded: false,
|
|
90
102
|
supported: false,
|
|
@@ -113,7 +125,7 @@
|
|
|
113
125
|
};
|
|
114
126
|
|
|
115
127
|
const $ = (id) => document.getElementById(id);
|
|
116
|
-
const MOBILE_ASSET_VERSION = '20260519-
|
|
128
|
+
const MOBILE_ASSET_VERSION = '20260519-walle-phone-model-picker';
|
|
117
129
|
const THEME_STORAGE_KEY = 'ctm.theme.mode';
|
|
118
130
|
const MOBILE_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
119
131
|
const SEND_LONG_PRESS_MS = 520;
|
|
@@ -162,6 +174,7 @@
|
|
|
162
174
|
attachments: 'walle-composer-attachments',
|
|
163
175
|
clear: 'walle-clear',
|
|
164
176
|
send: 'walle-send',
|
|
177
|
+
model: 'walle-model',
|
|
165
178
|
picker: 'walle-skill-picker',
|
|
166
179
|
}
|
|
167
180
|
: {
|
|
@@ -171,6 +184,7 @@
|
|
|
171
184
|
attachments: 'composer-attachments',
|
|
172
185
|
clear: 'detail-clear',
|
|
173
186
|
send: 'detail-send',
|
|
187
|
+
model: 'detail-model',
|
|
174
188
|
picker: 'mobile-skill-picker',
|
|
175
189
|
};
|
|
176
190
|
return $(ids[kind]);
|
|
@@ -219,23 +233,60 @@
|
|
|
219
233
|
return !!(input && input.isContentEditable);
|
|
220
234
|
}
|
|
221
235
|
|
|
222
|
-
function
|
|
236
|
+
function composerCursorToken(text, cursor) {
|
|
237
|
+
const before = String(text || '').slice(0, Math.max(0, cursor || 0));
|
|
238
|
+
const match = before.match(/(?:^|\s)(\S*)$/);
|
|
239
|
+
return match ? match[1] : '';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function composerCurrentLine(text, cursor) {
|
|
243
|
+
const before = String(text || '').slice(0, Math.max(0, cursor || 0));
|
|
244
|
+
return before.slice(before.lastIndexOf('\n') + 1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function composerShouldUseLiteralTextAssist(input) {
|
|
248
|
+
if (!input) return false;
|
|
249
|
+
const text = getComposerText(input);
|
|
250
|
+
const selection = getComposerSelection(input);
|
|
251
|
+
if (selection.start !== selection.end) return false;
|
|
252
|
+
const triggerInfo = findSkillTriggerInText(text, selection.start, selection.end);
|
|
253
|
+
if (triggerInfo) return true;
|
|
254
|
+
const token = composerCursorToken(text, selection.start);
|
|
255
|
+
const line = composerCurrentLine(text, selection.start);
|
|
256
|
+
if (/^[$/][^\s]*$/.test(token)) return true;
|
|
257
|
+
if (/^@[\w./-]*$/.test(token)) return true;
|
|
258
|
+
if (/^--?[\w-]*$/.test(token)) return true;
|
|
259
|
+
if (/^(https?:\/\/|[~./][\w./-]*|\w:[\\/])/.test(token)) return true;
|
|
260
|
+
if (/^[\w.-]+\.[A-Za-z0-9_-]{1,8}$/.test(token)) return true;
|
|
261
|
+
if (/`[^`]*$/.test(line) || /^\s*```/.test(line)) return true;
|
|
262
|
+
if (/^\s*(git|npm|npx|pnpm|yarn|node|uv|python3?|pip|cargo|go|docker|kubectl|ssh|scp|rsync|claude|codex|rg|grep|sed|awk|curl)\b/.test(line)) return true;
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function applyComposerTextAssistMode(input = $('detail-input')) {
|
|
223
267
|
const form = composerElement('form', input);
|
|
224
268
|
if (form) {
|
|
225
269
|
form.setAttribute('autocomplete', 'off');
|
|
226
|
-
form.setAttribute('autocorrect', '
|
|
270
|
+
form.setAttribute('autocorrect', 'on');
|
|
227
271
|
form.setAttribute('autocapitalize', 'sentences');
|
|
228
|
-
form.setAttribute('spellcheck', '
|
|
229
|
-
form.spellcheck =
|
|
272
|
+
form.setAttribute('spellcheck', 'true');
|
|
273
|
+
form.spellcheck = true;
|
|
230
274
|
}
|
|
231
275
|
if (!input) return;
|
|
276
|
+
const literal = composerShouldUseLiteralTextAssist(input);
|
|
232
277
|
input.setAttribute('autocomplete', 'off');
|
|
233
|
-
input.setAttribute('
|
|
234
|
-
input.setAttribute('autocapitalize', 'sentences');
|
|
278
|
+
input.setAttribute('inputmode', 'text');
|
|
235
279
|
input.setAttribute('enterkeyhint', 'send');
|
|
236
280
|
input.setAttribute('data-form-type', 'other');
|
|
237
|
-
input.setAttribute('
|
|
238
|
-
input.
|
|
281
|
+
input.setAttribute('data-text-assist', literal ? 'literal' : 'natural');
|
|
282
|
+
input.setAttribute('autocorrect', literal ? 'off' : 'on');
|
|
283
|
+
input.setAttribute('autocapitalize', literal ? 'none' : 'sentences');
|
|
284
|
+
input.setAttribute('spellcheck', literal ? 'false' : 'true');
|
|
285
|
+
input.spellcheck = !literal;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function configureComposerInput(input = $('detail-input')) {
|
|
289
|
+
applyComposerTextAssistMode(input);
|
|
239
290
|
}
|
|
240
291
|
|
|
241
292
|
function composerEditorPlainText(input) {
|
|
@@ -335,6 +386,7 @@
|
|
|
335
386
|
const cursor = Number.isFinite(opts.cursor) ? opts.cursor : value.length;
|
|
336
387
|
setComposerSelection(input, cursor, cursor);
|
|
337
388
|
}
|
|
389
|
+
applyComposerTextAssistMode(input);
|
|
338
390
|
}
|
|
339
391
|
|
|
340
392
|
function normalizeComposerEditor(input = $('detail-input')) {
|
|
@@ -367,6 +419,16 @@
|
|
|
367
419
|
dispatchComposerInput(input, 'insertText', insertion);
|
|
368
420
|
}
|
|
369
421
|
|
|
422
|
+
function updateMobileViewportInset() {
|
|
423
|
+
const vv = window.visualViewport;
|
|
424
|
+
let offset = 0;
|
|
425
|
+
if (vv && Number.isFinite(vv.height)) {
|
|
426
|
+
const layoutHeight = window.innerHeight || document.documentElement.clientHeight || vv.height;
|
|
427
|
+
offset = Math.max(0, Math.round(layoutHeight - vv.height - vv.offsetTop));
|
|
428
|
+
}
|
|
429
|
+
document.documentElement.style.setProperty('--mobile-keyboard-offset', `${offset}px`);
|
|
430
|
+
}
|
|
431
|
+
|
|
370
432
|
function lockDetailBackgroundScroll() {
|
|
371
433
|
if (state.detailScrollLock.locked) return;
|
|
372
434
|
const scrollY = window.scrollY || document.documentElement.scrollTop || 0;
|
|
@@ -550,6 +612,40 @@
|
|
|
550
612
|
return AI_PROVIDER_LABELS[key] || (key ? key.replace(/(^|-)([a-z])/g, (_, sep, ch) => (sep ? ' ' : '') + ch.toUpperCase()) : '');
|
|
551
613
|
}
|
|
552
614
|
|
|
615
|
+
function modelDisplayLabel(model) {
|
|
616
|
+
return String(model || '')
|
|
617
|
+
.replace(/@\d{6,8}\b/g, '')
|
|
618
|
+
.replace(/^[a-z][a-z0-9_-]+[:/]/i, '')
|
|
619
|
+
.replace(/\s+/g, ' ')
|
|
620
|
+
.trim();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function modelButtonLabel(card) {
|
|
624
|
+
const provider = providerKey(card?.walle_model_provider || card?.model_provider || card?.modelProvider || card?.providerType || '');
|
|
625
|
+
const model = modelDisplayLabel(card?.walle_model_id || card?.model_id || card?.model || '');
|
|
626
|
+
const providerText = providerLabel(provider);
|
|
627
|
+
if (providerText && model) return `${providerText} · ${model}`;
|
|
628
|
+
if (model) return model;
|
|
629
|
+
return 'Auto model';
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function modelButtonShortLabel(card) {
|
|
633
|
+
const provider = providerKey(card?.walle_model_provider || card?.model_provider || card?.modelProvider || card?.providerType || '');
|
|
634
|
+
if (provider === 'anthropic') return 'Claude';
|
|
635
|
+
if (provider === 'openai') return 'GPT';
|
|
636
|
+
if (provider === 'google') return 'Gemini';
|
|
637
|
+
if (provider === 'deepseek') return 'DeepSeek';
|
|
638
|
+
if (provider === 'moonshot') return 'Kimi';
|
|
639
|
+
if (provider === 'ollama') return 'Ollama';
|
|
640
|
+
if (provider === 'mlx') return 'MLX';
|
|
641
|
+
const model = modelDisplayLabel(card?.walle_model_id || card?.model_id || card?.model || '');
|
|
642
|
+
if (/claude|opus|sonnet|haiku/i.test(model)) return 'Claude';
|
|
643
|
+
if (/gpt|o[1-9]|codex/i.test(model)) return 'GPT';
|
|
644
|
+
if (/gemini/i.test(model)) return 'Gemini';
|
|
645
|
+
if (/deepseek/i.test(model)) return 'DeepSeek';
|
|
646
|
+
return 'Model';
|
|
647
|
+
}
|
|
648
|
+
|
|
553
649
|
function providerBadge(card) {
|
|
554
650
|
const key = sessionAiProviderKey(card);
|
|
555
651
|
if (!key) return '';
|
|
@@ -1194,7 +1290,7 @@
|
|
|
1194
1290
|
<section id="walle-messages" class="detail-messages walle-chat-messages" aria-live="polite">
|
|
1195
1291
|
<div class="empty-state">Loading Wall-E conversation...</div>
|
|
1196
1292
|
</section>
|
|
1197
|
-
<form id="walle-chat-composer" class="composer walle-chat-composer" autocomplete="off" autocorrect="
|
|
1293
|
+
<form id="walle-chat-composer" class="composer walle-chat-composer" autocomplete="off" autocorrect="on" autocapitalize="sentences" spellcheck="true">
|
|
1198
1294
|
<div class="composer-field">
|
|
1199
1295
|
<span id="walle-input-label" class="sr-only">Message Wall-E</span>
|
|
1200
1296
|
<div id="walle-composer-attachments" class="composer-attachments" aria-label="Attachments" hidden></div>
|
|
@@ -1202,11 +1298,15 @@
|
|
|
1202
1298
|
<button id="walle-attach-image" class="walle-tool-btn" type="button" aria-label="Attach image" title="Attach image" data-walle-attach="image"><span aria-hidden="true">▧</span></button>
|
|
1203
1299
|
<button id="walle-attach-file" class="walle-tool-btn" type="button" aria-label="Attach file" title="Attach file" data-walle-attach="file"><span aria-hidden="true">▤</span></button>
|
|
1204
1300
|
</div>
|
|
1205
|
-
<div id="walle-input" class="composer-editor" contenteditable="true" role="textbox" aria-multiline="true" aria-labelledby="walle-input-label" aria-describedby="walle-composer-status" data-placeholder="Message Wall-E" data-skill-agent="walle" data-skill-mode="walle-mobile-composer" inputmode="text" enterkeyhint="send" autocomplete="off" autocorrect="
|
|
1301
|
+
<div id="walle-input" class="composer-editor" contenteditable="true" role="textbox" aria-multiline="true" aria-labelledby="walle-input-label" aria-describedby="walle-composer-status" data-placeholder="Message Wall-E" data-skill-agent="walle" data-skill-mode="walle-mobile-composer" inputmode="text" enterkeyhint="send" autocomplete="off" autocorrect="on" autocapitalize="sentences" spellcheck="true" data-form-type="other" data-text-assist="natural"></div>
|
|
1206
1302
|
<button id="walle-clear" class="composer-clear" type="button" aria-label="Clear Wall-E message" title="Clear message" hidden><span aria-hidden="true">×</span></button>
|
|
1207
1303
|
<div id="walle-skill-picker" class="mobile-skill-picker" role="listbox" aria-label="Skill suggestions" hidden></div>
|
|
1208
1304
|
<div id="walle-composer-status" class="composer-status" aria-live="polite"></div>
|
|
1209
1305
|
</div>
|
|
1306
|
+
<button id="walle-model" class="model-picker-btn" type="button" aria-label="Choose model for this Wall-E session" aria-haspopup="dialog" aria-expanded="false" hidden>
|
|
1307
|
+
<span class="model-picker-btn-kicker">Model</span>
|
|
1308
|
+
<span id="walle-model-label" class="model-picker-btn-label">Auto</span>
|
|
1309
|
+
</button>
|
|
1210
1310
|
<button id="walle-send" class="send-btn" type="submit" aria-label="Send to Wall-E"><span aria-hidden="true">➤</span></button>
|
|
1211
1311
|
</form>
|
|
1212
1312
|
</div>
|
|
@@ -1541,7 +1641,8 @@
|
|
|
1541
1641
|
if (!card || !card.id) return null;
|
|
1542
1642
|
const copy = {};
|
|
1543
1643
|
for (const key of [
|
|
1544
|
-
'id', 'sessionId', 'title', '
|
|
1644
|
+
'id', 'sessionId', 'title', 'aiTitle', 'displayTitle', 'userRenamed',
|
|
1645
|
+
'agent', 'provider', 'model', 'model_id',
|
|
1545
1646
|
'modelProvider', 'model_provider', 'providerType', 'llmProvider',
|
|
1546
1647
|
'walle_model_id', 'walle_model_provider', 'runtime_model_id', 'runtime_model_provider',
|
|
1547
1648
|
'project', 'cwd', 'projectPath', 'branch', 'lane', 'status',
|
|
@@ -1631,12 +1732,29 @@
|
|
|
1631
1732
|
return false;
|
|
1632
1733
|
}
|
|
1633
1734
|
|
|
1735
|
+
const mergeLocalUserRename = (incoming, existing = null) => {
|
|
1736
|
+
if (!incoming || typeof incoming !== 'object') return incoming;
|
|
1737
|
+
const local = existing || findSession(incoming.id || incoming.sessionId);
|
|
1738
|
+
if (!local?.userRenamed) return incoming;
|
|
1739
|
+
const localTitle = detailTitleText(local);
|
|
1740
|
+
if (!localTitle) return incoming;
|
|
1741
|
+
const incomingTitle = detailTitleText(incoming);
|
|
1742
|
+
if (incoming.userRenamed && incomingTitle && incomingTitle !== localTitle) return incoming;
|
|
1743
|
+
return {
|
|
1744
|
+
...incoming,
|
|
1745
|
+
title: localTitle,
|
|
1746
|
+
aiTitle: localTitle,
|
|
1747
|
+
displayTitle: localTitle,
|
|
1748
|
+
userRenamed: true,
|
|
1749
|
+
};
|
|
1750
|
+
};
|
|
1751
|
+
|
|
1634
1752
|
if (standup.incremental && Array.isArray(standup.sessionOrder) && Array.isArray(standup.sessions)) {
|
|
1635
1753
|
const byId = new Map(state.sessions.map((session) => [String(session.id || ''), session]).filter(([id]) => id));
|
|
1636
1754
|
for (const id of standup.removedSessionIds || []) byId.delete(String(id));
|
|
1637
1755
|
for (const session of standup.sessions) {
|
|
1638
1756
|
const id = String(session?.id || '');
|
|
1639
|
-
if (id) byId.set(id, session);
|
|
1757
|
+
if (id) byId.set(id, mergeLocalUserRename(session, byId.get(id)));
|
|
1640
1758
|
}
|
|
1641
1759
|
state.sessions = standup.sessionOrder.map((id) => byId.get(String(id))).filter(Boolean);
|
|
1642
1760
|
state.lanes = Array.isArray(standup.lanes) ? standup.lanes : state.lanes;
|
|
@@ -1644,7 +1762,9 @@
|
|
|
1644
1762
|
return true;
|
|
1645
1763
|
}
|
|
1646
1764
|
|
|
1647
|
-
state.sessions = Array.isArray(standup.sessions)
|
|
1765
|
+
state.sessions = Array.isArray(standup.sessions)
|
|
1766
|
+
? standup.sessions.map((session) => mergeLocalUserRename(session))
|
|
1767
|
+
: [];
|
|
1648
1768
|
state.lanes = Array.isArray(standup.lanes) ? standup.lanes : [];
|
|
1649
1769
|
persistSessionSnapshot();
|
|
1650
1770
|
return true;
|
|
@@ -1833,7 +1953,6 @@
|
|
|
1833
1953
|
$('detail-status').className = `state-chip ${card.lane || card.status || 'idle'}`;
|
|
1834
1954
|
renderDetailAlert(card);
|
|
1835
1955
|
renderDetailControls(card);
|
|
1836
|
-
if (!isTerminalControlSession(card)) hideSendMenu();
|
|
1837
1956
|
updateComposerState();
|
|
1838
1957
|
}
|
|
1839
1958
|
|
|
@@ -2474,7 +2593,23 @@
|
|
|
2474
2593
|
const id = String(raw.id || remoteMessageId()).slice(0, 160);
|
|
2475
2594
|
const now = Date.now();
|
|
2476
2595
|
const normalizedBody = { session_id: sessionId, text };
|
|
2477
|
-
if (type === 'wall_e.send_message'
|
|
2596
|
+
if (type === 'wall_e.send_message') {
|
|
2597
|
+
if (cleanAttachments.length) normalizedBody.attachments = cleanAttachments;
|
|
2598
|
+
const modelId = String(body.model_id || body.model || raw.model_id || raw.model || '').trim();
|
|
2599
|
+
const modelProvider = providerKey(body.model_provider || body.provider || raw.model_provider || raw.provider || '');
|
|
2600
|
+
const registryId = String(body.model_registry_id || body.modelRegistryId || raw.model_registry_id || raw.modelRegistryId || '').trim();
|
|
2601
|
+
const providerId = String(body.model_provider_id || body.modelProviderId || body.provider_id || raw.model_provider_id || raw.modelProviderId || raw.provider_id || '').trim();
|
|
2602
|
+
if (modelId) {
|
|
2603
|
+
normalizedBody.model_id = modelId;
|
|
2604
|
+
normalizedBody.model = modelId;
|
|
2605
|
+
normalizedBody.model_provider = modelProvider;
|
|
2606
|
+
normalizedBody.provider = modelProvider;
|
|
2607
|
+
normalizedBody.model_registry_id = registryId;
|
|
2608
|
+
normalizedBody.model_provider_id = providerId;
|
|
2609
|
+
normalizedBody.modelPinned = true;
|
|
2610
|
+
normalizedBody.allowProviderFallback = false;
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2478
2613
|
return {
|
|
2479
2614
|
id,
|
|
2480
2615
|
type,
|
|
@@ -2719,6 +2854,7 @@
|
|
|
2719
2854
|
updateRemoteOutboxStatus();
|
|
2720
2855
|
$('detail-view').classList.add('active');
|
|
2721
2856
|
$('detail-view').setAttribute('aria-hidden', 'false');
|
|
2857
|
+
updateComposerState();
|
|
2722
2858
|
$('detail-messages').innerHTML = card._placeholder
|
|
2723
2859
|
? '<div class="empty-state">Loading session context. You can still type and send a prompt.</div>'
|
|
2724
2860
|
: '<div class="empty-state">Connecting to live stream...</div>';
|
|
@@ -2736,6 +2872,7 @@
|
|
|
2736
2872
|
setComposerStatus('', '');
|
|
2737
2873
|
hideMobileSkillPicker();
|
|
2738
2874
|
hideSendMenu();
|
|
2875
|
+
closeModelPicker();
|
|
2739
2876
|
clearComposerAttachments();
|
|
2740
2877
|
state.composerSending = false;
|
|
2741
2878
|
state.controlSending = false;
|
|
@@ -2770,6 +2907,13 @@
|
|
|
2770
2907
|
return /walle|wall-e/i.test(kind);
|
|
2771
2908
|
}
|
|
2772
2909
|
|
|
2910
|
+
function isWalleCodingSession(card) {
|
|
2911
|
+
if (!card || !isWalleSession(card)) return false;
|
|
2912
|
+
const caps = card.agentCapabilities || card.capabilities || {};
|
|
2913
|
+
if (caps.structuredTranscript === false && caps.terminalInput === false && caps.review === false) return false;
|
|
2914
|
+
return true;
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2773
2917
|
function terminalControlCapability(card) {
|
|
2774
2918
|
const caps = card?.capabilities || card?.agentCapabilities || {};
|
|
2775
2919
|
if (caps.terminalControl === true || caps.terminalInput === true || caps.canSendEscape === true) return true;
|
|
@@ -2860,6 +3004,7 @@
|
|
|
2860
3004
|
messageIndexByKey: new Map(),
|
|
2861
3005
|
liveTail: null,
|
|
2862
3006
|
walleActivity: null,
|
|
3007
|
+
expandedMessageKeys: new Set(),
|
|
2863
3008
|
viewMode: 'conversation',
|
|
2864
3009
|
refreshTimer: null,
|
|
2865
3010
|
refreshInFlight: null,
|
|
@@ -2867,6 +3012,31 @@
|
|
|
2867
3012
|
updateDetailTimelineModeButton();
|
|
2868
3013
|
}
|
|
2869
3014
|
|
|
3015
|
+
function activeTimelineExpansionSessionId() {
|
|
3016
|
+
return String(state.detail?.sessionId || state.activeSession?.id || '');
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
function timelineTurnExpansionSet(sessionId = activeTimelineExpansionSessionId()) {
|
|
3020
|
+
if (!sessionId) return null;
|
|
3021
|
+
if (!state.expandedTimelineTurnsBySession.has(sessionId)) {
|
|
3022
|
+
state.expandedTimelineTurnsBySession.set(sessionId, new Set());
|
|
3023
|
+
}
|
|
3024
|
+
return state.expandedTimelineTurnsBySession.get(sessionId);
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
function isTimelineTurnExpanded(key) {
|
|
3028
|
+
if (!key) return false;
|
|
3029
|
+
return !!timelineTurnExpansionSet()?.has(key);
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
function rememberTimelineTurnExpanded(key, expanded) {
|
|
3033
|
+
if (!key) return;
|
|
3034
|
+
const set = timelineTurnExpansionSet();
|
|
3035
|
+
if (!set) return;
|
|
3036
|
+
if (expanded) set.add(key);
|
|
3037
|
+
else set.delete(key);
|
|
3038
|
+
}
|
|
3039
|
+
|
|
2870
3040
|
function renderMessages(messages, opts = {}) {
|
|
2871
3041
|
const pendingLiveMessages = state.detail.messages.slice();
|
|
2872
3042
|
state.detail.messages = [];
|
|
@@ -2898,6 +3068,22 @@
|
|
|
2898
3068
|
}
|
|
2899
3069
|
|
|
2900
3070
|
function handleWalleSessionMessage(msg) {
|
|
3071
|
+
if (msg.type === 'walle-model') {
|
|
3072
|
+
const provider = providerKey(msg.model_provider || msg.provider || '');
|
|
3073
|
+
const item = msg.model_id
|
|
3074
|
+
? {
|
|
3075
|
+
id: String(msg.model_registry_id || msg.modelRegistryId || '').trim(),
|
|
3076
|
+
modelId: String(msg.model_id || '').trim(),
|
|
3077
|
+
label: modelDisplayLabel(msg.model_id),
|
|
3078
|
+
provider,
|
|
3079
|
+
providerId: String(msg.model_provider_id || msg.modelProviderId || '').trim(),
|
|
3080
|
+
providerLabel: providerLabel(provider) || 'Provider',
|
|
3081
|
+
}
|
|
3082
|
+
: null;
|
|
3083
|
+
applyWalleModelLocally(msg.id || msg.sessionId, item);
|
|
3084
|
+
renderModelPickerSheet();
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
2901
3087
|
if (msg.type === 'walle-history') {
|
|
2902
3088
|
state.detail.walleActivity = null;
|
|
2903
3089
|
renderMessages(Array.isArray(msg.messages) ? msg.messages : []);
|
|
@@ -3127,7 +3313,9 @@
|
|
|
3127
3313
|
}
|
|
3128
3314
|
|
|
3129
3315
|
const TERMINAL_PROMPT_PLACEHOLDERS = [
|
|
3316
|
+
// Codex starter suggestions look like prompt rows in PTY snapshots, but are not submitted user text.
|
|
3130
3317
|
/^explain this codebase$/i,
|
|
3318
|
+
/^find and fix a bug in @filename$/i,
|
|
3131
3319
|
];
|
|
3132
3320
|
|
|
3133
3321
|
function isTerminalPromptPlaceholder(text) {
|
|
@@ -3159,6 +3347,24 @@
|
|
|
3159
3347
|
return false;
|
|
3160
3348
|
}
|
|
3161
3349
|
|
|
3350
|
+
function stripTerminalEdgeDecorations(line) {
|
|
3351
|
+
return stripAnsi(String(line || ''))
|
|
3352
|
+
.replace(/\u00a0/g, ' ')
|
|
3353
|
+
.replace(/^[\s|│┃┆┊╎╏·•●◦▪*─━═╌╍┄┅⎯—_=~-]+/g, '')
|
|
3354
|
+
.replace(/[\s|│┃┆┊╎╏·•●◦▪*─━═╌╍┄┅⎯—_=~-]+$/g, '')
|
|
3355
|
+
.trim();
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
function isTerminalCompletionFooterLine(line) {
|
|
3359
|
+
const text = stripTerminalEdgeDecorations(line);
|
|
3360
|
+
if (!text) return false;
|
|
3361
|
+
return /^Worked for\s+\d+(?:\.\d+)?\s*(?:ms|msec|milliseconds?|s|sec|secs|seconds?|m|min|mins|minutes?|h|hr|hrs|hours?)(?:\s+\d+(?:\.\d+)?\s*(?:ms|msec|milliseconds?|s|sec|secs|seconds?|m|min|mins|minutes?|h|hr|hrs|hours?))*$/i.test(text);
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
function isTerminalNonContentLine(line) {
|
|
3365
|
+
return isTerminalDividerLine(line) || isTerminalCompletionFooterLine(line);
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3162
3368
|
function isTerminalStatusLine(line) {
|
|
3163
3369
|
const text = String(line || '').trim();
|
|
3164
3370
|
if (!text) return false;
|
|
@@ -3203,7 +3409,7 @@
|
|
|
3203
3409
|
}
|
|
3204
3410
|
|
|
3205
3411
|
function cleanLiveTerminalRows(rows) {
|
|
3206
|
-
return (rows || []).filter((row) => !
|
|
3412
|
+
return (rows || []).filter((row) => !isTerminalNonContentLine(row?.text));
|
|
3207
3413
|
}
|
|
3208
3414
|
|
|
3209
3415
|
function terminalRowsText(rows) {
|
|
@@ -3231,9 +3437,17 @@
|
|
|
3231
3437
|
if (!text) return true;
|
|
3232
3438
|
if (terminalPromptLineText(text) != null) return false;
|
|
3233
3439
|
if (isTerminalChromeLine(text) || isTerminalStatusLine(text) || isTerminalDividerLine(text)) return false;
|
|
3440
|
+
if (isTerminalAgentOutputLine(text)) return false;
|
|
3234
3441
|
return true;
|
|
3235
3442
|
}
|
|
3236
3443
|
|
|
3444
|
+
function isTerminalAgentOutputLine(line) {
|
|
3445
|
+
const text = String(line || '').trim();
|
|
3446
|
+
if (!text) return false;
|
|
3447
|
+
if (/^[⚠✓✔✗✘]\s*/.test(text)) return true;
|
|
3448
|
+
return /^(?:Error|Warning|Falling back|stream disconnected|Failed|Retrying)\b/i.test(text);
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3237
3451
|
function promptContinuationFallback(promptText, rows, promptIndex) {
|
|
3238
3452
|
const text = String(promptText || '').trim();
|
|
3239
3453
|
const nextRow = rows[promptIndex + 1];
|
|
@@ -3242,6 +3456,7 @@
|
|
|
3242
3456
|
if (!isPromptContinuationRow(nextRow)) return false;
|
|
3243
3457
|
const statusIndex = findBusyStatusBeforeNextPrompt(rows, promptIndex);
|
|
3244
3458
|
if (statusIndex > promptIndex + 1 && text.length >= 64) return true;
|
|
3459
|
+
if (/^\$[\w-]+\b/.test(text) && text.length >= 40) return true;
|
|
3245
3460
|
if (text.length >= 82) return true;
|
|
3246
3461
|
if (/[([{,;:]$/.test(text)) return true;
|
|
3247
3462
|
return /\b(?:a|an|and|as|at|by|for|from|in|into|of|on|or|the|to|with|without)$/i.test(text);
|
|
@@ -3341,6 +3556,101 @@
|
|
|
3341
3556
|
`;
|
|
3342
3557
|
}
|
|
3343
3558
|
|
|
3559
|
+
function renderLiveTerminalResponse(turn, tail, index, total) {
|
|
3560
|
+
const agent = state.activeSession?.agent || state.activeSession?.provider || 'Agent';
|
|
3561
|
+
const time = index === total - 1 ? messageTime(tail) : '';
|
|
3562
|
+
const body = window.MR?.formatMsgText
|
|
3563
|
+
? window.MR.formatMsgText(String(turn?.text || ''))
|
|
3564
|
+
: esc(turn?.text || '').replace(/\n/g, '<br>');
|
|
3565
|
+
return `
|
|
3566
|
+
<div class="review-msg assistant live-terminal-response">
|
|
3567
|
+
<div class="msg-header">
|
|
3568
|
+
<span class="msg-role">${esc(agent)} <span class="live-pill">Live</span></span>
|
|
3569
|
+
${time ? `<span class="msg-time">${esc(time)}</span>` : ''}
|
|
3570
|
+
</div>
|
|
3571
|
+
<div class="msg-text live-tail-text">${body}</div>
|
|
3572
|
+
</div>
|
|
3573
|
+
`;
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
function renderLiveTerminalPromptTurn(promptTurn, responseTurns, tail, index) {
|
|
3577
|
+
const key = mobileLiveTurnKey(tail, promptTurn?.text || '', index);
|
|
3578
|
+
const expanded = isTimelineTurnExpanded(key);
|
|
3579
|
+
const promptBody = esc(promptTurn?.text || '').replace(/\n/g, '<br>');
|
|
3580
|
+
const responseHtml = responseTurns.length
|
|
3581
|
+
? responseTurns.map((turn, responseIndex) => renderLiveTerminalResponse(turn, tail, responseIndex, responseTurns.length)).join('')
|
|
3582
|
+
: '<div class="prompt-turn-empty">Live response is still streaming.</div>';
|
|
3583
|
+
return `
|
|
3584
|
+
<section class="prompt-turn live-prompt-turn live-tail-row${expanded ? ' expanded' : ''}" data-mobile-turn-key="${esc(key)}" data-turn-id="${esc(key)}" data-live-tail="true" data-live-tail-kind="prompt">
|
|
3585
|
+
<div class="prompt-turn-header" role="button" tabindex="0" aria-expanded="${expanded ? 'true' : 'false'}">
|
|
3586
|
+
<span class="prompt-turn-chevron">▶</span>
|
|
3587
|
+
<div class="prompt-turn-head-main">
|
|
3588
|
+
<div class="review-msg user key-msg prompt-turn-prompt">
|
|
3589
|
+
<div class="msg-header">
|
|
3590
|
+
<span class="msg-role">You <span class="live-pill">Live</span></span>
|
|
3591
|
+
</div>
|
|
3592
|
+
<div class="msg-text live-terminal-prompt-text">${promptBody}</div>
|
|
3593
|
+
</div>
|
|
3594
|
+
</div>
|
|
3595
|
+
<div class="prompt-turn-meta">
|
|
3596
|
+
<span class="prompt-turn-badge">live</span>
|
|
3597
|
+
<span class="prompt-turn-badge">${responseTurns.length || 'no'} repl${responseTurns.length === 1 ? 'y' : 'ies'}</span>
|
|
3598
|
+
</div>
|
|
3599
|
+
</div>
|
|
3600
|
+
<div class="prompt-turn-response">
|
|
3601
|
+
${responseHtml}
|
|
3602
|
+
</div>
|
|
3603
|
+
</section>
|
|
3604
|
+
`;
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
function renderLiveTerminalStandaloneTurn(turn, tail, index) {
|
|
3608
|
+
const key = mobileLiveTurnKey(tail, turn?.text || '', index);
|
|
3609
|
+
const expanded = isTimelineTurnExpanded(key);
|
|
3610
|
+
const agent = state.activeSession?.agent || state.activeSession?.provider || 'Agent';
|
|
3611
|
+
const preview = compactPreview(turn?.text || '');
|
|
3612
|
+
return `
|
|
3613
|
+
<section class="prompt-turn live-prompt-turn live-standalone-turn live-tail-row${expanded ? ' expanded' : ''}" data-mobile-turn-key="${esc(key)}" data-turn-id="${esc(key)}" data-live-tail="true" data-live-tail-kind="response">
|
|
3614
|
+
<div class="prompt-turn-header" role="button" tabindex="0" aria-expanded="${expanded ? 'true' : 'false'}">
|
|
3615
|
+
<span class="prompt-turn-chevron">▶</span>
|
|
3616
|
+
<div class="prompt-turn-head-main">
|
|
3617
|
+
<div class="prompt-turn-live-title">
|
|
3618
|
+
<span>Live terminal</span>
|
|
3619
|
+
<span class="terminal-tail-preview">${esc(preview)}</span>
|
|
3620
|
+
</div>
|
|
3621
|
+
</div>
|
|
3622
|
+
<div class="prompt-turn-meta">
|
|
3623
|
+
<span class="prompt-turn-badge">${esc(agent)}</span>
|
|
3624
|
+
<span class="prompt-turn-badge">live</span>
|
|
3625
|
+
</div>
|
|
3626
|
+
</div>
|
|
3627
|
+
<div class="prompt-turn-response">
|
|
3628
|
+
${renderLiveTerminalResponse(turn, tail, 0, 1)}
|
|
3629
|
+
</div>
|
|
3630
|
+
</section>
|
|
3631
|
+
`;
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
function renderLiveTerminalConversationTurns(turns, tail) {
|
|
3635
|
+
const rows = [];
|
|
3636
|
+
for (let index = 0; index < turns.length; index += 1) {
|
|
3637
|
+
const turn = turns[index];
|
|
3638
|
+
if (turn?.role !== 'user') {
|
|
3639
|
+
rows.push(renderLiveTerminalStandaloneTurn(turn, tail, index));
|
|
3640
|
+
continue;
|
|
3641
|
+
}
|
|
3642
|
+
const responses = [];
|
|
3643
|
+
let cursor = index + 1;
|
|
3644
|
+
while (cursor < turns.length && turns[cursor]?.role !== 'user') {
|
|
3645
|
+
responses.push(turns[cursor]);
|
|
3646
|
+
cursor += 1;
|
|
3647
|
+
}
|
|
3648
|
+
rows.push(renderLiveTerminalPromptTurn(turn, responses, tail, index));
|
|
3649
|
+
index = cursor - 1;
|
|
3650
|
+
}
|
|
3651
|
+
return rows.join('');
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3344
3654
|
function hasTimelineContent(box) {
|
|
3345
3655
|
return !!(box && box.querySelector('.prompt-turn, .review-msg, .thought-group, .message-row, .live-tail-row'));
|
|
3346
3656
|
}
|
|
@@ -3358,16 +3668,16 @@
|
|
|
3358
3668
|
const turns = mode === 'conversation' ? parseLiveTerminalTurns(cleanRows) : null;
|
|
3359
3669
|
if (turns) {
|
|
3360
3670
|
if (!turns.length) return '';
|
|
3361
|
-
return turns
|
|
3671
|
+
return renderLiveTerminalConversationTurns(turns, tail);
|
|
3362
3672
|
}
|
|
3363
|
-
const
|
|
3364
|
-
const openAttr = mode === 'normal' ||
|
|
3673
|
+
const key = mobileLiveTurnKey(tail, cleanText, 0);
|
|
3674
|
+
const openAttr = mode === 'normal' || isTimelineTurnExpanded(key) ? ' open' : '';
|
|
3365
3675
|
const preview = compactPreview(cleanText);
|
|
3366
3676
|
const formatted = window.MR?.formatMsgText
|
|
3367
3677
|
? window.MR.formatMsgText(cleanText)
|
|
3368
3678
|
: esc(cleanText).replace(/\n/g, '<br>');
|
|
3369
3679
|
return `
|
|
3370
|
-
<details class="terminal-tail-panel live-tail-row"${openAttr} data-live-tail="true">
|
|
3680
|
+
<details class="terminal-tail-panel live-tail-row"${openAttr} data-live-tail="true" data-mobile-turn-key="${esc(key)}">
|
|
3371
3681
|
<summary class="terminal-tail-summary">
|
|
3372
3682
|
<span class="terminal-tail-main">
|
|
3373
3683
|
<span class="terminal-tail-kicker">Live terminal</span>
|
|
@@ -3439,14 +3749,34 @@
|
|
|
3439
3749
|
};
|
|
3440
3750
|
}
|
|
3441
3751
|
|
|
3752
|
+
function mobilePromptTurnKey(turn, idx) {
|
|
3753
|
+
if (!turn || typeof turn !== 'object') return `turn:${idx}`;
|
|
3754
|
+
if (turn.type === 'setup') return 'turn:setup';
|
|
3755
|
+
const prompt = turn.prompt?.m || turn.prompt || null;
|
|
3756
|
+
const stable = prompt && timelineMessageKey(prompt);
|
|
3757
|
+
if (stable) return `turn:${stable}`;
|
|
3758
|
+
const text = normalizeCompareText(prompt?.text || '').slice(0, 180);
|
|
3759
|
+
return `turn:${idx}:${text}`;
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
function mobileLiveTurnKey(tail, text, index = 0) {
|
|
3763
|
+
const sessionId = activeTimelineExpansionSessionId();
|
|
3764
|
+
const normalized = normalizeCompareText(text).slice(0, 180);
|
|
3765
|
+
return `live:${sessionId}:${index}:${normalized || tail?.reason || 'terminal'}`;
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3442
3768
|
function renderMobileConversationTurns(messages) {
|
|
3443
3769
|
const normalized = (messages || []).map(normalizeMobileReviewMessage).filter(Boolean);
|
|
3444
3770
|
if (!normalized.length) return [];
|
|
3445
|
-
if (window.MR?.
|
|
3446
|
-
const
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3771
|
+
if (window.MR?.groupMessagesIntoTurns && window.MR?.renderReviewTurn) {
|
|
3772
|
+
const html = window.MR.groupMessagesIntoTurns(normalized)
|
|
3773
|
+
.map((turn, idx) => window.MR.renderReviewTurn(turn, idx, {
|
|
3774
|
+
expanded: () => isTimelineTurnExpanded(mobilePromptTurnKey(turn, idx)),
|
|
3775
|
+
}))
|
|
3776
|
+
.join('');
|
|
3777
|
+
if (String(html || '').trim()) return [html];
|
|
3778
|
+
} else if (window.MR?.renderReviewTurns) {
|
|
3779
|
+
const html = window.MR.renderReviewTurns(normalized, { expanded: false });
|
|
3450
3780
|
if (String(html || '').trim()) return [html];
|
|
3451
3781
|
}
|
|
3452
3782
|
return messages.map(renderMobileMessage).filter(Boolean);
|
|
@@ -3495,10 +3825,16 @@
|
|
|
3495
3825
|
box.querySelectorAll('.prompt-turn-header').forEach((header) => {
|
|
3496
3826
|
const turn = header.closest('.prompt-turn');
|
|
3497
3827
|
if (!turn) return;
|
|
3828
|
+
const setExpanded = (next) => {
|
|
3829
|
+
window.MR.setPromptTurnExpanded(turn, next);
|
|
3830
|
+
syncMobilePromptTurnAction(turn, next);
|
|
3831
|
+
rememberTimelineTurnExpanded(turn.dataset.mobileTurnKey || turn.dataset.turnId || '', next);
|
|
3832
|
+
};
|
|
3498
3833
|
const toggle = (ev) => {
|
|
3499
3834
|
if (ev?.target?.closest?.('a,button,input,textarea,select')) return;
|
|
3500
3835
|
if (window.MR?.shouldKeepPromptTextSelection?.(ev, header)) return;
|
|
3501
|
-
|
|
3836
|
+
const next = !turn.classList.contains('expanded');
|
|
3837
|
+
setExpanded(next);
|
|
3502
3838
|
};
|
|
3503
3839
|
header.addEventListener('click', toggle);
|
|
3504
3840
|
header.addEventListener('keydown', (ev) => {
|
|
@@ -3506,6 +3842,48 @@
|
|
|
3506
3842
|
ev.preventDefault();
|
|
3507
3843
|
toggle(ev);
|
|
3508
3844
|
});
|
|
3845
|
+
const action = turn.querySelector('[data-prompt-turn-action="toggle"]');
|
|
3846
|
+
if (action) {
|
|
3847
|
+
action.addEventListener('click', (ev) => {
|
|
3848
|
+
ev.preventDefault();
|
|
3849
|
+
ev.stopPropagation();
|
|
3850
|
+
setExpanded(!turn.classList.contains('expanded'));
|
|
3851
|
+
});
|
|
3852
|
+
}
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
function syncMobilePromptTurnAction(turn, expanded = turn?.classList?.contains('expanded')) {
|
|
3857
|
+
const button = turn?.querySelector?.('[data-prompt-turn-action="toggle"]');
|
|
3858
|
+
if (!button) return;
|
|
3859
|
+
const isExpanded = !!expanded;
|
|
3860
|
+
button.textContent = isExpanded ? 'Hide details' : 'Full prompt';
|
|
3861
|
+
button.setAttribute('aria-expanded', isExpanded ? 'true' : 'false');
|
|
3862
|
+
button.setAttribute('aria-label', isExpanded ? 'Hide full prompt details' : 'Show full prompt');
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
function ensureMobilePromptTurnAction(turn) {
|
|
3866
|
+
if (!turn || turn.classList?.contains('setup-turn')) return;
|
|
3867
|
+
const meta = turn.querySelector?.('.prompt-turn-meta');
|
|
3868
|
+
const prompt = turn.querySelector?.('.prompt-turn-prompt .msg-text');
|
|
3869
|
+
if (!meta || !prompt) return;
|
|
3870
|
+
let button = meta.querySelector('[data-prompt-turn-action="toggle"]');
|
|
3871
|
+
if (!button) {
|
|
3872
|
+
button = document.createElement('button');
|
|
3873
|
+
button.className = 'prompt-turn-action';
|
|
3874
|
+
button.type = 'button';
|
|
3875
|
+
button.dataset.promptTurnAction = 'toggle';
|
|
3876
|
+
meta.prepend(button);
|
|
3877
|
+
}
|
|
3878
|
+
syncMobilePromptTurnAction(turn);
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
function wireMobileLiveDetails(box) {
|
|
3882
|
+
if (!box) return;
|
|
3883
|
+
box.querySelectorAll('details.terminal-tail-panel[data-mobile-turn-key]').forEach((details) => {
|
|
3884
|
+
details.addEventListener('toggle', () => {
|
|
3885
|
+
rememberTimelineTurnExpanded(details.dataset.mobileTurnKey || '', details.open);
|
|
3886
|
+
});
|
|
3509
3887
|
});
|
|
3510
3888
|
}
|
|
3511
3889
|
|
|
@@ -3516,15 +3894,56 @@
|
|
|
3516
3894
|
textEl.classList.toggle('collapsed', !willExpand);
|
|
3517
3895
|
button.textContent = willExpand ? 'Show less' : 'Show more';
|
|
3518
3896
|
button.setAttribute('aria-expanded', willExpand ? 'true' : 'false');
|
|
3897
|
+
const key = mobileMessageKeyForText(textEl);
|
|
3898
|
+
if (key) {
|
|
3899
|
+
if (willExpand) state.detail.expandedMessageKeys.add(key);
|
|
3900
|
+
else state.detail.expandedMessageKeys.delete(key);
|
|
3901
|
+
}
|
|
3519
3902
|
return true;
|
|
3520
3903
|
}
|
|
3521
3904
|
|
|
3905
|
+
function mobileMessageKeyForText(textEl) {
|
|
3906
|
+
if (!textEl) return '';
|
|
3907
|
+
const direct = textEl.dataset?.mobileMessageKey || '';
|
|
3908
|
+
if (direct) return direct;
|
|
3909
|
+
const host = textEl.closest?.('[data-mobile-message-key]');
|
|
3910
|
+
return host?.dataset?.mobileMessageKey || '';
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
function annotateMobileMessageKeys(box, messages, mode) {
|
|
3914
|
+
if (!box) return;
|
|
3915
|
+
const source = mode === 'conversation'
|
|
3916
|
+
? (messages || []).map(normalizeMobileReviewMessage).filter(Boolean)
|
|
3917
|
+
: (messages || []);
|
|
3918
|
+
box.querySelectorAll('.msg-text[data-msg-idx]').forEach((textEl) => {
|
|
3919
|
+
const idx = Number(textEl.dataset.msgIdx);
|
|
3920
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= source.length) return;
|
|
3921
|
+
const key = timelineMessageKey(source[idx]);
|
|
3922
|
+
if (!key) return;
|
|
3923
|
+
textEl.dataset.mobileMessageKey = key;
|
|
3924
|
+
const row = textEl.closest('.review-msg, .message-row');
|
|
3925
|
+
if (row) row.dataset.mobileMessageKey = key;
|
|
3926
|
+
});
|
|
3927
|
+
box.querySelectorAll('[data-mobile-event-key]').forEach((row) => {
|
|
3928
|
+
const key = row.dataset.mobileEventKey || '';
|
|
3929
|
+
if (!key) return;
|
|
3930
|
+
row.dataset.mobileMessageKey = key;
|
|
3931
|
+
row.querySelectorAll('.msg-text, .message-body').forEach((textEl) => {
|
|
3932
|
+
textEl.dataset.mobileMessageKey = key;
|
|
3933
|
+
});
|
|
3934
|
+
});
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3522
3937
|
function wireMobileMsgExpandButtons(box) {
|
|
3523
3938
|
if (!box) return;
|
|
3524
3939
|
box.querySelectorAll('.msg-expand').forEach((button) => {
|
|
3525
3940
|
button.removeAttribute('onclick');
|
|
3526
3941
|
button.type = 'button';
|
|
3527
3942
|
const textEl = button.previousElementSibling;
|
|
3943
|
+
const key = mobileMessageKeyForText(textEl);
|
|
3944
|
+
if (key && state.detail.expandedMessageKeys.has(key) && textEl?.classList) {
|
|
3945
|
+
textEl.classList.remove('collapsed');
|
|
3946
|
+
}
|
|
3528
3947
|
const expanded = !!(textEl && textEl.classList && !textEl.classList.contains('collapsed'));
|
|
3529
3948
|
button.textContent = expanded ? 'Show less' : 'Show more';
|
|
3530
3949
|
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
@@ -3540,16 +3959,25 @@
|
|
|
3540
3959
|
toggleMobileMsgExpand(button);
|
|
3541
3960
|
}
|
|
3542
3961
|
|
|
3543
|
-
function annotateMobilePromptTurns(box, mode) {
|
|
3962
|
+
function annotateMobilePromptTurns(box, mode, messages = []) {
|
|
3544
3963
|
if (!box) return;
|
|
3545
3964
|
if (mode === 'conversation') {
|
|
3546
3965
|
box.setAttribute('role', 'feed');
|
|
3547
3966
|
box.setAttribute('aria-label', 'Session conversation grouped by prompt');
|
|
3548
3967
|
const turns = Array.from(box.querySelectorAll('.prompt-turn'));
|
|
3968
|
+
const sourceTurns = window.MR?.groupMessagesIntoTurns
|
|
3969
|
+
? window.MR.groupMessagesIntoTurns((messages || []).map(normalizeMobileReviewMessage).filter(Boolean))
|
|
3970
|
+
: [];
|
|
3549
3971
|
turns.forEach((turn, idx) => {
|
|
3550
3972
|
turn.setAttribute('role', 'article');
|
|
3551
3973
|
turn.setAttribute('aria-posinset', String(idx + 1));
|
|
3552
3974
|
turn.setAttribute('aria-setsize', String(turns.length));
|
|
3975
|
+
const key = turn.dataset.mobileTurnKey || mobilePromptTurnKey(sourceTurns[idx], idx);
|
|
3976
|
+
if (key) turn.dataset.mobileTurnKey = key;
|
|
3977
|
+
if (window.MR?.setPromptTurnExpanded) {
|
|
3978
|
+
window.MR.setPromptTurnExpanded(turn, isTimelineTurnExpanded(key));
|
|
3979
|
+
}
|
|
3980
|
+
ensureMobilePromptTurnAction(turn);
|
|
3553
3981
|
const label = turn.querySelector('.prompt-turn-prompt .msg-text, .prompt-turn-setup-title')?.textContent?.trim();
|
|
3554
3982
|
if (label) turn.setAttribute('aria-label', label.slice(0, 120));
|
|
3555
3983
|
});
|
|
@@ -3580,8 +4008,10 @@
|
|
|
3580
4008
|
box.innerHTML = rows.length
|
|
3581
4009
|
? rows.join('')
|
|
3582
4010
|
: '<div class="empty-state">No transcript is available yet.</div>';
|
|
3583
|
-
|
|
4011
|
+
annotateMobileMessageKeys(box, timelineMessages, mode);
|
|
4012
|
+
annotateMobilePromptTurns(box, mode, timelineMessages);
|
|
3584
4013
|
if (mode === 'conversation') wireMobilePromptTurns(box);
|
|
4014
|
+
wireMobileLiveDetails(box);
|
|
3585
4015
|
wireMobileMsgExpandButtons(box);
|
|
3586
4016
|
updateDetailTimelineModeButton();
|
|
3587
4017
|
if (wasNearBottom) box.scrollTop = box.scrollHeight;
|
|
@@ -3695,7 +4125,7 @@
|
|
|
3695
4125
|
menu.hidden = false;
|
|
3696
4126
|
button.setAttribute('aria-expanded', 'true');
|
|
3697
4127
|
state.sendMenu.suppressNextClick = opts.suppressNextClick !== false;
|
|
3698
|
-
($('send-prev-prompt-option') || $('send-esc-option'))?.focus({ preventScroll: true });
|
|
4128
|
+
if (opts.focus !== false) ($('send-prev-prompt-option') || $('send-esc-option'))?.focus({ preventScroll: true });
|
|
3699
4129
|
return true;
|
|
3700
4130
|
}
|
|
3701
4131
|
|
|
@@ -3710,7 +4140,7 @@
|
|
|
3710
4140
|
state.sendMenu.holdTimer = null;
|
|
3711
4141
|
state.sendMenu.touchStartX = null;
|
|
3712
4142
|
state.sendMenu.touchStartY = null;
|
|
3713
|
-
showSendMenu();
|
|
4143
|
+
showSendMenu({ focus: false });
|
|
3714
4144
|
}, SEND_LONG_PRESS_MS);
|
|
3715
4145
|
}
|
|
3716
4146
|
|
|
@@ -3752,7 +4182,7 @@
|
|
|
3752
4182
|
function handleSendButtonContextMenu(event) {
|
|
3753
4183
|
if (!state.activeSession) return;
|
|
3754
4184
|
event.preventDefault();
|
|
3755
|
-
showSendMenu();
|
|
4185
|
+
showSendMenu({ focus: false });
|
|
3756
4186
|
}
|
|
3757
4187
|
|
|
3758
4188
|
function handleSendButtonKeydown(event) {
|
|
@@ -3762,7 +4192,7 @@
|
|
|
3762
4192
|
}
|
|
3763
4193
|
if ((event.key === 'ArrowUp' || event.key === 'ContextMenu') && state.activeSession) {
|
|
3764
4194
|
event.preventDefault();
|
|
3765
|
-
showSendMenu();
|
|
4195
|
+
showSendMenu({ focus: true });
|
|
3766
4196
|
}
|
|
3767
4197
|
}
|
|
3768
4198
|
|
|
@@ -3770,6 +4200,255 @@
|
|
|
3770
4200
|
return !!target.closest?.('#send-menu, #detail-send');
|
|
3771
4201
|
}
|
|
3772
4202
|
|
|
4203
|
+
function walleModelProviderSort(provider) {
|
|
4204
|
+
const order = { anthropic: 1, openai: 2, google: 3, deepseek: 4, moonshot: 5, ollama: 6, mlx: 7 };
|
|
4205
|
+
return order[providerKey(provider)] || 20;
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
function normalizeWallePhoneModels(payload) {
|
|
4209
|
+
const rows = Array.isArray(payload) ? payload : (Array.isArray(payload?.models) ? payload.models : []);
|
|
4210
|
+
const out = [];
|
|
4211
|
+
for (const row of rows) {
|
|
4212
|
+
if (!row || row.enabled === false || row.enabled === 0 || row.available === false || row.coding_capable === false) continue;
|
|
4213
|
+
const provider = providerKey(row.provider_type || row.provider || '');
|
|
4214
|
+
const modelId = String(row.model_id || row.modelId || row.id || '').trim();
|
|
4215
|
+
const label = modelDisplayLabel(row.display_name || row.name || modelId);
|
|
4216
|
+
if (!modelId || !label) continue;
|
|
4217
|
+
out.push({
|
|
4218
|
+
id: String(row.id || `${provider}:${modelId}`).trim(),
|
|
4219
|
+
modelId,
|
|
4220
|
+
label,
|
|
4221
|
+
provider,
|
|
4222
|
+
providerId: String(row.provider_id || row.providerId || '').trim(),
|
|
4223
|
+
providerLabel: row.provider_name || providerLabel(provider) || 'Provider',
|
|
4224
|
+
reason: String(row.coding_reason || row.reason || '').trim(),
|
|
4225
|
+
});
|
|
4226
|
+
}
|
|
4227
|
+
out.sort((a, b) => {
|
|
4228
|
+
const providerCmp = walleModelProviderSort(a.provider) - walleModelProviderSort(b.provider);
|
|
4229
|
+
if (providerCmp) return providerCmp;
|
|
4230
|
+
return a.label.localeCompare(b.label);
|
|
4231
|
+
});
|
|
4232
|
+
return out;
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
function selectedWalleModelIds(card) {
|
|
4236
|
+
return {
|
|
4237
|
+
modelId: String(card?.walle_model_id || card?.model_id || card?.model || '').trim(),
|
|
4238
|
+
provider: providerKey(card?.walle_model_provider || card?.model_provider || card?.modelProvider || card?.providerType || ''),
|
|
4239
|
+
registryId: String(card?.model_registry_id || card?.modelRegistryId || '').trim(),
|
|
4240
|
+
providerId: String(card?.model_provider_id || card?.modelProviderId || '').trim(),
|
|
4241
|
+
};
|
|
4242
|
+
}
|
|
4243
|
+
|
|
4244
|
+
function updateModelPickerButtons() {
|
|
4245
|
+
const visible = isWalleCodingSession(state.activeSession);
|
|
4246
|
+
const label = visible ? modelButtonShortLabel(state.activeSession) : 'Model';
|
|
4247
|
+
const controls = [
|
|
4248
|
+
{ input: $('detail-input'), form: $('detail-composer'), button: $('detail-model'), surface: 'detail' },
|
|
4249
|
+
{ input: $('walle-input'), form: $('walle-chat-composer'), button: $('walle-model'), surface: 'walle' },
|
|
4250
|
+
];
|
|
4251
|
+
for (const control of controls) {
|
|
4252
|
+
const { input, form, button, surface } = control;
|
|
4253
|
+
if (!button || !form) continue;
|
|
4254
|
+
const show = visible && !!input && (surface === 'detail' ? isDetailOpen() : state.activeTab === 'walle');
|
|
4255
|
+
button.hidden = !show;
|
|
4256
|
+
button.disabled = !show || state.composerSending;
|
|
4257
|
+
button.setAttribute('aria-expanded', show && state.modelPicker.open ? 'true' : 'false');
|
|
4258
|
+
button.title = show ? modelButtonLabel(state.activeSession) : 'Choose model';
|
|
4259
|
+
const value = button.querySelector('.model-picker-btn-label');
|
|
4260
|
+
if (value) value.textContent = label;
|
|
4261
|
+
form.classList.toggle('has-model-picker', show);
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
|
|
4265
|
+
function renderModelPickerSheet() {
|
|
4266
|
+
const sheet = $('model-picker-sheet');
|
|
4267
|
+
const list = $('model-picker-list');
|
|
4268
|
+
const current = $('model-picker-current');
|
|
4269
|
+
if (!sheet || !list || !current) return;
|
|
4270
|
+
sheet.hidden = !state.modelPicker.open;
|
|
4271
|
+
updateModelPickerButtons();
|
|
4272
|
+
if (!state.modelPicker.open) return;
|
|
4273
|
+
|
|
4274
|
+
const card = findSession(state.modelPicker.sessionId) || state.activeSession;
|
|
4275
|
+
current.textContent = card
|
|
4276
|
+
? `Current: ${modelButtonLabel(card)}`
|
|
4277
|
+
: 'Use a specific coding model for this Wall-E session.';
|
|
4278
|
+
|
|
4279
|
+
if (state.modelPicker.loading) {
|
|
4280
|
+
list.innerHTML = '<div class="model-picker-empty">Loading coding models from Wall-E...</div>';
|
|
4281
|
+
return;
|
|
4282
|
+
}
|
|
4283
|
+
if (state.modelPicker.error) {
|
|
4284
|
+
list.innerHTML = `<div class="model-picker-empty">${esc(state.modelPicker.error)}</div>`;
|
|
4285
|
+
return;
|
|
4286
|
+
}
|
|
4287
|
+
const selected = selectedWalleModelIds(card);
|
|
4288
|
+
const autoSelected = !selected.modelId;
|
|
4289
|
+
const rows = [
|
|
4290
|
+
`<button class="model-picker-option" type="button" role="option" aria-selected="${autoSelected ? 'true' : 'false'}" data-model-choice="auto">
|
|
4291
|
+
<span class="model-picker-option-main">
|
|
4292
|
+
<span class="model-picker-option-title">Auto routing</span>
|
|
4293
|
+
<span class="model-picker-option-meta">Let Wall-E pick the configured coding default</span>
|
|
4294
|
+
</span>
|
|
4295
|
+
<span class="model-picker-option-check" aria-hidden="true">${autoSelected ? '✓' : ''}</span>
|
|
4296
|
+
</button>`,
|
|
4297
|
+
];
|
|
4298
|
+
for (let i = 0; i < state.modelPicker.models.length; i += 1) {
|
|
4299
|
+
const item = state.modelPicker.models[i];
|
|
4300
|
+
const isSelected = (!!selected.registryId && item.id === selected.registryId)
|
|
4301
|
+
|| (!!selected.modelId && item.modelId === selected.modelId && (!selected.provider || selected.provider === item.provider));
|
|
4302
|
+
const meta = [item.providerLabel, item.reason].filter(Boolean).join(' · ');
|
|
4303
|
+
rows.push(`
|
|
4304
|
+
<button class="model-picker-option" type="button" role="option" aria-selected="${isSelected ? 'true' : 'false'}" data-model-choice="${i}">
|
|
4305
|
+
<span class="model-picker-option-main">
|
|
4306
|
+
<span class="model-picker-option-title">${esc(item.label)}</span>
|
|
4307
|
+
<span class="model-picker-option-meta">${esc(meta || item.modelId)}</span>
|
|
4308
|
+
</span>
|
|
4309
|
+
<span class="model-picker-option-check" aria-hidden="true">${isSelected ? '✓' : ''}</span>
|
|
4310
|
+
</button>
|
|
4311
|
+
`);
|
|
4312
|
+
}
|
|
4313
|
+
list.innerHTML = rows.join('');
|
|
4314
|
+
}
|
|
4315
|
+
|
|
4316
|
+
async function loadWallePhoneModels(force = false) {
|
|
4317
|
+
if (state.modelPicker.loaded && !force) return state.modelPicker.models;
|
|
4318
|
+
const requestSeq = state.modelPicker.requestSeq + 1;
|
|
4319
|
+
state.modelPicker.requestSeq = requestSeq;
|
|
4320
|
+
state.modelPicker.loading = true;
|
|
4321
|
+
state.modelPicker.error = '';
|
|
4322
|
+
renderModelPickerSheet();
|
|
4323
|
+
try {
|
|
4324
|
+
const payload = await api('/api/models/coding-availability');
|
|
4325
|
+
if (requestSeq !== state.modelPicker.requestSeq) return state.modelPicker.models;
|
|
4326
|
+
state.modelPicker.models = normalizeWallePhoneModels(payload);
|
|
4327
|
+
state.modelPicker.loaded = true;
|
|
4328
|
+
if (!state.modelPicker.models.length) state.modelPicker.error = 'No coding-capable models are available.';
|
|
4329
|
+
} catch (err) {
|
|
4330
|
+
if (requestSeq === state.modelPicker.requestSeq) {
|
|
4331
|
+
state.modelPicker.error = err?.message || 'Could not load coding models.';
|
|
4332
|
+
}
|
|
4333
|
+
} finally {
|
|
4334
|
+
if (requestSeq === state.modelPicker.requestSeq) {
|
|
4335
|
+
state.modelPicker.loading = false;
|
|
4336
|
+
renderModelPickerSheet();
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
return state.modelPicker.models;
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
function openModelPickerForActiveSession(input = composerElement('input')) {
|
|
4343
|
+
const card = state.activeSession;
|
|
4344
|
+
if (!isWalleCodingSession(card)) return;
|
|
4345
|
+
hideMobileSkillPicker();
|
|
4346
|
+
hideSendMenu();
|
|
4347
|
+
state.modelPicker.open = true;
|
|
4348
|
+
state.modelPicker.sessionId = card.id;
|
|
4349
|
+
state.modelPicker.inputId = input?.id || '';
|
|
4350
|
+
state.modelPicker.error = '';
|
|
4351
|
+
renderModelPickerSheet();
|
|
4352
|
+
loadWallePhoneModels().catch(() => {});
|
|
4353
|
+
}
|
|
4354
|
+
|
|
4355
|
+
function closeModelPicker() {
|
|
4356
|
+
state.modelPicker.open = false;
|
|
4357
|
+
state.modelPicker.sessionId = '';
|
|
4358
|
+
state.modelPicker.inputId = '';
|
|
4359
|
+
renderModelPickerSheet();
|
|
4360
|
+
}
|
|
4361
|
+
|
|
4362
|
+
function applyWalleModelLocally(sessionId, item) {
|
|
4363
|
+
if (!sessionId) return;
|
|
4364
|
+
const patch = item
|
|
4365
|
+
? {
|
|
4366
|
+
model: item.modelId,
|
|
4367
|
+
model_id: item.modelId,
|
|
4368
|
+
modelProvider: item.provider,
|
|
4369
|
+
model_provider: item.provider,
|
|
4370
|
+
model_registry_id: item.id || '',
|
|
4371
|
+
model_provider_id: item.providerId || '',
|
|
4372
|
+
walle_model_id: item.modelId,
|
|
4373
|
+
walle_model_provider: item.provider,
|
|
4374
|
+
model_pinned: true,
|
|
4375
|
+
}
|
|
4376
|
+
: {
|
|
4377
|
+
model: '',
|
|
4378
|
+
model_id: '',
|
|
4379
|
+
modelProvider: '',
|
|
4380
|
+
model_provider: '',
|
|
4381
|
+
model_registry_id: '',
|
|
4382
|
+
model_provider_id: '',
|
|
4383
|
+
walle_model_id: null,
|
|
4384
|
+
walle_model_provider: null,
|
|
4385
|
+
model_pinned: false,
|
|
4386
|
+
};
|
|
4387
|
+
const idx = findSessionIndex(sessionId);
|
|
4388
|
+
if (idx >= 0) state.sessions[idx] = { ...state.sessions[idx], ...patch };
|
|
4389
|
+
if (state.activeSession && (state.activeSession.id === sessionId || state.activeSession.sessionId === sessionId)) {
|
|
4390
|
+
state.activeSession = { ...state.activeSession, ...patch };
|
|
4391
|
+
}
|
|
4392
|
+
if (state.walle.sessionId === sessionId) {
|
|
4393
|
+
const active = findSession(sessionId) || state.activeSession;
|
|
4394
|
+
if (active && $('walle-context')) updateWalleContext(active, walleSessionCards());
|
|
4395
|
+
}
|
|
4396
|
+
persistSessionSnapshot();
|
|
4397
|
+
updateModelPickerButtons();
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
function appendWalleModelSelection(body, card) {
|
|
4401
|
+
if (!body || !card) return body;
|
|
4402
|
+
const selected = selectedWalleModelIds(card);
|
|
4403
|
+
if (selected.modelId) {
|
|
4404
|
+
body.model_id = selected.modelId;
|
|
4405
|
+
body.model = selected.modelId;
|
|
4406
|
+
body.model_provider = selected.provider || '';
|
|
4407
|
+
body.provider = selected.provider || '';
|
|
4408
|
+
body.model_registry_id = selected.registryId || '';
|
|
4409
|
+
body.model_provider_id = selected.providerId || '';
|
|
4410
|
+
body.modelPinned = true;
|
|
4411
|
+
body.allowProviderFallback = false;
|
|
4412
|
+
}
|
|
4413
|
+
return body;
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4416
|
+
async function chooseWallePhoneModel(choice) {
|
|
4417
|
+
const sessionId = state.modelPicker.sessionId || state.activeSession?.id || '';
|
|
4418
|
+
if (!sessionId) return;
|
|
4419
|
+
const item = choice === 'auto' ? null : state.modelPicker.models[Number(choice)];
|
|
4420
|
+
if (choice !== 'auto' && !item) return;
|
|
4421
|
+
const input = $(state.modelPicker.inputId) || composerElement('input');
|
|
4422
|
+
try {
|
|
4423
|
+
setComposerStatus('Saving model for this session...', '', input);
|
|
4424
|
+
const body = item
|
|
4425
|
+
? {
|
|
4426
|
+
session_id: sessionId,
|
|
4427
|
+
model_id: item.modelId,
|
|
4428
|
+
model_provider: item.provider,
|
|
4429
|
+
model_registry_id: item.id || '',
|
|
4430
|
+
model_provider_id: item.providerId || '',
|
|
4431
|
+
scope: 'session',
|
|
4432
|
+
pinned: true,
|
|
4433
|
+
}
|
|
4434
|
+
: {
|
|
4435
|
+
session_id: sessionId,
|
|
4436
|
+
model_id: '',
|
|
4437
|
+
model_provider: '',
|
|
4438
|
+
scope: 'session',
|
|
4439
|
+
pinned: false,
|
|
4440
|
+
};
|
|
4441
|
+
await sendRemoteMessage('wall_e.set_model', body);
|
|
4442
|
+
applyWalleModelLocally(sessionId, item);
|
|
4443
|
+
closeModelPicker();
|
|
4444
|
+
setComposerStatus(item ? `Model set to ${item.label}.` : 'Model reset to auto routing.', 'ok', input);
|
|
4445
|
+
} catch (err) {
|
|
4446
|
+
state.modelPicker.error = remoteSendErrorMessage(err);
|
|
4447
|
+
renderModelPickerSheet();
|
|
4448
|
+
setComposerStatus(state.modelPicker.error, 'error', input);
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
|
|
3773
4452
|
function formatBytes(bytes) {
|
|
3774
4453
|
const value = Number(bytes) || 0;
|
|
3775
4454
|
if (value < 1024) return `${value} B`;
|
|
@@ -3874,6 +4553,7 @@
|
|
|
3874
4553
|
setComposerText('', { input });
|
|
3875
4554
|
state.promptHistory.index = -1;
|
|
3876
4555
|
state.promptHistory.draft = '';
|
|
4556
|
+
updatePromptHistoryStrip({ hide: true });
|
|
3877
4557
|
autoSizeComposer(input);
|
|
3878
4558
|
setComposerStatus('', '');
|
|
3879
4559
|
hideMobileSkillPicker();
|
|
@@ -3891,6 +4571,7 @@
|
|
|
3891
4571
|
state.promptHistory.requestSeq += 1;
|
|
3892
4572
|
state.promptHistory.applying = false;
|
|
3893
4573
|
state.promptHistory.loadPromise = null;
|
|
4574
|
+
updatePromptHistoryStrip({ hide: true });
|
|
3894
4575
|
}
|
|
3895
4576
|
|
|
3896
4577
|
function normalizePromptHistoryEntries(items) {
|
|
@@ -3956,14 +4637,59 @@
|
|
|
3956
4637
|
return promise;
|
|
3957
4638
|
}
|
|
3958
4639
|
|
|
3959
|
-
function
|
|
4640
|
+
function promptHistoryMeta() {
|
|
4641
|
+
const prompts = state.promptHistory.prompts || [];
|
|
4642
|
+
const index = state.promptHistory.index;
|
|
4643
|
+
if (index < 0 || !prompts.length) return '';
|
|
4644
|
+
return `Prompt ${index + 1} of ${prompts.length}. Tap Edit to open the keyboard, or Send to reuse it.`;
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
function updatePromptHistoryStrip(opts = {}) {
|
|
4648
|
+
const strip = $('prompt-history-strip');
|
|
4649
|
+
if (!strip) return;
|
|
4650
|
+
if (opts.hide || state.promptHistory.index < 0) {
|
|
4651
|
+
strip.hidden = true;
|
|
4652
|
+
return;
|
|
4653
|
+
}
|
|
4654
|
+
const prompts = state.promptHistory.prompts || [];
|
|
4655
|
+
const index = state.promptHistory.index;
|
|
4656
|
+
strip.hidden = false;
|
|
4657
|
+
const title = $('prompt-history-title');
|
|
4658
|
+
const meta = $('prompt-history-meta');
|
|
4659
|
+
if (title) title.textContent = 'Recalled prompt';
|
|
4660
|
+
if (meta) meta.textContent = promptHistoryMeta();
|
|
4661
|
+
const prev = $('prompt-history-prev');
|
|
4662
|
+
const next = $('prompt-history-next');
|
|
4663
|
+
if (prev) prev.disabled = index <= 0;
|
|
4664
|
+
if (next) next.disabled = index < 0 || index >= prompts.length;
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4667
|
+
function finishPromptHistoryEditing() {
|
|
4668
|
+
const input = composerElement('input');
|
|
4669
|
+
if (!input) return;
|
|
4670
|
+
const text = getComposerText(input);
|
|
4671
|
+
setComposerText(text, { input, focus: true, cursor: text.length });
|
|
4672
|
+
setComposerStatus('Editing recalled prompt.', 'ok');
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4675
|
+
function closePromptHistoryStrip() {
|
|
4676
|
+
const input = composerElement('input');
|
|
4677
|
+
state.promptHistory.index = -1;
|
|
4678
|
+
state.promptHistory.draft = input ? getComposerText(input) : '';
|
|
4679
|
+
updatePromptHistoryStrip({ hide: true });
|
|
4680
|
+
setComposerStatus('Prompt history controls hidden.', '');
|
|
4681
|
+
}
|
|
4682
|
+
|
|
4683
|
+
function setComposerTextFromHistory(text, opts = {}) {
|
|
3960
4684
|
const input = composerElement('input');
|
|
3961
4685
|
if (!input) return;
|
|
3962
4686
|
state.promptHistory.applying = true;
|
|
3963
|
-
setComposerText(text || '', { input });
|
|
4687
|
+
setComposerText(text || '', { input, focus: opts.focus === true, cursor: opts.focus === true ? undefined : false });
|
|
4688
|
+
if (opts.focus !== true && document.activeElement === input) input.blur();
|
|
3964
4689
|
autoSizeComposer(input);
|
|
3965
4690
|
hideMobileSkillPicker();
|
|
3966
4691
|
updateComposerState(input);
|
|
4692
|
+
updatePromptHistoryStrip();
|
|
3967
4693
|
state.promptHistory.applying = false;
|
|
3968
4694
|
}
|
|
3969
4695
|
|
|
@@ -3986,6 +4712,7 @@
|
|
|
3986
4712
|
if (state.promptHistory.index >= prompts.length) {
|
|
3987
4713
|
state.promptHistory.index = -1;
|
|
3988
4714
|
setComposerTextFromHistory(state.promptHistory.draft || '');
|
|
4715
|
+
updatePromptHistoryStrip({ hide: true });
|
|
3989
4716
|
setComposerStatus(state.promptHistory.draft ? 'Draft restored.' : 'Back to current draft.', 'ok');
|
|
3990
4717
|
return true;
|
|
3991
4718
|
}
|
|
@@ -4184,6 +4911,7 @@
|
|
|
4184
4911
|
const button = composerElement('send', input);
|
|
4185
4912
|
if (!input || !button) return;
|
|
4186
4913
|
const text = getComposerText(input);
|
|
4914
|
+
applyComposerTextAssistMode(input);
|
|
4187
4915
|
const hasText = !!text.trim();
|
|
4188
4916
|
const hasAttachments = state.composerAttachments.length > 0;
|
|
4189
4917
|
const canMenu = !!state.activeSession;
|
|
@@ -4205,6 +4933,7 @@
|
|
|
4205
4933
|
button.setAttribute('aria-label', state.composerSending ? 'Sending to Wall-E' : 'Send to Wall-E');
|
|
4206
4934
|
button.title = 'Send to Wall-E';
|
|
4207
4935
|
}
|
|
4936
|
+
updateModelPickerButtons();
|
|
4208
4937
|
const clear = composerElement('clear', input);
|
|
4209
4938
|
if (clear) {
|
|
4210
4939
|
clear.hidden = text.length === 0;
|
|
@@ -4475,6 +5204,9 @@
|
|
|
4475
5204
|
const payload = err?.payload || {};
|
|
4476
5205
|
const code = payload.error || payload.result?.error || payload.result?.result?.error || err?.message || '';
|
|
4477
5206
|
const message = payload.message || payload.result?.message || payload.result?.result?.message || '';
|
|
5207
|
+
if (/session_busy/.test(code)) return 'The agent is still working. This reply will stay queued and retry automatically.';
|
|
5208
|
+
if (/session_input_in_flight/.test(code)) return 'Another phone reply is already being delivered. This reply will retry automatically.';
|
|
5209
|
+
if (/session_waiting_for_approval/.test(code)) return 'This session is waiting for an approval or choice. Respond to that prompt before sending a new message.';
|
|
4478
5210
|
if (/session_not_live/.test(code)) return 'This session is not live on the Mac. Open a live terminal session before sending.';
|
|
4479
5211
|
if (/session_not_found/.test(code)) return 'This session no longer exists on the Mac. Refresh the session list.';
|
|
4480
5212
|
if (/remote_action_not_enabled|approval.respond/.test(code)) return 'This action is not enabled from phone yet.';
|
|
@@ -4529,19 +5261,23 @@
|
|
|
4529
5261
|
const card = state.activeSession;
|
|
4530
5262
|
if (!card) return;
|
|
4531
5263
|
if (!text && !attachments.length) {
|
|
4532
|
-
showSendMenu({ suppressNextClick: false });
|
|
5264
|
+
showSendMenu({ suppressNextClick: false, focus: false });
|
|
4533
5265
|
return;
|
|
4534
5266
|
}
|
|
4535
5267
|
const outboundText = buildComposerTransportText(text, attachments);
|
|
4536
5268
|
try {
|
|
4537
5269
|
const messageType = isWalleSession(card) ? 'wall_e.send_message' : 'session.send_message';
|
|
4538
5270
|
const body = { session_id: card.id, text: outboundText };
|
|
4539
|
-
if (messageType === 'wall_e.send_message'
|
|
5271
|
+
if (messageType === 'wall_e.send_message') {
|
|
5272
|
+
if (attachments.length) body.attachments = attachments;
|
|
5273
|
+
appendWalleModelSelection(body, card);
|
|
5274
|
+
}
|
|
4540
5275
|
const entry = enqueueRemoteReply(messageType, body);
|
|
4541
5276
|
appendQueuedUserMessage(entry);
|
|
4542
5277
|
setComposerText('', { input, focus: false, cursor: false });
|
|
4543
5278
|
state.promptHistory.index = -1;
|
|
4544
5279
|
state.promptHistory.draft = '';
|
|
5280
|
+
updatePromptHistoryStrip({ hide: true });
|
|
4545
5281
|
autoSizeComposer(input);
|
|
4546
5282
|
clearComposerAttachments();
|
|
4547
5283
|
updateComposerState(input);
|
|
@@ -4566,12 +5302,14 @@
|
|
|
4566
5302
|
try {
|
|
4567
5303
|
const body = { session_id: card.id, text: outboundText };
|
|
4568
5304
|
if (attachments.length) body.attachments = attachments;
|
|
5305
|
+
appendWalleModelSelection(body, card);
|
|
4569
5306
|
const entry = enqueueRemoteReply('wall_e.send_message', body);
|
|
4570
5307
|
appendQueuedUserMessage(entry);
|
|
4571
5308
|
state.walle.draft = '';
|
|
4572
5309
|
setComposerText('', { input, focus: false, cursor: false });
|
|
4573
5310
|
state.promptHistory.index = -1;
|
|
4574
5311
|
state.promptHistory.draft = '';
|
|
5312
|
+
updatePromptHistoryStrip({ hide: true });
|
|
4575
5313
|
autoSizeComposer(input);
|
|
4576
5314
|
clearComposerAttachments();
|
|
4577
5315
|
updateComposerState(input);
|
|
@@ -4863,20 +5601,43 @@
|
|
|
4863
5601
|
async function createSession(event) {
|
|
4864
5602
|
event.preventDefault();
|
|
4865
5603
|
const cwd = $('new-session-project').value || undefined;
|
|
4866
|
-
const
|
|
5604
|
+
const agentSelect = $('new-session-agent');
|
|
5605
|
+
const agent = agentSelect.value;
|
|
5606
|
+
const agentLabel = agentSelect.selectedOptions?.[0]?.textContent?.trim() || agent;
|
|
4867
5607
|
const prompt = $('new-session-prompt').value.trim();
|
|
4868
|
-
const
|
|
4869
|
-
const
|
|
5608
|
+
const isWalle = agent === 'walle';
|
|
5609
|
+
const cmd = agent === 'codex'
|
|
5610
|
+
? 'codex'
|
|
5611
|
+
: agent === 'gemini'
|
|
5612
|
+
? 'gemini'
|
|
5613
|
+
: agent === 'shell'
|
|
5614
|
+
? undefined
|
|
5615
|
+
: isWalle
|
|
5616
|
+
? 'walle'
|
|
5617
|
+
: 'claude';
|
|
5618
|
+
const args = prompt && !isWalle ? [prompt] : [];
|
|
4870
5619
|
await withStepUp({
|
|
4871
5620
|
title: 'Confirm new session',
|
|
4872
|
-
copy: `Spawn ${
|
|
5621
|
+
copy: `Spawn ${agentLabel} in ${compactPath(cwd || 'the selected project')}.`,
|
|
4873
5622
|
label: 'Face ID & Spawn',
|
|
4874
5623
|
action: async () => {
|
|
4875
5624
|
const id = randomId();
|
|
4876
|
-
|
|
5625
|
+
const payload = { type: 'create', id, cwd, cmd, args };
|
|
5626
|
+
if (isWalle) {
|
|
5627
|
+
Object.assign(payload, {
|
|
5628
|
+
label: 'Wall-E Coding',
|
|
5629
|
+
agentType: 'walle',
|
|
5630
|
+
agentMode: 'coding',
|
|
5631
|
+
agentKind: 'walle-coding',
|
|
5632
|
+
taskType: 'coding',
|
|
5633
|
+
_skipProjectConfig: true,
|
|
5634
|
+
});
|
|
5635
|
+
if (prompt) payload.initialMessage = prompt;
|
|
5636
|
+
}
|
|
5637
|
+
await sendWs(payload);
|
|
4877
5638
|
$('new-session-sheet').hidden = true;
|
|
4878
5639
|
await refresh();
|
|
4879
|
-
const card = { id, title:
|
|
5640
|
+
const card = { id, title: agentLabel, cwd, agent, lane: 'running', status: 'running' };
|
|
4880
5641
|
state.sessions.unshift(card);
|
|
4881
5642
|
openDetail(id);
|
|
4882
5643
|
},
|
|
@@ -4915,9 +5676,23 @@
|
|
|
4915
5676
|
$('detail-send').addEventListener('click', handleSendButtonClick);
|
|
4916
5677
|
$('detail-send').addEventListener('contextmenu', handleSendButtonContextMenu);
|
|
4917
5678
|
$('detail-send').addEventListener('keydown', handleSendButtonKeydown);
|
|
5679
|
+
$('detail-model')?.addEventListener('click', () => openModelPickerForActiveSession($('detail-input')));
|
|
5680
|
+
$('model-picker-close')?.addEventListener('click', closeModelPicker);
|
|
5681
|
+
$('model-picker-sheet')?.addEventListener('click', (event) => {
|
|
5682
|
+
if (event.target?.id === 'model-picker-sheet') {
|
|
5683
|
+
closeModelPicker();
|
|
5684
|
+
return;
|
|
5685
|
+
}
|
|
5686
|
+
const choice = event.target.closest?.('[data-model-choice]');
|
|
5687
|
+
if (choice) chooseWallePhoneModel(choice.dataset.modelChoice);
|
|
5688
|
+
});
|
|
4918
5689
|
$('detail-clear')?.addEventListener('click', () => clearComposerText());
|
|
4919
5690
|
$('send-prev-prompt-option')?.addEventListener('click', () => sendPromptHistoryToSession('previous'));
|
|
4920
5691
|
$('send-next-prompt-option')?.addEventListener('click', () => sendPromptHistoryToSession('next'));
|
|
5692
|
+
$('prompt-history-prev')?.addEventListener('click', () => applyPromptHistoryNavigation('previous'));
|
|
5693
|
+
$('prompt-history-next')?.addEventListener('click', () => applyPromptHistoryNavigation('next'));
|
|
5694
|
+
$('prompt-history-edit')?.addEventListener('click', finishPromptHistoryEditing);
|
|
5695
|
+
$('prompt-history-close')?.addEventListener('click', closePromptHistoryStrip);
|
|
4921
5696
|
$('send-copy-url-option')?.addEventListener('click', copySessionUrlFromMenu);
|
|
4922
5697
|
$('send-esc-option')?.addEventListener('click', sendEscapeToSession);
|
|
4923
5698
|
$('send-attach-image-option')?.addEventListener('click', () => triggerAttachmentInput('image'));
|
|
@@ -4935,6 +5710,7 @@
|
|
|
4935
5710
|
if (!state.promptHistory.applying) {
|
|
4936
5711
|
state.promptHistory.index = -1;
|
|
4937
5712
|
state.promptHistory.draft = getComposerText(event.currentTarget);
|
|
5713
|
+
updatePromptHistoryStrip({ hide: true });
|
|
4938
5714
|
}
|
|
4939
5715
|
updateComposerState();
|
|
4940
5716
|
updateMobileSkillPicker(event.currentTarget);
|
|
@@ -4974,6 +5750,10 @@
|
|
|
4974
5750
|
$('walle-content')?.addEventListener('click', (event) => {
|
|
4975
5751
|
const input = $('walle-input');
|
|
4976
5752
|
if (event.target?.id === 'walle-input') updateMobileSkillPicker(event.target);
|
|
5753
|
+
if (event.target.closest?.('#walle-model')) {
|
|
5754
|
+
openModelPickerForActiveSession(input);
|
|
5755
|
+
return;
|
|
5756
|
+
}
|
|
4977
5757
|
if (event.target.closest?.('#walle-clear')) clearComposerText(input);
|
|
4978
5758
|
const attach = event.target.closest?.('[data-walle-attach]');
|
|
4979
5759
|
if (attach) triggerAttachmentInput(attach.dataset.walleAttach === 'image' ? 'image' : 'file');
|
|
@@ -5019,7 +5799,10 @@
|
|
|
5019
5799
|
if (!shouldKeepSendMenuOpen(event.target)) hideSendMenu();
|
|
5020
5800
|
});
|
|
5021
5801
|
document.addEventListener('keydown', (event) => {
|
|
5022
|
-
if (event.key === 'Escape')
|
|
5802
|
+
if (event.key === 'Escape') {
|
|
5803
|
+
hideSendMenu();
|
|
5804
|
+
closeModelPicker();
|
|
5805
|
+
}
|
|
5023
5806
|
});
|
|
5024
5807
|
$('stepup-cancel').addEventListener('click', () => {
|
|
5025
5808
|
$('stepup-sheet').hidden = true;
|
|
@@ -5045,6 +5828,9 @@
|
|
|
5045
5828
|
applySearchSuggestion(chip.dataset.searchSuggestion || '');
|
|
5046
5829
|
});
|
|
5047
5830
|
window.addEventListener('hashchange', handleDeepLink);
|
|
5831
|
+
window.addEventListener('resize', updateMobileViewportInset);
|
|
5832
|
+
window.visualViewport?.addEventListener?.('resize', updateMobileViewportInset);
|
|
5833
|
+
window.visualViewport?.addEventListener?.('scroll', updateMobileViewportInset);
|
|
5048
5834
|
window.addEventListener('online', () => {
|
|
5049
5835
|
recoverMobileConnection('online').finally(() => scheduleRemoteOutboxDrain(0));
|
|
5050
5836
|
});
|
|
@@ -5078,6 +5864,7 @@
|
|
|
5078
5864
|
|
|
5079
5865
|
async function init() {
|
|
5080
5866
|
loadThemePreference();
|
|
5867
|
+
updateMobileViewportInset();
|
|
5081
5868
|
configureComposerInput();
|
|
5082
5869
|
bindEvents();
|
|
5083
5870
|
loadRemoteOutbox();
|