aicodeman 1.26.2 → 1.28.0

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 (121) hide show
  1. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  2. package/dist/config/cli-registry/stock.js +13 -2
  3. package/dist/config/cli-registry/stock.js.map +1 -1
  4. package/dist/session-cli-builder.d.ts.map +1 -1
  5. package/dist/session-cli-builder.js +31 -5
  6. package/dist/session-cli-builder.js.map +1 -1
  7. package/dist/session.d.ts.map +1 -1
  8. package/dist/session.js +19 -0
  9. package/dist/session.js.map +1 -1
  10. package/dist/tmux-manager.d.ts +3 -0
  11. package/dist/tmux-manager.d.ts.map +1 -1
  12. package/dist/tmux-manager.js +65 -8
  13. package/dist/tmux-manager.js.map +1 -1
  14. package/dist/types/common.d.ts +2 -0
  15. package/dist/types/common.d.ts.map +1 -1
  16. package/dist/utils/omp-session-resolver.d.ts +14 -0
  17. package/dist/utils/omp-session-resolver.d.ts.map +1 -1
  18. package/dist/utils/omp-session-resolver.js +21 -2
  19. package/dist/utils/omp-session-resolver.js.map +1 -1
  20. package/dist/web/public/admin-ui.js.gz +0 -0
  21. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  22. package/dist/web/public/app.78bf0bf8.js +38 -0
  23. package/dist/web/public/app.78bf0bf8.js.br +0 -0
  24. package/dist/web/public/app.78bf0bf8.js.gz +0 -0
  25. package/dist/web/public/approvals-ui.js.gz +0 -0
  26. package/dist/web/public/{constants.e6d48776.js → constants.9cd8ed69.js} +7 -1
  27. package/dist/web/public/constants.9cd8ed69.js.br +0 -0
  28. package/dist/web/public/constants.9cd8ed69.js.gz +0 -0
  29. package/dist/web/public/cron-ui.js.gz +0 -0
  30. package/dist/web/public/entrance-animations.js.gz +0 -0
  31. package/dist/web/public/home-sessions.js.gz +0 -0
  32. package/dist/web/public/i18n.9db8c4e0.js.gz +0 -0
  33. package/dist/web/public/image-input.cd4b97c4.js.gz +0 -0
  34. package/dist/web/public/index.html +22 -10
  35. package/dist/web/public/index.html.br +0 -0
  36. package/dist/web/public/index.html.gz +0 -0
  37. package/dist/web/public/input-cjk.87f14251.js.gz +0 -0
  38. package/dist/web/public/{keyboard-accessory.daf18302.js → keyboard-accessory.1d0dda9b.js} +182 -13
  39. package/dist/web/public/keyboard-accessory.1d0dda9b.js.br +0 -0
  40. package/dist/web/public/keyboard-accessory.1d0dda9b.js.gz +0 -0
  41. package/dist/web/public/mobile-handlers.2e9c214a.js.gz +0 -0
  42. package/dist/web/public/mobile-overview.js.gz +0 -0
  43. package/dist/web/public/mobile.65176832.css.gz +0 -0
  44. package/dist/web/public/notification-manager.36ea4624.js.gz +0 -0
  45. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  46. package/dist/web/public/{panels-ui.9eb5b235.js → panels-ui.4e9ea307.js} +1 -1
  47. package/dist/web/public/panels-ui.4e9ea307.js.br +0 -0
  48. package/dist/web/public/panels-ui.4e9ea307.js.gz +0 -0
  49. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  50. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  51. package/dist/web/public/readmymind-ui.js.gz +0 -0
  52. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  53. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  54. package/dist/web/public/session-lineage.js.gz +0 -0
  55. package/dist/web/public/session-ui.cd222710.js.gz +0 -0
  56. package/dist/web/public/settings-ui.c175db9a.js +67 -0
  57. package/dist/web/public/settings-ui.c175db9a.js.br +0 -0
  58. package/dist/web/public/settings-ui.c175db9a.js.gz +0 -0
  59. package/dist/web/public/{styles.89069bbc.css → styles.0d41361c.css} +1 -1
  60. package/dist/web/public/styles.0d41361c.css.br +0 -0
  61. package/dist/web/public/styles.0d41361c.css.gz +0 -0
  62. package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
  63. package/dist/web/public/sw.js.gz +0 -0
  64. package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
  65. package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js +1 -0
  66. package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.br +0 -0
  67. package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.gz +0 -0
  68. package/dist/web/public/terminal-ui.89e4eb1a.js +2 -0
  69. package/dist/web/public/terminal-ui.89e4eb1a.js.br +0 -0
  70. package/dist/web/public/terminal-ui.89e4eb1a.js.gz +0 -0
  71. package/dist/web/public/ultracode-panel.js.gz +0 -0
  72. package/dist/web/public/ultracode-windows.js.gz +0 -0
  73. package/dist/web/public/upload.html.gz +0 -0
  74. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  75. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  76. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  77. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  78. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  79. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  80. package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
  81. package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
  82. package/dist/web/public/vendor/xterm.css.gz +0 -0
  83. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  84. package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
  85. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  86. package/dist/web/public/webview-tabs.js +199 -9
  87. package/dist/web/public/webview-tabs.js.br +0 -0
  88. package/dist/web/public/webview-tabs.js.gz +0 -0
  89. package/dist/web/response-viewer-transcript.d.ts +20 -0
  90. package/dist/web/response-viewer-transcript.d.ts.map +1 -1
  91. package/dist/web/response-viewer-transcript.js +27 -0
  92. package/dist/web/response-viewer-transcript.js.map +1 -1
  93. package/dist/web/routes/file-routes.d.ts.map +1 -1
  94. package/dist/web/routes/file-routes.js +11 -3
  95. package/dist/web/routes/file-routes.js.map +1 -1
  96. package/dist/web/routes/session-routes.d.ts.map +1 -1
  97. package/dist/web/routes/session-routes.js +16 -4
  98. package/dist/web/routes/session-routes.js.map +1 -1
  99. package/dist/web/schemas.d.ts +4 -0
  100. package/dist/web/schemas.d.ts.map +1 -1
  101. package/dist/web/schemas.js +9 -0
  102. package/dist/web/schemas.js.map +1 -1
  103. package/package.json +1 -1
  104. package/skills/codeman/reference/endpoints.md +1 -0
  105. package/dist/web/public/app.c4dbfcfb.js +0 -38
  106. package/dist/web/public/app.c4dbfcfb.js.br +0 -0
  107. package/dist/web/public/app.c4dbfcfb.js.gz +0 -0
  108. package/dist/web/public/constants.e6d48776.js.br +0 -0
  109. package/dist/web/public/constants.e6d48776.js.gz +0 -0
  110. package/dist/web/public/keyboard-accessory.daf18302.js.br +0 -0
  111. package/dist/web/public/keyboard-accessory.daf18302.js.gz +0 -0
  112. package/dist/web/public/panels-ui.9eb5b235.js.br +0 -0
  113. package/dist/web/public/panels-ui.9eb5b235.js.gz +0 -0
  114. package/dist/web/public/settings-ui.554f9cdd.js +0 -67
  115. package/dist/web/public/settings-ui.554f9cdd.js.br +0 -0
  116. package/dist/web/public/settings-ui.554f9cdd.js.gz +0 -0
  117. package/dist/web/public/styles.89069bbc.css.br +0 -0
  118. package/dist/web/public/styles.89069bbc.css.gz +0 -0
  119. package/dist/web/public/terminal-ui.1547ce5e.js +0 -2
  120. package/dist/web/public/terminal-ui.1547ce5e.js.br +0 -0
  121. package/dist/web/public/terminal-ui.1547ce5e.js.gz +0 -0
@@ -47,11 +47,24 @@
47
47
  // the picker browses Home and every configured root, so wanting dotfiles in a
48
48
  // project does not imply wanting them in ~.
49
49
  const PATH_PICKER_SHOW_HIDDEN_KEY = 'codeman:pathPickerShowHidden';
50
+ // Per-device like the hidden toggle: how you scan a folder is a habit of the
51
+ // hand, not of the workspace.
52
+ const PATH_PICKER_SORT_KEY = 'codeman:pathPickerSort';
53
+ const PATH_PICKER_SORT_MODES = [
54
+ { value: 'name-asc', label: 'Name A→Z' },
55
+ { value: 'name-desc', label: 'Name Z→A' },
56
+ { value: 'mtime-desc', label: 'Newest first' },
57
+ { value: 'mtime-asc', label: 'Oldest first' },
58
+ ];
59
+ const PATH_PICKER_DEFAULT_SORT = 'name-asc';
50
60
 
51
61
  const PathPicker = {
52
62
  overlay: null,
53
63
  _options: null,
54
64
  _selectedPath: '',
65
+ _currentPath: '',
66
+ _entries: [],
67
+ _truncated: false,
55
68
  _previousFocus: null,
56
69
  _keydownHandler: null,
57
70
  _loadSequence: 0,
@@ -59,6 +72,7 @@ const PathPicker = {
59
72
  _previewRequestSequence: 0,
60
73
  _previewPreviousFocus: null,
61
74
  _showHidden: false,
75
+ _sortMode: PATH_PICKER_DEFAULT_SORT,
62
76
 
63
77
  /**
64
78
  * Open the lazy filesystem browser.
@@ -69,7 +83,10 @@ const PathPicker = {
69
83
  this.close(false);
70
84
  this._options = options;
71
85
  this._selectedPath = '';
86
+ this._currentPath = '';
87
+ this._entries = [];
72
88
  this._showHidden = this._loadShowHidden();
89
+ this._sortMode = this._loadSortMode();
73
90
  this._previousFocus = document.activeElement;
74
91
  this._previousFocus?.blur?.();
75
92
 
@@ -90,11 +107,19 @@ const PathPicker = {
90
107
  </div>
91
108
  <div class="path-picker-nav">
92
109
  <button type="button" class="path-picker-up" title="Parent folder" aria-label="Parent folder">&#x2191;</button>
93
- <div class="path-picker-current" title="Current folder"></div>
110
+ <form class="path-picker-jump" title="Current folder — edit and press Enter to jump">
111
+ <input type="text" class="path-picker-current" aria-label="Current folder path" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" enterkeyhint="go">
112
+ <button type="submit" class="path-picker-go" title="Go to this path" aria-label="Go to this path">Go</button>
113
+ </form>
94
114
  <button type="button" class="path-picker-hidden" title="Show hidden files and folders" aria-label="Show hidden files and folders" aria-pressed="false">.*</button>
95
115
  <button type="button" class="path-picker-refresh" title="Refresh" aria-label="Refresh">&#x21BB;</button>
96
116
  </div>
97
- <div class="path-picker-status" aria-live="polite">Loading...</div>
117
+ <div class="path-picker-toolbar">
118
+ <div class="path-picker-status" aria-live="polite">Loading...</div>
119
+ <label class="path-picker-sort-label">Sort
120
+ <select class="path-picker-sort" aria-label="Sort entries"></select>
121
+ </label>
122
+ </div>
98
123
  <div class="path-picker-list" role="listbox"></div>
99
124
  <div class="path-picker-selection">
100
125
  <span class="path-picker-selection-label">Selected</span>
@@ -114,12 +139,27 @@ const PathPicker = {
114
139
  overlay.querySelector('.path-picker-cancel').addEventListener('click', () => this.close(true));
115
140
  overlay.querySelector('.path-picker-confirm').addEventListener('click', () => this.confirm());
116
141
  overlay.querySelector('.path-picker-current-select').addEventListener('click', () => {
117
- const current = overlay.querySelector('.path-picker-current').textContent;
118
- if (current) this.select(current);
142
+ if (this._currentPath) this.select(this._currentPath);
119
143
  });
120
- overlay.querySelector('.path-picker-refresh').addEventListener('click', () => this.load());
144
+ overlay.querySelector('.path-picker-refresh').addEventListener('click', () => this.load(this._currentPath));
121
145
  overlay.querySelector('.path-picker-hidden').addEventListener('click', () => this.toggleHidden());
122
146
  this._syncHiddenButton();
147
+ // Typing a path is the fast way there. The listing is loaded ONLY on Enter/Go,
148
+ // never on each keystroke: a half-typed path is a 404 the server has to
149
+ // answer for nothing, and jumping mid-edit would yank the field around.
150
+ overlay.querySelector('.path-picker-jump').addEventListener('submit', (event) => {
151
+ event.preventDefault();
152
+ this.jumpTo(overlay.querySelector('.path-picker-current').value);
153
+ });
154
+ const sortSelect = overlay.querySelector('.path-picker-sort');
155
+ for (const mode of PATH_PICKER_SORT_MODES) {
156
+ const option = document.createElement('option');
157
+ option.value = mode.value;
158
+ option.textContent = mode.label;
159
+ sortSelect.appendChild(option);
160
+ }
161
+ sortSelect.value = this._sortMode;
162
+ sortSelect.addEventListener('change', (event) => this.setSortMode(event.target.value));
123
163
  overlay.querySelector('.path-picker-up').addEventListener('click', () => {
124
164
  const parent = overlay.querySelector('.path-picker-up').dataset.parent;
125
165
  if (parent) this.load(parent);
@@ -169,15 +209,97 @@ const PathPicker = {
169
209
  // OFF inside a hidden folder makes the current path unbrowsable again; the
170
210
  // server answers 403 and load()'s catch falls back to the default root,
171
211
  // which is the only place left to stand.
172
- this.load(this.overlay.querySelector('.path-picker-current').textContent || '');
212
+ this.load(this._currentPath || '');
213
+ },
214
+
215
+ _loadSortMode() {
216
+ try {
217
+ const stored = localStorage.getItem(PATH_PICKER_SORT_KEY);
218
+ return PATH_PICKER_SORT_MODES.some((mode) => mode.value === stored) ? stored : PATH_PICKER_DEFAULT_SORT;
219
+ } catch {
220
+ return PATH_PICKER_DEFAULT_SORT;
221
+ }
222
+ },
223
+
224
+ setSortMode(mode) {
225
+ if (!PATH_PICKER_SORT_MODES.some((candidate) => candidate.value === mode)) return;
226
+ this._sortMode = mode;
227
+ try {
228
+ localStorage.setItem(PATH_PICKER_SORT_KEY, mode);
229
+ } catch {}
230
+ const select = this.overlay?.querySelector('.path-picker-sort');
231
+ if (select && select.value !== mode) select.value = mode;
232
+ // Re-order what is already on screen; no round trip, no lost selection.
233
+ if (this.overlay) this.renderEntries();
234
+ },
235
+
236
+ /**
237
+ * Order entries for display. Folders always come first, whatever the mode:
238
+ * a date sort is for finding the file you just made, and the folders are the
239
+ * way past it, not the thing being looked for. An entry without an mtime (an
240
+ * older server, the in-container source) sorts after every dated one and then
241
+ * by name, so a listing never degrades into an unstable order.
242
+ *
243
+ * ⚠️ This re-orders the listing the SERVER returned, and the server cuts at
244
+ * FILESYSTEM_PICKER_ENTRY_LIMIT (500) after sorting by name. So in a folder past
245
+ * that limit, "Newest first" is the newest of the first 500 BY NAME, not the newest
246
+ * in the folder, which is the one case this sort exists for. The status line says
247
+ * "(first 500 by name)" rather than "(first 500)" so the cut is legible; ordering
248
+ * before the cut would have to happen server-side, and would cost a stat on every
249
+ * entry in the directory rather than on the 500 that are returned.
250
+ */
251
+ _sortEntries(entries) {
252
+ const [key, direction] = this._sortMode.split('-');
253
+ const sign = direction === 'desc' ? -1 : 1;
254
+ const byName = (a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' });
255
+ return entries.slice().sort((a, b) => {
256
+ if (a.type !== b.type) return a.type === 'directory' ? -1 : 1;
257
+ if (key === 'mtime') {
258
+ const aTime = typeof a.mtimeMs === 'number' ? a.mtimeMs : null;
259
+ const bTime = typeof b.mtimeMs === 'number' ? b.mtimeMs : null;
260
+ if (aTime !== null && bTime !== null && aTime !== bTime) return sign * (aTime - bTime);
261
+ if (aTime === null && bTime !== null) return 1;
262
+ if (aTime !== null && bTime === null) return -1;
263
+ return byName(a, b);
264
+ }
265
+ return sign * byName(a, b);
266
+ });
267
+ },
268
+
269
+ /** Compact modified-time label: time of day today, month-day this year, else the date. */
270
+ _formatModified(mtimeMs) {
271
+ if (typeof mtimeMs !== 'number' || !Number.isFinite(mtimeMs)) return '';
272
+ const date = new Date(mtimeMs);
273
+ if (Number.isNaN(date.getTime())) return '';
274
+ const now = new Date();
275
+ const pad = (n) => String(n).padStart(2, '0');
276
+ if (date.toDateString() === now.toDateString()) return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
277
+ if (date.getFullYear() === now.getFullYear()) return `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
278
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
279
+ },
280
+
281
+ /**
282
+ * Go to a path the user typed. A file path lands in its folder with the
283
+ * file selected, so pasting a full path from a log or a message is one Enter
284
+ * away from Select. A path that does not resolve lands in its parent folder
285
+ * when that exists (the closest place to stand) and otherwise keeps the
286
+ * current listing, and says so either way — unlike a stale initialPath, a
287
+ * typo is not a reason to throw the user back to the root.
288
+ */
289
+ jumpTo(rawPath) {
290
+ const path = String(rawPath || '').trim();
291
+ if (!path) return;
292
+ this.load(path, { typed: true });
173
293
  },
174
294
 
175
- async load(path) {
295
+ async load(path, options = {}) {
176
296
  if (!this.overlay || !this._options) return;
177
297
  const loadSequence = ++this._loadSequence;
178
298
  const list = this.overlay.querySelector('.path-picker-list');
179
299
  const status = this.overlay.querySelector('.path-picker-status');
180
- list.replaceChildren();
300
+ const typed = !!options.typed;
301
+ if (!typed) list.replaceChildren();
302
+ status.classList.remove('error');
181
303
  status.textContent = 'Loading...';
182
304
 
183
305
  const params = new URLSearchParams();
@@ -194,13 +316,40 @@ const PathPicker = {
194
316
  if (!result?.success) throw new Error(result?.error || 'Failed to browse this folder');
195
317
  if (!this.overlay || loadSequence !== this._loadSequence) return;
196
318
  this.render(result.data);
319
+ if (options.selectIfListed) {
320
+ // Landed in the typed path's folder: select the entry if it is there
321
+ // (a file path), otherwise say what the server said about the full
322
+ // path — the listing is still the closest place to stand.
323
+ if (this._entries.some((entry) => entry.path === options.selectIfListed)) {
324
+ this.select(options.selectIfListed);
325
+ } else {
326
+ status.textContent = options.failMessage || 'Path not found';
327
+ status.classList.add('error');
328
+ }
329
+ }
197
330
  } catch (error) {
198
331
  if (!this.overlay || loadSequence !== this._loadSequence) return;
332
+ const message = error.message || 'Failed to browse this folder';
333
+ if (typed) {
334
+ // The browse endpoint answers a FILE path with "not found" (it resolves
335
+ // folders only), so one retry lands in the parent folder and selects
336
+ // the entry from the listing. Only one level: a typo two segments up
337
+ // is an error, not a reason to climb to the root.
338
+ const slash = path.lastIndexOf('/');
339
+ if (slash > 0 && !options.selectIfListed) {
340
+ this.load(path.slice(0, slash), { typed: true, selectIfListed: path, failMessage: message });
341
+ return;
342
+ }
343
+ this.renderEntries();
344
+ status.textContent = options.failMessage || message;
345
+ status.classList.add('error');
346
+ return;
347
+ }
199
348
  if (path) {
200
349
  this.load('');
201
350
  return;
202
351
  }
203
- status.textContent = error.message || 'Failed to browse this folder';
352
+ status.textContent = message;
204
353
  status.classList.add('error');
205
354
  }
206
355
  },
@@ -216,19 +365,29 @@ const PathPicker = {
216
365
  rootSelect.appendChild(option);
217
366
  }
218
367
 
219
- this.overlay.querySelector('.path-picker-current').textContent = data.path;
368
+ this._currentPath = data.path;
369
+ this.overlay.querySelector('.path-picker-current').value = data.path;
220
370
  const up = this.overlay.querySelector('.path-picker-up');
221
371
  up.dataset.parent = data.parent || '';
222
372
  up.disabled = !data.parent;
373
+ this._entries = Array.isArray(data.entries) ? data.entries : [];
374
+ this._truncated = !!data.truncated;
375
+ this.renderEntries();
376
+ },
377
+
378
+ /** (Re)build the list from the last listing in the current sort order. */
379
+ renderEntries() {
380
+ if (!this.overlay) return;
381
+ const entries = this._sortEntries(this._entries);
223
382
  const status = this.overlay.querySelector('.path-picker-status');
224
383
  status.classList.remove('error');
225
- status.textContent = data.entries.length === 0
384
+ status.textContent = entries.length === 0
226
385
  ? 'This folder is empty'
227
- : `${data.entries.length} item${data.entries.length === 1 ? '' : 's'}${data.truncated ? ' (first 500)' : ''}`;
386
+ : `${entries.length} item${entries.length === 1 ? '' : 's'}${this._truncated ? ' (first 500 by name)' : ''}`;
228
387
 
229
388
  const list = this.overlay.querySelector('.path-picker-list');
230
389
  list.replaceChildren();
231
- for (const entry of data.entries) {
390
+ for (const entry of entries) {
232
391
  const row = document.createElement('div');
233
392
  row.className = 'path-picker-item';
234
393
  if (entry.type === 'file' && this._options.directoriesOnly && !entry.previewKind) {
@@ -248,6 +407,14 @@ const PathPicker = {
248
407
  name.className = 'path-picker-item-name';
249
408
  name.textContent = entry.name;
250
409
  open.append(icon, name);
410
+ const modified = this._formatModified(entry.mtimeMs);
411
+ if (modified) {
412
+ const meta = document.createElement('span');
413
+ meta.className = 'path-picker-item-meta';
414
+ meta.textContent = modified;
415
+ meta.title = new Date(entry.mtimeMs).toLocaleString();
416
+ open.appendChild(meta);
417
+ }
251
418
  if (entry.symlink) {
252
419
  const link = document.createElement('span');
253
420
  link.className = 'path-picker-item-link';
@@ -417,6 +584,8 @@ const PathPicker = {
417
584
  this._previousFocus = null;
418
585
  this._options = null;
419
586
  this._selectedPath = '';
587
+ this._currentPath = '';
588
+ this._entries = [];
420
589
  if (restoreFocus) previousFocus?.focus?.();
421
590
  },
422
591
  };
@@ -187,7 +187,7 @@
187
187
  <span class="tool-detail">${escapeHtml(i)}</span>
188
188
  </div>`}else if(e.type==="message"){const s=e.text.length>150?e.text.substring(0,150)+"...":e.text;return`<div class="message-line">
189
189
  <span class="time">${t}</span> \u{1F4AC} ${escapeHtml(s)}
190
- </div>`}return""},updateSubagentWindows(){for(const e of this.subagentWindows.keys())this.renderSubagentWindowContent(e),this.updateSubagentWindowHeader(e)},updateSubagentWindowHeader(e){const t=this.subagents.get(e);if(!t)return;const s=document.getElementById(`subagent-window-${e}`);if(!s)return;const n=s.querySelector(".subagent-window-title .id");if(n){const c=this.getTeammateInfo(t),h=c?c.name:t.description||e.substring(0,7),u=h.length>50?h.substring(0,50)+"...":h;n.textContent=u}let i=s.querySelector(".teammate-badge");const a=this.getTeammateInfo(t);if(a&&!i){const c=s.querySelector(".subagent-window-title");if(c){const h=document.createElement("span");h.className=`teammate-badge teammate-color-${a.color}`,h.title=`Team: ${a.teamName}`,h.textContent=`@${a.name}`;const u=c.querySelector(".status");u&&u.insertAdjacentElement("beforebegin",h)}}const r=s.querySelector(".subagent-window-title");r&&(r.title=t.description||e);let o=s.querySelector(".subagent-window-title .subagent-model-badge");if(t.modelShort){if(!o){o=document.createElement("span"),o.className=`subagent-model-badge ${t.modelShort}`;const c=s.querySelector(".subagent-window-title .status");c&&c.insertAdjacentElement("beforebegin",o)}o.className=`subagent-model-badge ${t.modelShort}`,o.textContent=t.modelShort}const l=s.querySelector(".subagent-window-title .status");l&&(l.className=`status ${t.status}`,l.textContent=t.status)},openAllActiveSubagentWindows(){for(const[e,t]of this.subagents)t.status==="active"&&!this.subagentWindows.has(e)&&this.openSubagentWindow(e)},initTeammateTerminal(e,t,s){const n=s.querySelector(".subagent-window-body");if(!n)return;n.innerHTML="",n.classList.add("teammate-terminal-body"),s.classList.add("has-terminal");const i=t.sessionId,a=[];this.teammateTerminals.set(e,{terminal:null,fitAddon:null,paneTarget:t.paneTarget,sessionId:i,resizeObserver:null,pendingData:a}),requestAnimationFrame(()=>{if(!document.contains(n)){this.teammateTerminals.delete(e);return}const r=new Terminal({theme:{...window.codemanCurrentXtermTheme()},minimumContrastRatio:window.codemanCurrentSkinIsLight()?4.5:1,fontFamily:window.CodemanTerminalFont.resolve(this.loadAppSettingsFromStorage?.().terminalFontFamily),fontSize:12,lineHeight:1.2,cursorBlink:!0,cursorStyle:"block",scrollback:DEFAULT_SCROLLBACK,allowTransparency:!0,allowProposedApi:!0}),o=new FitAddon.FitAddon;if(r.loadAddon(o),typeof Unicode11Addon<"u")try{const h=new Unicode11Addon.Unicode11Addon;r.loadAddon(h),r.unicode.activeVersion="11"}catch{}try{r.open(n)}catch(h){console.warn("[TeammateTerminal] Failed to open terminal:",h),this.teammateTerminals.delete(e);return}setTimeout(()=>{try{o.fit()}catch{}fetch(`/api/sessions/${i}/teammate-pane-buffer/${encodeURIComponent(t.paneTarget)}`).then(h=>h.json()).then(h=>{if(h.success&&h.data?.buffer)try{r.write(h.data.buffer)}catch{}}).catch(h=>console.error("[TeammateTerminal] Failed to fetch buffer:",h));for(const h of a)try{r.write(h)}catch{}a.length=0},100),r.onData(h=>{fetch(`/api/sessions/${i}/teammate-pane-input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({paneTarget:t.paneTarget,input:h})}).catch(u=>console.error("[TeammateTerminal] Failed to send input:",u))});const l=new ResizeObserver(()=>{requestAnimationFrame(()=>{try{o.fit()}catch{}})});l.observe(n);const c=this.teammateTerminals.get(e);c&&(c.terminal=r,c.fitAddon=o,c.resizeObserver=l)})},openTeammateTerminalWindow(e){if(!this.sessions.has(e.sessionId))return;const t=`pane-${e.paneTarget}`;if(this.subagentWindows.has(t)){const v=this.subagentWindows.get(t);v.hidden&&(v.element.style.display="flex",v.hidden=!1),v.element.style.zIndex=++this.subagentWindowZIndex,v.minimized&&this.restoreSubagentWindow(t);return}const s=this.subagentWindows.size,n=550,i=400,a=20,r=window.innerWidth,o=window.innerHeight,l=50,c=120,h=Math.floor((r-l-50)/(n+a))||1,u=Math.floor((o-c-50)/(i+a))||1,d=s%h,p=Math.floor(s/h)%u;let m=l+d*(n+a),g=c+p*(i+a);m=Math.max(10,Math.min(m,r-n-10)),g=Math.max(10,Math.min(g,o-i-10));const y=e.color||"blue",w=document.createElement("div");w.className="subagent-window has-terminal",w.id=`subagent-window-${t}`,w.style.zIndex=++this.subagentWindowZIndex,w.style.left=`${m}px`,w.style.top=`${g}px`,w.style.width=`${n}px`,w.style.height=`${i}px`,w.innerHTML=`
190
+ </div>`}return""},updateSubagentWindows(){for(const e of this.subagentWindows.keys())this.renderSubagentWindowContent(e),this.updateSubagentWindowHeader(e)},updateSubagentWindowHeader(e){const t=this.subagents.get(e);if(!t)return;const s=document.getElementById(`subagent-window-${e}`);if(!s)return;const n=s.querySelector(".subagent-window-title .id");if(n){const c=this.getTeammateInfo(t),h=c?c.name:t.description||e.substring(0,7),u=h.length>50?h.substring(0,50)+"...":h;n.textContent=u}let i=s.querySelector(".teammate-badge");const a=this.getTeammateInfo(t);if(a&&!i){const c=s.querySelector(".subagent-window-title");if(c){const h=document.createElement("span");h.className=`teammate-badge teammate-color-${a.color}`,h.title=`Team: ${a.teamName}`,h.textContent=`@${a.name}`;const u=c.querySelector(".status");u&&u.insertAdjacentElement("beforebegin",h)}}const r=s.querySelector(".subagent-window-title");r&&(r.title=t.description||e);let o=s.querySelector(".subagent-window-title .subagent-model-badge");if(t.modelShort){if(!o){o=document.createElement("span"),o.className=`subagent-model-badge ${t.modelShort}`;const c=s.querySelector(".subagent-window-title .status");c&&c.insertAdjacentElement("beforebegin",o)}o.className=`subagent-model-badge ${t.modelShort}`,o.textContent=t.modelShort}const l=s.querySelector(".subagent-window-title .status");l&&(l.className=`status ${t.status}`,l.textContent=t.status)},openAllActiveSubagentWindows(){for(const[e,t]of this.subagents)t.status==="active"&&!this.subagentWindows.has(e)&&this.openSubagentWindow(e)},initTeammateTerminal(e,t,s){const n=s.querySelector(".subagent-window-body");if(!n)return;n.innerHTML="",n.classList.add("teammate-terminal-body"),s.classList.add("has-terminal");const i=t.sessionId,a=[];this.teammateTerminals.set(e,{terminal:null,fitAddon:null,paneTarget:t.paneTarget,sessionId:i,resizeObserver:null,pendingData:a}),requestAnimationFrame(()=>{if(!document.contains(n)){this.teammateTerminals.delete(e);return}const r=new Terminal({theme:{...window.codemanCurrentXtermTheme()},minimumContrastRatio:window.codemanCurrentSkinIsLight()?4.5:1,fontFamily:window.CodemanTerminalFont.resolve(this.loadAppSettingsFromStorage?.().terminalFontFamily),fontSize:12,lineHeight:1.2,cursorBlink:!0,cursorStyle:"block",scrollback:DEFAULT_SCROLLBACK,allowTransparency:!0,allowProposedApi:!0}),o=new FitAddon.FitAddon;if(r.loadAddon(o),typeof Unicode11Addon<"u")try{const h=new Unicode11Addon.Unicode11Addon;r.loadAddon(h),r.unicode.activeVersion="11"}catch{}try{r.open(n)}catch(h){console.warn("[TeammateTerminal] Failed to open terminal:",h),this.teammateTerminals.delete(e);return}r.attachCustomKeyEventHandler(h=>h.type==="keydown"&&h.key.toLowerCase()==="z"&&h.ctrlKey&&!h.altKey&&!h.metaKey&&!h.shiftKey?(h.preventDefault(),!1):!0),setTimeout(()=>{try{o.fit()}catch{}fetch(`/api/sessions/${i}/teammate-pane-buffer/${encodeURIComponent(t.paneTarget)}`).then(h=>h.json()).then(h=>{if(h.success&&h.data?.buffer)try{r.write(h.data.buffer)}catch{}}).catch(h=>console.error("[TeammateTerminal] Failed to fetch buffer:",h));for(const h of a)try{r.write(h)}catch{}a.length=0},100),r.onData(h=>{fetch(`/api/sessions/${i}/teammate-pane-input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({paneTarget:t.paneTarget,input:h})}).catch(u=>console.error("[TeammateTerminal] Failed to send input:",u))});const l=new ResizeObserver(()=>{requestAnimationFrame(()=>{try{o.fit()}catch{}})});l.observe(n);const c=this.teammateTerminals.get(e);c&&(c.terminal=r,c.fitAddon=o,c.resizeObserver=l)})},openTeammateTerminalWindow(e){if(!this.sessions.has(e.sessionId))return;const t=`pane-${e.paneTarget}`;if(this.subagentWindows.has(t)){const v=this.subagentWindows.get(t);v.hidden&&(v.element.style.display="flex",v.hidden=!1),v.element.style.zIndex=++this.subagentWindowZIndex,v.minimized&&this.restoreSubagentWindow(t);return}const s=this.subagentWindows.size,n=550,i=400,a=20,r=window.innerWidth,o=window.innerHeight,l=50,c=120,h=Math.floor((r-l-50)/(n+a))||1,u=Math.floor((o-c-50)/(i+a))||1,d=s%h,p=Math.floor(s/h)%u;let m=l+d*(n+a),g=c+p*(i+a);m=Math.max(10,Math.min(m,r-n-10)),g=Math.max(10,Math.min(g,o-i-10));const y=e.color||"blue",w=document.createElement("div");w.className="subagent-window has-terminal",w.id=`subagent-window-${t}`,w.style.zIndex=++this.subagentWindowZIndex,w.style.left=`${m}px`,w.style.top=`${g}px`,w.style.width=`${n}px`,w.style.height=`${i}px`,w.innerHTML=`
191
191
  <div class="subagent-window-header">
192
192
  <div class="subagent-window-title" title="Teammate terminal: ${escapeHtml(e.teammateName)} (pane ${e.paneTarget})">
193
193
  <span class="icon" style="color: var(--team-color-${y}, #339af0)">\u2B24</span>
Binary file