@lelouchhe/webagent 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -13,7 +13,7 @@
13
13
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
14
14
  <script src="https://cdn.jsdelivr.net/npm/dompurify@3.3.2/dist/purify.min.js"></script>
15
15
  <script>document.documentElement.setAttribute('data-theme', localStorage.getItem('theme') || 'auto');</script>
16
- <link rel="stylesheet" href="/styles.mmlj9sk2.css">
16
+ <link rel="stylesheet" href="/styles.mmmmfxhu.css">
17
17
  </head>
18
18
  <body>
19
19
 
@@ -40,7 +40,8 @@
40
40
  <button id="send-btn" class="input-btn" title="Send (Enter)">↵</button>
41
41
  <input type="file" id="file-input" accept="image/*" multiple hidden>
42
42
  </div>
43
+ <div id="status-bar"></div>
43
44
 
44
- <script type="module" src="/js/app.mmlj9sk2.js"></script>
45
+ <script type="module" src="/js/app.mmmmfxhu.js"></script>
45
46
  </body>
46
47
  </html>
@@ -1,13 +1,13 @@
1
1
  // Boot entry point — imports all modules and starts the app
2
2
 
3
- import './render.mmlj9sk2.js'; // theme, click-to-collapse listeners
4
- import './commands.mmlj9sk2.js'; // slash menu listeners
5
- import './images.mmlj9sk2.js'; // attach/paste listeners
6
- import './input.mmlj9sk2.js'; // keyboard/send listeners
7
- import { connect } from './connection.mmlj9sk2.js';
8
- import { state, setHashSessionId, resetSessionUI, updateSessionInfo } from './state.mmlj9sk2.js';
9
- import { loadHistory } from './events.mmlj9sk2.js';
10
- import { addSystem, scrollToBottom } from './render.mmlj9sk2.js';
3
+ import './render.mmmmfxhu.js'; // theme, click-to-collapse listeners
4
+ import './commands.mmmmfxhu.js'; // slash menu listeners
5
+ import './images.mmmmfxhu.js'; // attach/paste listeners
6
+ import './input.mmmmfxhu.js'; // keyboard/send listeners
7
+ import { connect } from './connection.mmmmfxhu.js';
8
+ import { state, setHashSessionId, resetSessionUI, updateSessionInfo } from './state.mmmmfxhu.js';
9
+ import { loadHistory } from './events.mmmmfxhu.js';
10
+ import { addSystem, scrollToBottom } from './render.mmmmfxhu.js';
11
11
 
12
12
  connect();
13
13
 
@@ -4,9 +4,9 @@ import {
4
4
  state, dom, setBusy, resetSessionUI, requestNewSession, sendCancel,
5
5
  getConfigOption, getConfigValue, setHashSessionId, updateSessionInfo,
6
6
  updateNewBtnVisibility,
7
- } from './state.mmlj9sk2.js';
8
- import { addSystem, addMessage, scrollToBottom, escHtml, formatLocalTime } from './render.mmlj9sk2.js';
9
- import { loadHistory } from './events.mmlj9sk2.js';
7
+ } from './state.mmmmfxhu.js';
8
+ import { addSystem, addMessage, scrollToBottom, escHtml, formatLocalTime } from './render.mmmmfxhu.js';
9
+ import { loadHistory } from './events.mmmmfxhu.js';
10
10
 
11
11
  // --- Push notification helpers ---
12
12
 
@@ -1,8 +1,8 @@
1
1
  // WebSocket connection lifecycle
2
2
 
3
- import { state, setBusy, getHashSessionId, requestNewSession, resetSessionUI, setConnectionStatus, clearCancelTimer } from './state.mmlj9sk2.js';
4
- import { addSystem, finishThinking, finishAssistant, finishBash, scrollToBottom } from './render.mmlj9sk2.js';
5
- import { handleEvent, loadHistory, loadNewEvents, retryUnconfirmedPermissions } from './events.mmlj9sk2.js';
3
+ import { state, setBusy, getHashSessionId, requestNewSession, resetSessionUI, setConnectionStatus, clearCancelTimer } from './state.mmmmfxhu.js';
4
+ import { addSystem, finishThinking, finishAssistant, finishBash, scrollToBottom } from './render.mmmmfxhu.js';
5
+ import { handleEvent, loadHistory, loadNewEvents, retryUnconfirmedPermissions } from './events.mmmmfxhu.js';
6
6
 
7
7
  export function connect() {
8
8
  const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
@@ -2,14 +2,14 @@
2
2
 
3
3
  import {
4
4
  state, dom, setBusy, setConfigValue, getConfigOption, updateConfigOptions,
5
- updateModeUI, resetSessionUI, requestNewSession, setHashSessionId, updateSessionInfo,
5
+ updateModeUI, updateStatusBar, resetSessionUI, requestNewSession, setHashSessionId, updateSessionInfo,
6
6
  setConnectionStatus, clearCancelTimer,
7
- } from './state.mmlj9sk2.js';
7
+ } from './state.mmmmfxhu.js';
8
8
  import {
9
9
  addMessage, addSystem, finishAssistant, finishThinking, hideWaiting,
10
10
  scrollToBottom, renderMd, escHtml, renderPatchDiff, addBashBlock, finishBash, appendMessageElement,
11
11
  formatLocalTime,
12
- } from './render.mmlj9sk2.js';
12
+ } from './render.mmmmfxhu.js';
13
13
 
14
14
  const NOTIFY_TIP_KEY = 'webagent_notify_tip_shown';
15
15
  const NOTIFY_TIP_DENIED_KEY = 'webagent_notify_tip_denied_shown';
@@ -377,6 +377,7 @@ export function handleEvent(msg) {
377
377
  dom.sendBtn.disabled = false;
378
378
  dom.input.placeholder = '';
379
379
  setBusy(Boolean(msg.busyKind));
380
+ state.newTurnStarted = false;
380
381
  if (msg.busyKind === 'bash') {
381
382
  const pendingBashEl = document.getElementById('bash-replay-pending');
382
383
  if (pendingBashEl) {
@@ -390,9 +391,16 @@ export function handleEvent(msg) {
390
391
  if (dom.messages.children.length === 0) {
391
392
  addSystem(`Session created: ${state.sessionTitle || msg.sessionId.slice(0, 8) + '…'}`);
392
393
  }
394
+ updateStatusBar();
393
395
  break;
394
396
 
395
397
  case 'user_message': {
398
+ // A new turn is starting (from another client's broadcast).
399
+ // Finalise any in-progress streaming from the previous turn so
400
+ // subsequent message_chunks create a fresh element BELOW this bubble.
401
+ finishThinking();
402
+ finishAssistant();
403
+ state.newTurnStarted = true;
396
404
  state.turnEnded = false;
397
405
  if (msg.sessionId === state.sessionId) {
398
406
  const el = addMessage('user', msg.text);
@@ -613,8 +621,18 @@ export function handleEvent(msg) {
613
621
  break;
614
622
  }
615
623
 
616
- case 'prompt_done':
624
+ case 'prompt_done': {
617
625
  clearCancelTimer();
626
+ if (msg.stopReason === 'cancelled' && state.newTurnStarted) {
627
+ // This prompt_done belongs to a previous turn — a new turn has already
628
+ // started (signaled by user_message from another client). Don't clobber
629
+ // the current turn's pending state; just tidy up leftover streaming elements.
630
+ state.newTurnStarted = false;
631
+ finishThinking();
632
+ finishAssistant();
633
+ break;
634
+ }
635
+ state.newTurnStarted = false;
618
636
  if (msg.stopReason === 'cancelled') {
619
637
  cancelPendingTurnUI();
620
638
  }
@@ -622,6 +640,7 @@ export function handleEvent(msg) {
622
640
  state.pendingPromptDone = true;
623
641
  finishPromptIfIdle();
624
642
  break;
643
+ }
625
644
 
626
645
  case 'session_deleted':
627
646
  if (msg.sessionId === state.sessionId) {
@@ -645,6 +664,7 @@ export function handleEvent(msg) {
645
664
  const valueName = opt?.options.find(o => o.value === msg.value)?.name || msg.value;
646
665
  addSystem(`ok: ${label}: ${valueName}`);
647
666
  if (msg.configId === 'mode') updateModeUI();
667
+ updateStatusBar();
648
668
  break;
649
669
  }
650
670
 
@@ -1,6 +1,6 @@
1
1
  // Image attach, preview, and paste handling
2
2
 
3
- import { state, dom } from './state.mmlj9sk2.js';
3
+ import { state, dom } from './state.mmmmfxhu.js';
4
4
 
5
5
  function readFileAsBase64(file) {
6
6
  return new Promise((resolve) => {
@@ -3,10 +3,10 @@
3
3
  import {
4
4
  state, dom, setBusy, sendCancel,
5
5
  getConfigOption, getConfigValue, updateNewBtnVisibility,
6
- } from './state.mmlj9sk2.js';
7
- import { addMessage, addSystem, addBashBlock, showWaiting } from './render.mmlj9sk2.js';
8
- import { handleSlashCommand, hideSlashMenu, handleSlashMenuKey, updateSlashMenu } from './commands.mmlj9sk2.js';
9
- import { renderAttachPreview } from './images.mmlj9sk2.js';
6
+ } from './state.mmmmfxhu.js';
7
+ import { addMessage, addSystem, addBashBlock, showWaiting } from './render.mmmmfxhu.js';
8
+ import { handleSlashCommand, hideSlashMenu, handleSlashMenuKey, updateSlashMenu } from './commands.mmmmfxhu.js';
9
+ import { renderAttachPreview } from './images.mmmmfxhu.js';
10
10
 
11
11
  function wsReady() {
12
12
  return state.ws && state.ws.readyState === 1;
@@ -1,6 +1,6 @@
1
1
  // Rendering functions, theme, markdown, bash UI
2
2
 
3
- import { dom, state } from './state.mmlj9sk2.js';
3
+ import { dom, state } from './state.mmmmfxhu.js';
4
4
 
5
5
  // --- Markdown ---
6
6
  marked.setOptions({ breaks: true, gfm: true });
@@ -16,6 +16,7 @@ export const dom = {
16
16
  themeBtn: $('#theme-btn'),
17
17
  slashMenu: $('#slash-menu'),
18
18
  inputArea: $('#input-area'),
19
+ statusBar: $('#status-bar'),
19
20
  };
20
21
 
21
22
  export const state = {
@@ -37,6 +38,7 @@ export const state = {
37
38
  pendingPermissionRequestIds: new Set(),
38
39
  pendingPromptDone: false,
39
40
  turnEnded: false,
41
+ newTurnStarted: false,
40
42
  cancelTimeout: 10_000,
41
43
  _cancelTimerId: null,
42
44
  _onCancelTimeout: null,
@@ -71,6 +73,7 @@ export function setConfigValue(id, value) {
71
73
  export function updateConfigOptions(newOptions) {
72
74
  state.configOptions = newOptions;
73
75
  updateModeUI();
76
+ updateStatusBar();
74
77
  }
75
78
 
76
79
  export function updateModeUI() {
@@ -80,6 +83,26 @@ export function updateModeUI() {
80
83
  else if (modeValue.includes('#autopilot')) dom.inputArea.classList.add('autopilot-mode');
81
84
  }
82
85
 
86
+ export function updateStatusBar() {
87
+ if (!dom.statusBar) return;
88
+ const model = getConfigValue('model');
89
+ const cwd = state.sessionCwd || '';
90
+ dom.statusBar.textContent = '';
91
+ const parts = [];
92
+ if (model) parts.push(model);
93
+ if (cwd) {
94
+ if (model) {
95
+ dom.statusBar.appendChild(document.createTextNode(model + ' \u00b7 '));
96
+ }
97
+ const cwdSpan = document.createElement('span');
98
+ cwdSpan.className = 'status-cwd';
99
+ cwdSpan.textContent = cwd;
100
+ dom.statusBar.appendChild(cwdSpan);
101
+ } else if (model) {
102
+ dom.statusBar.textContent = model;
103
+ }
104
+ }
105
+
83
106
  export function setBusy(on) {
84
107
  state.busy = on;
85
108
  if (on) {
@@ -116,6 +139,7 @@ export function resetSessionUI() {
116
139
  state.unconfirmedPermissions.clear();
117
140
  state.pendingPromptDone = false;
118
141
  state.turnEnded = false;
142
+ state.newTurnStarted = false;
119
143
  state._cancelTimerId = null;
120
144
  state.lastEventSeq = 0;
121
145
  state.replayInProgress = false;
@@ -126,6 +150,7 @@ export function resetSessionUI() {
126
150
  dom.sendBtn.disabled = false;
127
151
  dom.input.placeholder = '';
128
152
  setBusy(false);
153
+ if (dom.statusBar) dom.statusBar.textContent = '';
129
154
  }
130
155
 
131
156
  export function updateNewBtnVisibility() {
@@ -355,7 +355,7 @@ body {
355
355
  /* Input */
356
356
  #input-area {
357
357
  position: relative;
358
- padding: 8px 16px 20px;
358
+ padding: 8px 16px 4px;
359
359
  background: var(--bg);
360
360
  border-top: 1px solid var(--border);
361
361
  display: flex;
@@ -410,6 +410,25 @@ body {
410
410
  #input-area.autopilot-mode #input-prompt {
411
411
  color: var(--red);
412
412
  }
413
+ #status-bar {
414
+ display: flex;
415
+ padding: 0 16px 10px;
416
+ background: var(--bg);
417
+ color: var(--text-dim);
418
+ font-size: 11px;
419
+ line-height: 1.4;
420
+ white-space: nowrap;
421
+ user-select: none;
422
+ }
423
+ #status-bar::before {
424
+ content: '\200b';
425
+ }
426
+ #status-bar .status-cwd {
427
+ overflow: hidden;
428
+ text-overflow: ellipsis;
429
+ direction: rtl;
430
+ text-align: left;
431
+ }
413
432
  #input-prompt {
414
433
  color: var(--text);
415
434
  font-weight: bold;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lelouchhe/webagent",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "A terminal-style web UI for ACP-compatible agents",
5
5
  "type": "module",
6
6
  "license": "MIT",