create-walle 0.9.13 → 0.9.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +8 -3
  2. package/bin/create-walle.js +232 -32
  3. package/bin/mcp-inject.js +18 -53
  4. package/package.json +3 -1
  5. package/template/claude-task-manager/api-prompts.js +11 -2
  6. package/template/claude-task-manager/approval-agent.js +7 -0
  7. package/template/claude-task-manager/db.js +94 -75
  8. package/template/claude-task-manager/docs/session-standup-command-center-design.md +242 -0
  9. package/template/claude-task-manager/docs/session-tooltip-freshness-design.md +224 -0
  10. package/template/claude-task-manager/docs/session-ux-issue-review-2026-05-01.md +369 -0
  11. package/template/claude-task-manager/fuzzy-utils.js +10 -2
  12. package/template/claude-task-manager/git-utils.js +140 -10
  13. package/template/claude-task-manager/lib/agent-capabilities.js +1 -1
  14. package/template/claude-task-manager/lib/agent-presets.js +38 -5
  15. package/template/claude-task-manager/lib/codex-terminal-final.js +53 -0
  16. package/template/claude-task-manager/lib/ctm-session-context-api.js +222 -0
  17. package/template/claude-task-manager/lib/session-diagnostics.js +56 -0
  18. package/template/claude-task-manager/lib/session-history.js +309 -16
  19. package/template/claude-task-manager/lib/session-standup.js +409 -0
  20. package/template/claude-task-manager/lib/session-stream.js +253 -20
  21. package/template/claude-task-manager/lib/standup-attention.js +200 -0
  22. package/template/claude-task-manager/lib/status-hooks.js +8 -2
  23. package/template/claude-task-manager/lib/update-telemetry.js +114 -0
  24. package/template/claude-task-manager/lib/walle-ctm-history.js +49 -6
  25. package/template/claude-task-manager/lib/walle-default-model.js +55 -0
  26. package/template/claude-task-manager/lib/walle-mcp-auto-config.js +66 -0
  27. package/template/claude-task-manager/lib/walle-supervisor.js +86 -19
  28. package/template/claude-task-manager/lib/walle-transcript.js +1 -3
  29. package/template/claude-task-manager/lib/worktree-cwd.js +82 -0
  30. package/template/claude-task-manager/package.json +1 -0
  31. package/template/claude-task-manager/providers/codex-mcp.js +104 -0
  32. package/template/claude-task-manager/providers/index.js +2 -0
  33. package/template/claude-task-manager/public/css/setup.css +2 -1
  34. package/template/claude-task-manager/public/css/walle.css +71 -0
  35. package/template/claude-task-manager/public/index.html +2388 -429
  36. package/template/claude-task-manager/public/js/message-renderer.js +314 -35
  37. package/template/claude-task-manager/public/js/session-search-utils.js +185 -3
  38. package/template/claude-task-manager/public/js/session-status-precedence.js +125 -0
  39. package/template/claude-task-manager/public/js/setup.js +62 -19
  40. package/template/claude-task-manager/public/js/stream-view.js +396 -55
  41. package/template/claude-task-manager/public/js/terminal-restore-state.js +57 -0
  42. package/template/claude-task-manager/public/js/walle-session.js +234 -26
  43. package/template/claude-task-manager/public/js/walle.js +143 -2
  44. package/template/claude-task-manager/server.js +1402 -433
  45. package/template/claude-task-manager/session-integrity.js +77 -28
  46. package/template/claude-task-manager/workers/approval-widget-validator.js +15 -5
  47. package/template/claude-task-manager/workers/scrollback-worker.js +5 -6
  48. package/template/claude-task-manager/workers/state-detectors/codex.js +6 -0
  49. package/template/package.json +1 -1
  50. package/template/wall-e/agent-runners/claude-code.js +2 -0
  51. package/template/wall-e/agent.js +63 -8
  52. package/template/wall-e/api-walle.js +330 -52
  53. package/template/wall-e/brain.js +291 -42
  54. package/template/wall-e/chat.js +172 -15
  55. package/template/wall-e/coding/compaction-service.js +19 -5
  56. package/template/wall-e/coding/stream-processor.js +22 -2
  57. package/template/wall-e/coding/workspace-replay.js +1 -4
  58. package/template/wall-e/coding-orchestrator.js +250 -80
  59. package/template/wall-e/compat.js +0 -28
  60. package/template/wall-e/context/context-builder.js +3 -1
  61. package/template/wall-e/embeddings.js +2 -7
  62. package/template/wall-e/eval/agent-runner.js +30 -9
  63. package/template/wall-e/eval/benchmark-generator.js +21 -1
  64. package/template/wall-e/eval/benchmarks/chat-eval.json +66 -6
  65. package/template/wall-e/eval/benchmarks/coding-agent.json +0 -596
  66. package/template/wall-e/eval/cc-replay.js +1 -0
  67. package/template/wall-e/eval/codex-cli-baseline.js +633 -0
  68. package/template/wall-e/eval/debug-agent003.js +1 -0
  69. package/template/wall-e/eval/eval-orchestrator.js +3 -3
  70. package/template/wall-e/eval/run-agent-benchmarks.js +11 -3
  71. package/template/wall-e/eval/run-codex-cli-baseline.js +177 -0
  72. package/template/wall-e/eval/run-model-comparison.js +1 -0
  73. package/template/wall-e/eval/swebench-adapter.js +1 -0
  74. package/template/wall-e/evaluation/quorum-evaluator.js +0 -1
  75. package/template/wall-e/extraction/knowledge-extractor.js +1 -2
  76. package/template/wall-e/lib/mcp-integration.js +336 -0
  77. package/template/wall-e/llm/ollama.js +47 -8
  78. package/template/wall-e/llm/ollama.plugin.json +1 -1
  79. package/template/wall-e/llm/tool-adapter.js +1 -0
  80. package/template/wall-e/loops/ingest.js +42 -8
  81. package/template/wall-e/loops/initiative.js +87 -2
  82. package/template/wall-e/mcp-server.js +872 -19
  83. package/template/wall-e/memory/ctm-context-client.js +230 -0
  84. package/template/wall-e/memory/ctm-session-context.js +1376 -0
  85. package/template/wall-e/prompts/coding/memory-protocol.md +6 -0
  86. package/template/wall-e/server.js +30 -1
  87. package/template/wall-e/skills/_bundled/memory-search/SKILL.md +8 -0
  88. package/template/wall-e/skills/_bundled/scan-ctm-sessions/SKILL.md +20 -0
  89. package/template/wall-e/skills/_bundled/scan-ctm-sessions/run.js +43 -0
  90. package/template/wall-e/skills/_bundled/slack-mentions/run.js +471 -188
  91. package/template/wall-e/skills/skill-planner.js +86 -4
  92. package/template/wall-e/slack/socket-mode-listener.js +276 -0
  93. package/template/wall-e/telemetry.js +70 -2
  94. package/template/wall-e/tools/builtin-middleware.js +55 -2
  95. package/template/wall-e/tools/shell-policy.js +1 -1
  96. package/template/wall-e/tools/slack-owner.js +104 -0
  97. package/template/website/index.html +4 -4
  98. package/template/builder-journal.md +0 -17
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ (function initTerminalRestoreState(root, factory) {
4
+ if (typeof module === 'object' && module.exports) {
5
+ module.exports = factory();
6
+ } else {
7
+ root.TerminalRestoreState = factory();
8
+ }
9
+ })(typeof globalThis !== 'undefined' ? globalThis : this, function buildTerminalRestoreState() {
10
+ const STATES = Object.freeze({
11
+ INACTIVE: 'inactive',
12
+ ACTIVATING: 'activating',
13
+ RESTORE_SAVED: 'restore_saved',
14
+ RESTORE_SNAPSHOT: 'restore_snapshot',
15
+ WAIT_FRESH_SNAPSHOT: 'wait_fresh_snapshot',
16
+ ATTACHING: 'attaching',
17
+ SNAPSHOT_REQUESTED: 'snapshot_requested',
18
+ });
19
+
20
+ function shouldShowInitialOverlay({ hasTerminal, canRestoreExitedText, hasCachedSnapshot, shouldRestoreSavedText }) {
21
+ return Boolean(hasTerminal && !canRestoreExitedText && !hasCachedSnapshot && !shouldRestoreSavedText);
22
+ }
23
+
24
+ function activationRestoreDecision({
25
+ canRestoreExitedText,
26
+ shouldRestoreSavedText,
27
+ hasCachedSnapshot,
28
+ cachedSnapshotDimsMatch,
29
+ } = {}) {
30
+ if (canRestoreExitedText) {
31
+ return { state: STATES.RESTORE_SAVED, action: 'restore-saved-scrollback', reason: 'exited-saved-scrollback' };
32
+ }
33
+ if (shouldRestoreSavedText) {
34
+ return { state: STATES.RESTORE_SAVED, action: 'restore-saved-scrollback', reason: 'snapshot-shorter-than-saved' };
35
+ }
36
+ if (hasCachedSnapshot && cachedSnapshotDimsMatch) {
37
+ return { state: STATES.RESTORE_SNAPSHOT, action: 'restore-cached-snapshot', reason: 'cached-dims-match' };
38
+ }
39
+ if (hasCachedSnapshot && !cachedSnapshotDimsMatch) {
40
+ return { state: STATES.WAIT_FRESH_SNAPSHOT, action: 'show-loading-overlay', reason: 'cached-dims-mismatch' };
41
+ }
42
+ return { state: STATES.WAIT_FRESH_SNAPSHOT, action: 'wait-fresh-snapshot', reason: 'no-local-restore' };
43
+ }
44
+
45
+ function activationRequestDecision({ needsAttach } = {}) {
46
+ return needsAttach
47
+ ? { state: STATES.ATTACHING, messageType: 'attach', clearStaleQueue: true, reason: 'needs-attach' }
48
+ : { state: STATES.SNAPSHOT_REQUESTED, messageType: 'snapshot', clearStaleQueue: true, reason: 'refresh-snapshot' };
49
+ }
50
+
51
+ return {
52
+ STATES,
53
+ shouldShowInitialOverlay,
54
+ activationRestoreDecision,
55
+ activationRequestDecision,
56
+ };
57
+ });
@@ -7,9 +7,18 @@ window.WalleSession = (function() {
7
7
  'use strict';
8
8
 
9
9
  // ---------- state helper ----------
10
+ function initialModelFromSession(s) {
11
+ var meta = (s && s.meta) || {};
12
+ return {
13
+ model: meta.model_registry_id || meta.modelRegistryId || meta.model_id || '',
14
+ provider: meta.model_provider ? providerLabel(meta.model_provider) : '',
15
+ };
16
+ }
17
+
10
18
  function getState(id) {
11
19
  var s = state.sessions.get(id);
12
20
  if (!s) return null;
21
+ var initialModel = initialModelFromSession(s);
13
22
  if (!s.walleState) {
14
23
  s.walleState = {
15
24
  messages: [],
@@ -17,16 +26,28 @@ window.WalleSession = (function() {
17
26
  totalCost: 0,
18
27
  totalTokens: 0,
19
28
  messageCount: 0,
20
- selectedModel: '',
29
+ selectedModel: initialModel.model,
21
30
  selectedModelLabel: '',
22
- selectedModelProvider: '',
23
- _currentAssistant: null
31
+ selectedModelProvider: initialModel.provider,
32
+ _currentAssistant: null,
33
+ _modelHydrated: true,
34
+ _modelManual: false,
35
+ promptNavIdx: -1
24
36
  };
37
+ } else if (!s.walleState._modelHydrated) {
38
+ s.walleState.selectedModel = s.walleState.selectedModel || initialModel.model;
39
+ s.walleState.selectedModelProvider = s.walleState.selectedModelProvider || initialModel.provider;
40
+ s.walleState._modelHydrated = true;
41
+ } else if (initialModel.model && !s.walleState.selectedModel && !s.walleState._modelManual) {
42
+ s.walleState.selectedModel = initialModel.model;
43
+ s.walleState.selectedModelProvider = s.walleState.selectedModelProvider || initialModel.provider;
25
44
  }
45
+ if (typeof s.walleState.promptNavIdx !== 'number') s.walleState.promptNavIdx = -1;
26
46
  return s.walleState;
27
47
  }
28
48
 
29
49
  var _walleModelRegistryCache = null;
50
+ var _walleModelRegistryLoaded = false;
30
51
  var _walleModelRegistryPromise = null;
31
52
  var _walleModelPicker = null;
32
53
 
@@ -163,17 +184,31 @@ window.WalleSession = (function() {
163
184
  var toolbarModelBtn = createModelPickerButton(id, 'toolbar');
164
185
  toolbar.appendChild(toolbarModelBtn);
165
186
 
166
- // Message count badge (like prompt nav badge)
187
+ // Prompt navigation for user messages (shares terminal prompt-nav styling)
167
188
  var msgNav = document.createElement('div');
168
- msgNav.className = 'prompt-nav';
189
+ msgNav.className = 'prompt-nav walle-prompt-nav';
190
+ msgNav.id = 'walle-prompt-nav-' + id;
169
191
  msgNav.style.position = 'relative';
192
+ var prevBtn = document.createElement('button');
193
+ prevBtn.className = 'prompt-nav-btn';
194
+ prevBtn.dataset.dir = 'prev';
195
+ prevBtn.title = 'Previous prompt (Alt+\u2191)';
196
+ prevBtn.innerHTML = '▲';
197
+ prevBtn.onclick = function() { promptNavGo(id, -1); };
198
+ msgNav.appendChild(prevBtn);
170
199
  var msgBadge = document.createElement('span');
171
200
  msgBadge.className = 'prompt-nav-badge';
172
201
  msgBadge.id = 'walle-msg-badge-' + id;
173
- msgBadge.title = 'Message count';
174
- var ws0 = getState(id);
175
- msgBadge.textContent = '\u26A0 ' + (ws0 ? ws0.messageCount : 0) + ' messages';
202
+ msgBadge.title = 'Click to see all prompts';
203
+ msgBadge.onclick = function() { promptNavToggleList(id); };
176
204
  msgNav.appendChild(msgBadge);
205
+ var nextBtn = document.createElement('button');
206
+ nextBtn.className = 'prompt-nav-btn';
207
+ nextBtn.dataset.dir = 'next';
208
+ nextBtn.title = 'Next prompt (Alt+\u2193)';
209
+ nextBtn.innerHTML = '▼';
210
+ nextBtn.onclick = function() { promptNavGo(id, 1); };
211
+ msgNav.appendChild(nextBtn);
177
212
  toolbar.appendChild(msgNav);
178
213
 
179
214
  // Review action button (rightmost)
@@ -260,6 +295,8 @@ window.WalleSession = (function() {
260
295
  for (var i = 0; i < ws.messages.length; i++) {
261
296
  renderMessage(messagesArea, ws.messages[i]);
262
297
  }
298
+ syncPromptMessageDomIndices(id);
299
+ updatePromptNav(id);
263
300
  scrollToBottom(messagesArea);
264
301
 
265
302
  loadWalleModels().then(function() { syncWalleModelButtons(id); }).catch(function() {});
@@ -269,6 +306,7 @@ window.WalleSession = (function() {
269
306
  function renderMessage(container, msg) {
270
307
  var el = document.createElement('div');
271
308
  el.className = 'walle-msg';
309
+ el.dataset.walleRole = msg.role || '';
272
310
 
273
311
  var avatar = document.createElement('div');
274
312
  avatar.className = 'walle-msg-avatar ' + (msg.role === 'user' ? 'user' : 'assistant');
@@ -464,6 +502,7 @@ window.WalleSession = (function() {
464
502
 
465
503
  var ws = getState(id);
466
504
  if (!ws) return;
505
+ if (ws.isGenerating) return;
467
506
 
468
507
  var msg = { role: 'user', content: text, timestamp: Date.now() };
469
508
  ws.messages.push(msg);
@@ -472,12 +511,18 @@ window.WalleSession = (function() {
472
511
  var messagesArea = document.getElementById('walle-messages-' + id);
473
512
  if (messagesArea) {
474
513
  renderMessage(messagesArea, msg);
514
+ syncPromptMessageDomIndices(id);
515
+ updatePromptNav(id);
475
516
  scrollToBottom(messagesArea);
476
517
  }
477
518
 
478
- // Send via WebSocket (include model if user selected one)
519
+ // Send via WebSocket (include model/provider when the session is pinned)
479
520
  var model = ws.selectedModel || '';
480
- send({ type: 'walle-message', id: id, text: text, model: model, contextSessionId: resolveContextSessionId(id) });
521
+ var modelItem = findModelItem(model);
522
+ var session = state.sessions.get(id);
523
+ var meta = (session && session.meta) || {};
524
+ var provider = modelItem ? modelItem.provider : (model && model === meta.model_id ? (meta.model_provider || '') : '');
525
+ send({ type: 'walle-message', id: id, text: text, model: model, provider: provider, contextSessionId: resolveContextSessionId(id) });
481
526
 
482
527
  // Clear input
483
528
  textarea.value = '';
@@ -600,6 +645,8 @@ window.WalleSession = (function() {
600
645
  ws.isGenerating = false;
601
646
 
602
647
  renderMessage(messagesArea, assistantMsg);
648
+ syncPromptMessageDomIndices(id);
649
+ updatePromptNav(id);
603
650
  scrollToBottom(messagesArea);
604
651
  updateSendButton(id, false);
605
652
  updateHeaderStats(id);
@@ -611,6 +658,12 @@ window.WalleSession = (function() {
611
658
  var ws = getState(id);
612
659
  if (!ws) return;
613
660
 
661
+ var session = state.sessions.get(id);
662
+ if (session) {
663
+ session.needsAttach = false;
664
+ session._walleHistoryRequested = true;
665
+ }
666
+
614
667
  ws.messages = [];
615
668
  ws.messageCount = 0;
616
669
  ws.totalCost = 0;
@@ -635,6 +688,7 @@ window.WalleSession = (function() {
635
688
  if (document.getElementById('walle-session-' + id)) {
636
689
  renderSession(id);
637
690
  }
691
+ updatePromptNav(id);
638
692
  }
639
693
 
640
694
  // ---------- handleError ----------
@@ -736,18 +790,31 @@ window.WalleSession = (function() {
736
790
  }
737
791
 
738
792
  // ---------- model picker ----------
793
+ function invalidateWalleModelCache() {
794
+ _walleModelRegistryCache = null;
795
+ _walleModelRegistryLoaded = false;
796
+ }
797
+
739
798
  function loadWalleModels() {
740
- if (_walleModelRegistryCache) return Promise.resolve(_walleModelRegistryCache);
799
+ if (_walleModelRegistryLoaded && Array.isArray(_walleModelRegistryCache)) {
800
+ return Promise.resolve(_walleModelRegistryCache);
801
+ }
741
802
  if (_walleModelRegistryPromise) return _walleModelRegistryPromise;
742
- _walleModelRegistryPromise = fetch('/api/models/registry')
743
- .then(function(r) { return r.json(); })
803
+ _walleModelRegistryPromise = fetch('/api/models/registry', { cache: 'no-store' })
804
+ .then(function(r) {
805
+ if (!r.ok) throw new Error('HTTP ' + r.status);
806
+ return r.json();
807
+ })
744
808
  .then(function(data) {
745
- _walleModelRegistryCache = normalizeWalleModels(data.models || data || []);
809
+ if (data && data.error) throw new Error(data.error);
810
+ var rows = Array.isArray(data) ? data : (Array.isArray(data?.models) ? data.models : []);
811
+ _walleModelRegistryCache = normalizeWalleModels(rows);
812
+ _walleModelRegistryLoaded = true;
746
813
  return _walleModelRegistryCache;
747
814
  })
748
815
  .catch(function() {
749
- _walleModelRegistryCache = [];
750
- return _walleModelRegistryCache;
816
+ invalidateWalleModelCache();
817
+ return [];
751
818
  })
752
819
  .finally(function() {
753
820
  _walleModelRegistryPromise = null;
@@ -898,8 +965,10 @@ window.WalleSession = (function() {
898
965
 
899
966
  function findModelItem(modelId) {
900
967
  if (!_walleModelRegistryCache || !modelId) return null;
968
+ var id = String(modelId);
901
969
  for (var i = 0; i < _walleModelRegistryCache.length; i++) {
902
- if (_walleModelRegistryCache[i].id === modelId) return _walleModelRegistryCache[i];
970
+ var item = _walleModelRegistryCache[i];
971
+ if (item.id === id || item.modelId === id) return item;
903
972
  }
904
973
  return null;
905
974
  }
@@ -910,6 +979,7 @@ window.WalleSession = (function() {
910
979
  ws.selectedModel = item ? item.id : '';
911
980
  ws.selectedModelLabel = item ? item.baseLabel : '';
912
981
  ws.selectedModelProvider = item ? item.providerLabel : '';
982
+ ws._modelManual = true;
913
983
  syncWalleModelButtons(id);
914
984
  closeModelPicker();
915
985
  }
@@ -962,7 +1032,7 @@ window.WalleSession = (function() {
962
1032
  popover.appendChild(footer);
963
1033
 
964
1034
  var showLegacy = false;
965
- var models = _walleModelRegistryCache || [];
1035
+ var models = _walleModelRegistryLoaded ? (_walleModelRegistryCache || []) : [];
966
1036
 
967
1037
  function makeSection(titleText) {
968
1038
  var title = document.createElement('div');
@@ -1026,7 +1096,7 @@ window.WalleSession = (function() {
1026
1096
 
1027
1097
  function renderList() {
1028
1098
  while (body.firstChild) body.removeChild(body.firstChild);
1029
- if (!models.length && !_walleModelRegistryCache) {
1099
+ if (!models.length && !_walleModelRegistryLoaded) {
1030
1100
  renderLoading();
1031
1101
  return;
1032
1102
  }
@@ -1090,7 +1160,7 @@ window.WalleSession = (function() {
1090
1160
  }
1091
1161
  });
1092
1162
 
1093
- if (_walleModelRegistryCache) {
1163
+ if (_walleModelRegistryLoaded) {
1094
1164
  renderList();
1095
1165
  } else {
1096
1166
  renderLoading();
@@ -1143,6 +1213,128 @@ window.WalleSession = (function() {
1143
1213
  });
1144
1214
  }
1145
1215
 
1216
+ // ---------- prompt navigation ----------
1217
+ function getPromptEntries(ws) {
1218
+ var prompts = [];
1219
+ var messages = (ws && ws.messages) || [];
1220
+ for (var i = 0; i < messages.length; i++) {
1221
+ var m = messages[i];
1222
+ if (!m || m.role !== 'user') continue;
1223
+ prompts.push({ messageIndex: i, content: m.content || '', timestamp: m.timestamp || 0 });
1224
+ }
1225
+ return prompts;
1226
+ }
1227
+
1228
+ function promptPreview(text) {
1229
+ if (typeof _extractPromptPreview === 'function') {
1230
+ var extracted = _extractPromptPreview(text);
1231
+ if (extracted) return extracted;
1232
+ }
1233
+ var line = String(text || '').split('\n').map(function(s) { return s.trim(); }).filter(Boolean)[0] || '';
1234
+ return line || '(empty prompt)';
1235
+ }
1236
+
1237
+ function syncPromptMessageDomIndices(id) {
1238
+ var area = document.getElementById('walle-messages-' + id);
1239
+ if (!area) return;
1240
+ var nodes = area.querySelectorAll('.walle-msg[data-walle-role="user"]');
1241
+ for (var i = 0; i < nodes.length; i++) {
1242
+ nodes[i].dataset.wallePromptIdx = String(i);
1243
+ }
1244
+ }
1245
+
1246
+ function updatePromptNav(id) {
1247
+ var ws = getState(id);
1248
+ if (!ws) return;
1249
+ var nav = document.getElementById('walle-prompt-nav-' + id);
1250
+ if (!nav) return;
1251
+ var prompts = getPromptEntries(ws);
1252
+ var total = prompts.length;
1253
+ if (ws.promptNavIdx >= total) ws.promptNavIdx = -1;
1254
+ var badge = nav.querySelector('.prompt-nav-badge');
1255
+ var prevBtn = nav.querySelector('[data-dir="prev"]');
1256
+ var nextBtn = nav.querySelector('[data-dir="next"]');
1257
+ if (badge) {
1258
+ badge.textContent = total === 0
1259
+ ? '0 prompts'
1260
+ : (ws.promptNavIdx >= 0 ? (ws.promptNavIdx + 1) + '/' + total : total + (total === 1 ? ' prompt' : ' prompts'));
1261
+ }
1262
+ if (prevBtn) prevBtn.disabled = total === 0;
1263
+ if (nextBtn) nextBtn.disabled = total === 0;
1264
+ }
1265
+
1266
+ function jumpToPrompt(id, idx) {
1267
+ var ws = getState(id);
1268
+ if (!ws) return false;
1269
+ var prompts = getPromptEntries(ws);
1270
+ if (idx < 0 || idx >= prompts.length) return false;
1271
+ syncPromptMessageDomIndices(id);
1272
+ var area = document.getElementById('walle-messages-' + id);
1273
+ if (!area) return false;
1274
+ var target = area.querySelector('.walle-msg[data-walle-prompt-idx="' + idx + '"]');
1275
+ if (!target) return false;
1276
+ ws.promptNavIdx = idx;
1277
+ var top = Math.max(0, target.offsetTop - area.offsetTop - 8);
1278
+ area.scrollTop = top;
1279
+ updatePromptNav(id);
1280
+ return true;
1281
+ }
1282
+
1283
+ function promptNavGo(id, dir) {
1284
+ var ws = getState(id);
1285
+ if (!ws) return;
1286
+ var total = getPromptEntries(ws).length;
1287
+ if (total === 0) return;
1288
+ var idx = ws.promptNavIdx;
1289
+ var next = idx < 0 ? (dir < 0 ? total - 1 : 0) : idx + dir;
1290
+ if (next < 0 || next >= total) return;
1291
+ jumpToPrompt(id, next);
1292
+ }
1293
+
1294
+ function promptNavToggleList(id) {
1295
+ var ws = getState(id);
1296
+ if (!ws) return;
1297
+ var nav = document.getElementById('walle-prompt-nav-' + id);
1298
+ if (!nav) return;
1299
+ var existing = nav.querySelector('.prompt-nav-list');
1300
+ if (existing) { existing.remove(); return; }
1301
+ var prompts = getPromptEntries(ws);
1302
+ if (prompts.length === 0) return;
1303
+
1304
+ var list = document.createElement('div');
1305
+ list.className = 'prompt-nav-list';
1306
+ list.addEventListener('wheel', function(e) { e.stopPropagation(); }, { passive: false });
1307
+ var seen = {};
1308
+ for (var i = prompts.length - 1; i >= 0; i--) {
1309
+ var text = promptPreview(prompts[i].content);
1310
+ var fullText = text;
1311
+ if (text.length > 80) text = text.slice(0, 80) + '\u2026';
1312
+ if (seen[text]) continue;
1313
+ seen[text] = true;
1314
+ var item = document.createElement('div');
1315
+ item.className = 'prompt-nav-list-item' + (i === ws.promptNavIdx ? ' current' : '');
1316
+ item.textContent = text;
1317
+ item.title = fullText;
1318
+ item.onclick = (function(idx) { return function() {
1319
+ if (jumpToPrompt(id, idx)) list.remove();
1320
+ }; })(i);
1321
+ list.appendChild(item);
1322
+ }
1323
+ nav.appendChild(list);
1324
+
1325
+ setTimeout(function() {
1326
+ function close(e) {
1327
+ if (!list.contains(e.target)) {
1328
+ list.remove();
1329
+ document.removeEventListener('click', close);
1330
+ document.removeEventListener('mousedown', close);
1331
+ }
1332
+ }
1333
+ document.addEventListener('click', close);
1334
+ document.addEventListener('mousedown', close);
1335
+ }, 0);
1336
+ }
1337
+
1146
1338
  // ---------- helpers ----------
1147
1339
  function scrollToBottom(el) {
1148
1340
  if (el) {
@@ -1232,11 +1424,22 @@ window.WalleSession = (function() {
1232
1424
  costEl.textContent = '$' + ws.totalCost.toFixed(2);
1233
1425
  }
1234
1426
 
1235
- // Update toolbar message count badge
1236
- var msgBadge = document.getElementById('walle-msg-badge-' + id);
1237
- if (msgBadge) {
1238
- msgBadge.textContent = '\u26A0 ' + ws.messageCount + ' messages';
1239
- }
1427
+ updatePromptNav(id);
1428
+ }
1429
+
1430
+ function handleModel(msg) {
1431
+ var id = msg.id;
1432
+ var s = state.sessions.get(id);
1433
+ if (!s) return;
1434
+ s.meta = s.meta || {};
1435
+ if (msg.model_id) s.meta.model_id = msg.model_id;
1436
+ if (msg.model_provider) s.meta.model_provider = msg.model_provider;
1437
+ var ws = getState(id);
1438
+ if (!ws || ws._modelManual || !msg.model_id) return;
1439
+ ws.selectedModel = msg.model_id;
1440
+ ws.selectedModelLabel = '';
1441
+ ws.selectedModelProvider = msg.model_provider ? providerLabel(msg.model_provider) : ws.selectedModelProvider;
1442
+ syncWalleModelButtons(id);
1240
1443
  }
1241
1444
 
1242
1445
  // ---------- public API ----------
@@ -1248,6 +1451,11 @@ window.WalleSession = (function() {
1248
1451
  handleResponse: handleResponse,
1249
1452
  handleHistory: handleHistory,
1250
1453
  handleError: handleError,
1251
- populateWalleModelSelector: populateWalleModelSelector
1454
+ handleModel: handleModel,
1455
+ promptNavGo: promptNavGo,
1456
+ promptNavToggleList: promptNavToggleList,
1457
+ updatePromptNav: updatePromptNav,
1458
+ populateWalleModelSelector: populateWalleModelSelector,
1459
+ invalidateModelCache: invalidateWalleModelCache
1252
1460
  };
1253
1461
  })();
@@ -2045,6 +2045,7 @@ function renderChatUI() {
2045
2045
  html += '</div>'; // container
2046
2046
 
2047
2047
  safeSetHtml(body, html);
2048
+ renderServiceAlerts(_serviceAlerts);
2048
2049
 
2049
2050
  // Update elapsed timer if processing
2050
2051
  if (chatThinkingState.isProcessing && chatThinkingState.startTime) {
@@ -6095,10 +6096,13 @@ checkProviderStatus();
6095
6096
  // ---------------------------------------------------------------------------
6096
6097
 
6097
6098
  var _alertsPollTimer = null;
6099
+ var _serviceAlerts = [];
6100
+ var _updatePromptShownVersions = {};
6098
6101
 
6099
6102
  function checkServiceAlerts() {
6100
6103
  api('/alerts').then(function(d) {
6101
6104
  var alerts = d.alerts || [];
6105
+ _serviceAlerts = alerts;
6102
6106
  var providerIssue = null;
6103
6107
  for (var i = 0; i < alerts.length; i++) {
6104
6108
  var issue = _normalizeProviderIssue(alerts[i]);
@@ -6109,6 +6113,7 @@ function checkServiceAlerts() {
6109
6113
  if (currentView === 'chat') renderChatUI();
6110
6114
  }
6111
6115
  renderServiceAlerts(alerts);
6116
+ maybeShowUpdateWizard(alerts);
6112
6117
  clearTimeout(_alertsPollTimer);
6113
6118
  _alertsPollTimer = setTimeout(checkServiceAlerts, 60000);
6114
6119
  }).catch(function() {
@@ -6130,8 +6135,14 @@ function renderServiceAlerts(alerts) {
6130
6135
  var safeId = esc(a.id).replace(/'/g, '&#39;');
6131
6136
  var dismissBtn = '<button class="we-service-alert-dismiss" onclick="WE._dismissAlert(\'' + safeId + '\')" title="Dismiss">&times;</button>';
6132
6137
  var actionBtn = '';
6133
- if (a.action_url && /^(\/|https?:\/\/)/.test(a.action_url)) {
6134
- actionBtn = ' <a href="' + esc(a.action_url) + '" class="we-service-alert-action">Fix</a>';
6138
+ var actionLabel = esc(a.action_label || 'Fix');
6139
+ if (a.action === 'repair_slack_owner') {
6140
+ var actionName = esc(a.action || 'custom').replace(/'/g, '&#39;');
6141
+ actionBtn = ' <button class="we-service-alert-action" onclick="WE._runAlertAction(\'' + safeId + '\', \'' + actionName + '\')">' + actionLabel + '</button>';
6142
+ } else if (a.action === 'show_update_wizard') {
6143
+ actionBtn = ' <button class="we-service-alert-action" onclick="WE._showUpdateWizardFromAlert(\'' + safeId + '\')">' + actionLabel + '</button>';
6144
+ } else if (a.action_url && /^(\/|https?:\/\/)/.test(a.action_url)) {
6145
+ actionBtn = ' <a href="' + esc(a.action_url) + '" class="we-service-alert-action">' + actionLabel + '</a>';
6135
6146
  }
6136
6147
  return '<div class="we-service-alert-item ' + kind + '">'
6137
6148
  + '<span class="we-service-alert-icon">' + icon + '</span>'
@@ -6150,6 +6161,136 @@ function renderServiceAlerts(alerts) {
6150
6161
  }
6151
6162
  }
6152
6163
 
6164
+ function _findUpdateAlert(alerts) {
6165
+ alerts = alerts || _serviceAlerts || [];
6166
+ for (var i = 0; i < alerts.length; i++) {
6167
+ if (alerts[i] && alerts[i].type === 'update_available') return alerts[i];
6168
+ }
6169
+ return null;
6170
+ }
6171
+
6172
+ function maybeShowUpdateWizard(alerts) {
6173
+ var alert = _findUpdateAlert(alerts);
6174
+ if (!alert) return;
6175
+ var version = alert.latest || 'unknown';
6176
+ if (_updatePromptShownVersions[version]) return;
6177
+ var key = 'walle:updatePrompt:' + version;
6178
+ try {
6179
+ if (window.localStorage && localStorage.getItem(key)) return;
6180
+ if (window.localStorage) localStorage.setItem(key, 'shown');
6181
+ } catch {}
6182
+ _updatePromptShownVersions[version] = true;
6183
+ showUpdateWizard(alert, { automatic: true });
6184
+ WE._trackUpgradePrompt('shown');
6185
+ }
6186
+
6187
+ function copyTextToClipboard(text) {
6188
+ if (navigator.clipboard && navigator.clipboard.writeText) {
6189
+ return navigator.clipboard.writeText(text);
6190
+ }
6191
+ var ta = document.createElement('textarea');
6192
+ ta.value = text;
6193
+ ta.style.position = 'fixed';
6194
+ ta.style.left = '-9999px';
6195
+ document.body.appendChild(ta);
6196
+ ta.select();
6197
+ try { document.execCommand('copy'); } finally { ta.remove(); }
6198
+ return Promise.resolve();
6199
+ }
6200
+
6201
+ function showUpdateWizard(alert, opts) {
6202
+ opts = opts || {};
6203
+ var existing = document.getElementById('we-update-wizard');
6204
+ if (existing) existing.remove();
6205
+ var command = 'npx create-walle@latest update';
6206
+ var overlay = document.createElement('div');
6207
+ overlay.id = 'we-update-wizard';
6208
+ overlay.className = 'we-update-wizard-overlay';
6209
+ overlay.innerHTML = ''
6210
+ + '<div class="we-update-wizard" role="dialog" aria-modal="true" aria-labelledby="we-update-title">'
6211
+ + '<button class="we-update-wizard-close" type="button" aria-label="Close">&times;</button>'
6212
+ + '<div class="we-update-wizard-kicker">Update available</div>'
6213
+ + '<h3 id="we-update-title">Update CTM / Wall-E</h3>'
6214
+ + '<p>Version <strong>' + esc(alert.latest || 'latest') + '</strong> is available. You are running <strong>' + esc(alert.current || 'unknown') + '</strong>.</p>'
6215
+ + '<div class="we-update-command">' + esc(command) + '</div>'
6216
+ + '<div class="we-update-wizard-actions">'
6217
+ + '<button class="btn primary we-update-copy" type="button">Copy Command</button>'
6218
+ + '<button class="btn we-update-later" type="button">Later</button>'
6219
+ + '<button class="btn danger we-update-dismiss" type="button">Dismiss</button>'
6220
+ + '</div>'
6221
+ + '</div>';
6222
+ document.body.appendChild(overlay);
6223
+
6224
+ var close = function(action) {
6225
+ overlay.remove();
6226
+ if (action) WE._trackUpgradePrompt(action);
6227
+ };
6228
+ overlay.addEventListener('click', function(e) {
6229
+ if (e.target === overlay) close('later');
6230
+ });
6231
+ overlay.querySelector('.we-update-wizard-close').addEventListener('click', function() { close('later'); });
6232
+ overlay.querySelector('.we-update-later').addEventListener('click', function() { close('later'); });
6233
+ overlay.querySelector('.we-update-dismiss').addEventListener('click', function() {
6234
+ WE._dismissAlert(alert.id || 'version_update');
6235
+ close();
6236
+ });
6237
+ overlay.querySelector('.we-update-copy').addEventListener('click', function(e) {
6238
+ var btn = e.currentTarget;
6239
+ copyTextToClipboard(command).then(function() {
6240
+ btn.textContent = 'Copied';
6241
+ WE._trackUpgradePrompt('copy_command');
6242
+ if (typeof showToast === 'function') showToast('Update command copied', 'var(--green, #9ece6a)', 1800);
6243
+ }).catch(function() {
6244
+ btn.textContent = command;
6245
+ });
6246
+ });
6247
+ }
6248
+
6249
+ WE._showUpdateWizardFromAlert = function(alertId) {
6250
+ var alert = null;
6251
+ for (var i = 0; i < _serviceAlerts.length; i++) {
6252
+ if (String(_serviceAlerts[i].id) === String(alertId)) alert = _serviceAlerts[i];
6253
+ }
6254
+ showUpdateWizard(alert || _findUpdateAlert(_serviceAlerts) || { id: 'version_update' }, { automatic: false });
6255
+ WE._trackUpgradePrompt('shown');
6256
+ };
6257
+
6258
+ WE._trackUpgradePrompt = function(action) {
6259
+ resolveWalleBase().then(function() {
6260
+ var token = window._ctmState?.token || '';
6261
+ fetch(WALLE_BASE + '/api/wall-e/update/prompt-event?token=' + encodeURIComponent(token), {
6262
+ method: 'POST',
6263
+ headers: { 'Content-Type': 'application/json' },
6264
+ body: JSON.stringify({ action: action })
6265
+ }).catch(function() {});
6266
+ }).catch(function() {});
6267
+ };
6268
+
6269
+ WE._runAlertAction = function(alertId, actionName) {
6270
+ if (actionName !== 'repair_slack_owner') return;
6271
+ resolveWalleBase().then(function() {
6272
+ var token = window._ctmState?.token || '';
6273
+ fetch(WALLE_BASE + '/api/wall-e/slack/repair-owner?token=' + encodeURIComponent(token), { method: 'POST' })
6274
+ .then(function(r) { return r.json().then(function(d) { return { ok: r.ok, data: d }; }); })
6275
+ .then(function(result) {
6276
+ if (result.ok && result.data && result.data.ok) {
6277
+ if (typeof showToast === 'function') showToast('Slack sync fixed automatically', 'var(--green, #9ece6a)', 2500);
6278
+ checkServiceAlerts();
6279
+ return;
6280
+ }
6281
+ var msg = (result.data && result.data.error) || 'Could not fix Slack sync automatically';
6282
+ if (result.data && result.data.needsSlackAuth) {
6283
+ if (typeof navTo === 'function') navTo('setup');
6284
+ msg = 'Connect Slack first, then Wall-E can finish setup automatically.';
6285
+ }
6286
+ if (typeof showToast === 'function') showToast(msg, 'var(--red, #f7768e)', 3500);
6287
+ })
6288
+ .catch(function(e) {
6289
+ if (typeof showToast === 'function') showToast(e.message, 'var(--red, #f7768e)', 3500);
6290
+ });
6291
+ });
6292
+ };
6293
+
6153
6294
  WE._dismissAlert = function(alertId) {
6154
6295
  resolveWalleBase().then(function() {
6155
6296
  var token = window._ctmState?.token || '';