glad-web 1.0.33 → 1.0.35
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/lib/codex/structured-session.js +52 -2
- package/lib/commands/web.js +6 -0
- package/lib/web/codex.js +201 -14
- package/lib/web/core.js +7 -0
- package/lib/web/session.js +10 -0
- package/lib/web/styles.css +12 -2
- package/package.json +1 -1
|
@@ -4,6 +4,8 @@ const readline = require('readline');
|
|
|
4
4
|
const crypto = require('crypto');
|
|
5
5
|
const PTYManager = require('../session/pty-manager');
|
|
6
6
|
|
|
7
|
+
const CODEX_MESSAGE_PAGE_BYTES = 200 * 1024;
|
|
8
|
+
|
|
7
9
|
const PERMISSION_MODES = new Set(['untrusted', 'on-request', 'never']);
|
|
8
10
|
const SANDBOX_MODES = new Set(['read-only', 'workspace-write', 'danger-full-access']);
|
|
9
11
|
const FALLBACK_EFFORTS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'ultra'];
|
|
@@ -250,14 +252,60 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
snapshot() {
|
|
255
|
+
const historyPage = this.getMessagePage();
|
|
256
|
+
const { messages, ...historyPageMeta } = historyPage;
|
|
253
257
|
return { id: this.id, name: this.name, tool: this.tool.displayName, toolKey: this.tool.key,
|
|
254
|
-
status: this.status, state: this.getControlState(), messages:
|
|
258
|
+
status: this.status, state: this.getControlState(), messages, historyPage: historyPageMeta,
|
|
255
259
|
pendingPermissions: [
|
|
256
260
|
...this.completedPermissions,
|
|
257
261
|
...Array.from(this.pendingPermissions.values()).map(item => item.public)
|
|
258
262
|
] };
|
|
259
263
|
}
|
|
260
264
|
|
|
265
|
+
getMessagePage(beforeId = null, maxBytes = CODEX_MESSAGE_PAGE_BYTES) {
|
|
266
|
+
const requestedBeforeId = beforeId == null ? '' : String(beforeId);
|
|
267
|
+
let end = this.messages.length;
|
|
268
|
+
if (requestedBeforeId) {
|
|
269
|
+
const beforeIndex = this.messages.findIndex(item => String(item.id || '') === requestedBeforeId);
|
|
270
|
+
if (beforeIndex < 0) {
|
|
271
|
+
return { messages: [], hasMore: false, beforeId: null, bytes: 2, maxBytes };
|
|
272
|
+
}
|
|
273
|
+
end = beforeIndex;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (end <= 0) return { messages: [], hasMore: false, beforeId: null, bytes: 2, maxBytes };
|
|
277
|
+
|
|
278
|
+
const groupStarts = [0];
|
|
279
|
+
for (let i = 1; i < end; i++) {
|
|
280
|
+
const item = this.messages[i];
|
|
281
|
+
if (item.kind === 'turn-start' && (!item.threadId || item.threadId === this.threadId)) {
|
|
282
|
+
groupStarts.push(i);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
groupStarts.push(end);
|
|
286
|
+
|
|
287
|
+
let start = end;
|
|
288
|
+
let selectedBytes = 2;
|
|
289
|
+
for (let groupIndex = groupStarts.length - 2; groupIndex >= 0; groupIndex--) {
|
|
290
|
+
const candidateStart = groupStarts[groupIndex];
|
|
291
|
+
const candidate = this.messages.slice(candidateStart, end);
|
|
292
|
+
const candidateBytes = Buffer.byteLength(JSON.stringify(candidate), 'utf8');
|
|
293
|
+
if (start < end && candidateBytes > maxBytes) break;
|
|
294
|
+
start = candidateStart;
|
|
295
|
+
selectedBytes = candidateBytes;
|
|
296
|
+
if (candidateBytes >= maxBytes) break;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const messages = this.messages.slice(start, end);
|
|
300
|
+
return {
|
|
301
|
+
messages,
|
|
302
|
+
hasMore: start > 0,
|
|
303
|
+
beforeId: messages[0]?.id || null,
|
|
304
|
+
bytes: selectedBytes,
|
|
305
|
+
maxBytes
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
261
309
|
getControlState() {
|
|
262
310
|
const activeSubagentCount = Array.from(this.threadTurns.entries())
|
|
263
311
|
.filter(([threadId, turn]) => threadId !== this.threadId && turn?.status === 'running').length;
|
|
@@ -1088,7 +1136,9 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
1088
1136
|
...(completedAtMs ? { createdAt: completedAtMs } : {}) });
|
|
1089
1137
|
}
|
|
1090
1138
|
if (eventText) this.append({ kind: 'event', level: 'info', text: eventText });
|
|
1091
|
-
|
|
1139
|
+
const historyPage = this.getMessagePage();
|
|
1140
|
+
const { messages, ...historyPageMeta } = historyPage;
|
|
1141
|
+
this.emitEvent({ type: 'history-reset', messages, historyPage: historyPageMeta });
|
|
1092
1142
|
this.emitEvent({ type: 'state', state: this.getControlState() });
|
|
1093
1143
|
}
|
|
1094
1144
|
|
package/lib/commands/web.js
CHANGED
|
@@ -377,6 +377,12 @@ async function webCommand(options) {
|
|
|
377
377
|
if (payload.type === 'codex-compact') {
|
|
378
378
|
sessionManager.compactCodexContext(sessionId).catch(error => logger.error(`Codex compact error: ${error.message}`));
|
|
379
379
|
}
|
|
380
|
+
if (payload.type === 'codex-history-before') {
|
|
381
|
+
const codex = sessionManager.get(sessionId);
|
|
382
|
+
if (codex && codex.kind === 'codex-structured' && codex.presentation === 'structured') {
|
|
383
|
+
ws.send(JSON.stringify({ type: 'codex-history-page', page: codex.getMessagePage(payload.beforeId) }));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
380
386
|
if (payload.type === 'codex-abort') {
|
|
381
387
|
sessionManager.abortCodex(sessionId);
|
|
382
388
|
}
|
package/lib/web/codex.js
CHANGED
|
@@ -320,7 +320,46 @@
|
|
|
320
320
|
const current = container.firstElementChild;
|
|
321
321
|
if (!current) container.appendChild(next);
|
|
322
322
|
else syncCodexDom(current, next);
|
|
323
|
-
|
|
323
|
+
if (codexHistoryPrependAnchor) {
|
|
324
|
+
const anchor = codexHistoryPrependAnchor;
|
|
325
|
+
codexHistoryPrependAnchor = null;
|
|
326
|
+
container.scrollTop = anchor.scrollTop + Math.max(0, container.scrollHeight - anchor.scrollHeight);
|
|
327
|
+
} else {
|
|
328
|
+
container.scrollTop = shouldStickToBottom ? container.scrollHeight : previousScrollTop;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function applyCodexHistoryPageMeta(page = {}) {
|
|
333
|
+
codexHistoryBeforeId = page?.beforeId || codexMessages[0]?.id || null;
|
|
334
|
+
codexHistoryHasMore = Boolean(page?.hasMore);
|
|
335
|
+
codexHistoryLoading = false;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function handleCodexHistoryScroll(event) {
|
|
339
|
+
if (event?.isTrusted) codexHistoryUserScrolled = true;
|
|
340
|
+
if (!codexHistoryUserScrolled || !codexHistoryHasMore || codexHistoryLoading) return;
|
|
341
|
+
const container = event?.currentTarget || document.getElementById('codex-chat-container');
|
|
342
|
+
const scrollRange = Math.max(0, container.scrollHeight - container.clientHeight);
|
|
343
|
+
if (container.scrollTop > scrollRange * 0.5) return;
|
|
344
|
+
if (!codexHistoryBeforeId || currentSocket?.readyState !== 1) return;
|
|
345
|
+
codexHistoryLoading = true;
|
|
346
|
+
currentSocket.send(JSON.stringify({ type: 'codex-history-before', beforeId: codexHistoryBeforeId }));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function applyCodexHistoryPage(page = {}) {
|
|
350
|
+
const older = Array.isArray(page.messages) ? page.messages : [];
|
|
351
|
+
const knownIds = new Set(codexMessages.map(item => String(item.id || '')));
|
|
352
|
+
const uniqueOlder = older.filter(item => item?.id && !knownIds.has(String(item.id)));
|
|
353
|
+
const container = document.getElementById('codex-chat-container');
|
|
354
|
+
if (uniqueOlder.length && container) {
|
|
355
|
+
codexHistoryPrependAnchor = {
|
|
356
|
+
scrollTop: container.scrollTop,
|
|
357
|
+
scrollHeight: container.scrollHeight
|
|
358
|
+
};
|
|
359
|
+
codexMessages = [...uniqueOlder, ...codexMessages];
|
|
360
|
+
}
|
|
361
|
+
applyCodexHistoryPageMeta(page);
|
|
362
|
+
if (uniqueOlder.length) renderCodexChat();
|
|
324
363
|
}
|
|
325
364
|
|
|
326
365
|
function applyCodexState(state = {}) {
|
|
@@ -445,7 +484,12 @@
|
|
|
445
484
|
if (!event) return;
|
|
446
485
|
if (event.type === 'message' && event.message) codexMessages.push(event.message);
|
|
447
486
|
else if (event.type === 'message-updated' && event.message) { const i = codexMessages.findIndex(item => item.id === event.message.id); if (i >= 0) codexMessages[i] = event.message; else codexMessages.push(event.message); }
|
|
448
|
-
else if (event.type === 'history-reset')
|
|
487
|
+
else if (event.type === 'history-reset') {
|
|
488
|
+
codexMessages = event.messages || [];
|
|
489
|
+
codexHistoryUserScrolled = false;
|
|
490
|
+
codexHistoryPrependAnchor = null;
|
|
491
|
+
applyCodexHistoryPageMeta(event.historyPage);
|
|
492
|
+
}
|
|
449
493
|
else if (event.type === 'permission-request' && event.request) { codexPendingPermissions = [...codexPendingPermissions.filter(item => item.id !== event.request.id), event.request]; }
|
|
450
494
|
else if (event.type === 'permission-updated' && event.request) codexPendingPermissions = codexPendingPermissions.map(item => item.id === event.request.id ? event.request : item);
|
|
451
495
|
if (event.state) applyCodexState(event.state); else renderCodexChat();
|
|
@@ -603,19 +647,157 @@
|
|
|
603
647
|
}
|
|
604
648
|
}
|
|
605
649
|
|
|
606
|
-
|
|
650
|
+
const CODEX_RECENT_SKILLS_KEY = 'glad.codex.recentSkills';
|
|
651
|
+
|
|
652
|
+
function codexSkillIdentity(skill) {
|
|
653
|
+
return `${String(skill?.name || '')}\n${String(skill?.path || '')}`;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function codexSkillDisplayName(skill) {
|
|
657
|
+
return String(skill?.interface?.displayName || skill?.name || 'Unknown skill');
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function codexSkillDescription(skill) {
|
|
661
|
+
return String(skill?.interface?.shortDescription || skill?.shortDescription || skill?.description || '');
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function codexSkillDirectory(skill) {
|
|
665
|
+
const path = String(skill?.path || '').replace(/\\/g, '/');
|
|
666
|
+
const separator = path.lastIndexOf('/');
|
|
667
|
+
const directory = separator > 0 ? path.slice(0, separator) : path;
|
|
668
|
+
const skillRoot = directory.toLowerCase().lastIndexOf('/skills/');
|
|
669
|
+
return skillRoot >= 0 ? directory.slice(skillRoot + '/skills/'.length) : directory.split('/').filter(Boolean).pop() || directory;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function codexSkillSource(skill) {
|
|
673
|
+
const scope = String(skill?.scope || '').toLowerCase();
|
|
674
|
+
const path = String(skill?.path || '').replace(/\\/g, '/').toLowerCase();
|
|
675
|
+
if (['repo', 'project', 'workspace'].includes(scope)) return { group: 'project', label: 'Project' };
|
|
676
|
+
if (['user', 'personal'].includes(scope)) return { group: 'personal', label: 'Personal' };
|
|
677
|
+
if (scope === 'plugin') return { group: 'other', label: 'Plugin' };
|
|
678
|
+
if (['system', 'admin', 'builtin', 'built-in'].includes(scope)) return { group: 'other', label: 'System' };
|
|
679
|
+
if (path.includes('/plugins/')) return { group: 'other', label: 'Plugin' };
|
|
680
|
+
if (path.includes('/.system/')) return { group: 'other', label: 'System' };
|
|
681
|
+
if (path.includes('/.codex/skills/')) return { group: 'personal', label: 'Personal' };
|
|
682
|
+
if (path.includes('/.agents/skills/')) return { group: 'project', label: 'Project' };
|
|
683
|
+
return { group: 'other', label: scope ? scope[0].toUpperCase() + scope.slice(1) : 'Other' };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function compareCodexSkills(left, right) {
|
|
687
|
+
return codexSkillDisplayName(left).localeCompare(codexSkillDisplayName(right), undefined, {
|
|
688
|
+
sensitivity: 'base',
|
|
689
|
+
numeric: true
|
|
690
|
+
}) || String(left.name || '').localeCompare(String(right.name || ''), undefined, { sensitivity: 'base', numeric: true })
|
|
691
|
+
|| String(left.path || '').localeCompare(String(right.path || ''), undefined, { sensitivity: 'base', numeric: true });
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function readRecentCodexSkills() {
|
|
695
|
+
try {
|
|
696
|
+
const value = JSON.parse(localStorage.getItem(CODEX_RECENT_SKILLS_KEY) || '[]');
|
|
697
|
+
return Array.isArray(value) ? value.filter(item => item?.name && item?.path).slice(0, 3) : [];
|
|
698
|
+
} catch (_) {
|
|
699
|
+
return [];
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function rememberCodexSkill(skill) {
|
|
704
|
+
const key = codexSkillIdentity(skill);
|
|
705
|
+
const recent = readRecentCodexSkills().filter(item => codexSkillIdentity(item) !== key);
|
|
706
|
+
recent.unshift({ name: skill.name, path: skill.path });
|
|
707
|
+
try { localStorage.setItem(CODEX_RECENT_SKILLS_KEY, JSON.stringify(recent.slice(0, 3))); } catch (_) {}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function scoreCodexSkillSearch(skill, query) {
|
|
711
|
+
const tokens = String(query || '').toLocaleLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
712
|
+
if (!tokens.length) return 0;
|
|
713
|
+
const source = codexSkillSource(skill);
|
|
714
|
+
const fields = [
|
|
715
|
+
String(skill.name || '').toLocaleLowerCase(),
|
|
716
|
+
codexSkillDisplayName(skill).toLocaleLowerCase(),
|
|
717
|
+
codexSkillDescription(skill).toLocaleLowerCase(),
|
|
718
|
+
source.label.toLocaleLowerCase(),
|
|
719
|
+
String(skill.scope || '').toLocaleLowerCase(),
|
|
720
|
+
String(skill.path || '').toLocaleLowerCase()
|
|
721
|
+
];
|
|
722
|
+
let score = 0;
|
|
723
|
+
for (const token of tokens) {
|
|
724
|
+
let tokenScore = Number.POSITIVE_INFINITY;
|
|
725
|
+
fields.forEach((field, fieldIndex) => {
|
|
726
|
+
const position = field.indexOf(token);
|
|
727
|
+
if (position < 0) return;
|
|
728
|
+
const exactBonus = field === token ? -4 : position === 0 ? -2 : 0;
|
|
729
|
+
tokenScore = Math.min(tokenScore, fieldIndex * 10 + position + exactBonus);
|
|
730
|
+
});
|
|
731
|
+
if (!Number.isFinite(tokenScore)) return null;
|
|
732
|
+
score += tokenScore;
|
|
733
|
+
}
|
|
734
|
+
return score;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function codexSkillItemHtml(item, index) {
|
|
738
|
+
const selected = selectedCodexSkill?.name === item.name && selectedCodexSkill?.path === item.path;
|
|
739
|
+
const displayName = codexSkillDisplayName(item);
|
|
740
|
+
const description = codexSkillDescription(item);
|
|
741
|
+
const source = codexSkillSource(item);
|
|
742
|
+
const fullName = String(item.name || displayName);
|
|
743
|
+
const directory = codexSkillDirectory(item);
|
|
744
|
+
return `<button type="button" class="codex-skill-item${selected ? ' selected' : ''}" data-codex-skill-path="${escapeHtml(item.path || '')}" title="${escapeHtml(item.path || displayName)}" onclick="selectCodexSkill(${index})"><span class="codex-skill-item-name"><span>${escapeHtml(fullName)}</span><span class="codex-skill-source">${escapeHtml(source.label)}</span></span>${description ? `<span class="codex-skill-description">${escapeHtml(description)}</span>` : ''}<span class="codex-skill-meta"><span class="codex-skill-directory" title="${escapeHtml(directory)}">Dir · ${escapeHtml(directory)}</span></span></button>`;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function codexSkillSectionHtml(title, entries) {
|
|
748
|
+
if (!entries.length) return '';
|
|
749
|
+
return `<section class="codex-skill-section"><div class="codex-skill-section-title"><span>${escapeHtml(title)}</span><span>${entries.length}</span></div>${entries.map(entry => codexSkillItemHtml(entry.item, entry.index)).join('')}</section>`;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function codexSkillResultsHtml() {
|
|
753
|
+
const entries = codexSkillItems.map((item, index) => ({ item, index }));
|
|
754
|
+
const query = codexSkillQuery.trim();
|
|
755
|
+
let sections = '';
|
|
756
|
+
if (query) {
|
|
757
|
+
const matches = entries.map(entry => ({ ...entry, score: scoreCodexSkillSearch(entry.item, query) }))
|
|
758
|
+
.filter(entry => entry.score != null)
|
|
759
|
+
.sort((left, right) => left.score - right.score || compareCodexSkills(left.item, right.item));
|
|
760
|
+
sections = codexSkillSectionHtml('Search results', matches);
|
|
761
|
+
} else {
|
|
762
|
+
const byIdentity = new Map(entries.map(entry => [codexSkillIdentity(entry.item), entry]));
|
|
763
|
+
const recent = readRecentCodexSkills().map(item => byIdentity.get(codexSkillIdentity(item))).filter(Boolean);
|
|
764
|
+
const recentKeys = new Set(recent.map(entry => codexSkillIdentity(entry.item)));
|
|
765
|
+
const remaining = entries.filter(entry => !recentKeys.has(codexSkillIdentity(entry.item)));
|
|
766
|
+
const group = name => remaining.filter(entry => codexSkillSource(entry.item).group === name)
|
|
767
|
+
.sort((left, right) => compareCodexSkills(left.item, right.item));
|
|
768
|
+
sections = codexSkillSectionHtml('Recently used', recent)
|
|
769
|
+
+ codexSkillSectionHtml('Current project', group('project'))
|
|
770
|
+
+ codexSkillSectionHtml('Personal', group('personal'))
|
|
771
|
+
+ codexSkillSectionHtml('Other', group('other'));
|
|
772
|
+
}
|
|
773
|
+
const emptyText = query ? 'No skills match your search.' : 'No enabled skills found for this folder.';
|
|
774
|
+
const empty = !sections && !codexSkillLoading && !codexSkillError
|
|
775
|
+
? `<div class="claude-resume-meta codex-skill-empty">${emptyText}</div>` : '';
|
|
776
|
+
return `${codexSkillError ? `<div class="claude-resume-meta codex-skill-error">${escapeHtml(codexSkillError)}</div>` : ''}${sections}${empty}`;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function renderCodexSkillPanel(focusSearch = false) {
|
|
607
780
|
const panel = document.getElementById('codex-skill-panel');
|
|
608
781
|
if (!panel) return;
|
|
609
782
|
panel.classList.toggle('active', codexSkillPanelOpen);
|
|
610
783
|
if (!codexSkillPanelOpen) { panel.innerHTML = ''; return; }
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
784
|
+
panel.innerHTML = `<div class="codex-skill-header"><div class="codex-skill-header-row"><span>Available skills</span><span>${codexSkillLoading ? 'Loading…' : codexSkillItems.length}</span></div><input id="codex-skill-search" class="codex-skill-search" type="search" aria-label="Search skills" placeholder="Search skills…" value="${escapeHtml(codexSkillQuery)}" oninput="updateCodexSkillQuery(this.value)" autocomplete="off" spellcheck="false"></div><div id="codex-skill-results">${codexSkillResultsHtml()}</div>`;
|
|
785
|
+
if (focusSearch) {
|
|
786
|
+
requestAnimationFrame(() => {
|
|
787
|
+
const search = document.getElementById('codex-skill-search');
|
|
788
|
+
if (!search) return;
|
|
789
|
+
search.focus({ preventScroll: true });
|
|
790
|
+
search.setSelectionRange(search.value.length, search.value.length);
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
updateTerminalControlsHeight();
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function updateCodexSkillQuery(value) {
|
|
797
|
+
codexSkillQuery = String(value || '');
|
|
798
|
+
const results = document.getElementById('codex-skill-results');
|
|
799
|
+
if (results) results.innerHTML = codexSkillResultsHtml();
|
|
800
|
+
else renderCodexSkillPanel(true);
|
|
619
801
|
updateTerminalControlsHeight();
|
|
620
802
|
}
|
|
621
803
|
|
|
@@ -636,18 +818,22 @@
|
|
|
636
818
|
return;
|
|
637
819
|
}
|
|
638
820
|
codexSkillLoading = true;
|
|
821
|
+
codexSkillQuery = '';
|
|
822
|
+
codexSkillError = '';
|
|
639
823
|
codexSkillItems = [];
|
|
640
|
-
renderCodexSkillPanel();
|
|
824
|
+
renderCodexSkillPanel(true);
|
|
641
825
|
try {
|
|
642
826
|
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-skills?forceReload=true`, {}, 30000);
|
|
643
827
|
const data = await res.json();
|
|
644
828
|
if (!res.ok || !data.success) throw new Error(data.error || 'Failed to load skills');
|
|
645
829
|
codexSkillItems = data.skills || [];
|
|
646
830
|
codexSkillLoading = false;
|
|
647
|
-
|
|
831
|
+
codexSkillError = (data.errors || []).map(item => item.message || String(item)).filter(Boolean).join(' · ');
|
|
832
|
+
renderCodexSkillPanel(true);
|
|
648
833
|
} catch (error) {
|
|
649
834
|
codexSkillLoading = false;
|
|
650
|
-
|
|
835
|
+
codexSkillError = error.message;
|
|
836
|
+
renderCodexSkillPanel(true);
|
|
651
837
|
}
|
|
652
838
|
}
|
|
653
839
|
|
|
@@ -656,6 +842,7 @@
|
|
|
656
842
|
if (!skill) return;
|
|
657
843
|
const alreadySelected = selectedCodexSkill?.name === skill.name && selectedCodexSkill?.path === skill.path;
|
|
658
844
|
selectedCodexSkill = alreadySelected ? null : { name: skill.name, path: skill.path };
|
|
845
|
+
if (selectedCodexSkill) rememberCodexSkill(skill);
|
|
659
846
|
codexSkillPanelOpen = false;
|
|
660
847
|
renderCodexSkillPanel();
|
|
661
848
|
applyCodexState({});
|
package/lib/web/core.js
CHANGED
|
@@ -47,9 +47,16 @@
|
|
|
47
47
|
let codexSkillPanelOpen = false;
|
|
48
48
|
let codexSkillItems = [];
|
|
49
49
|
let codexSkillLoading = false;
|
|
50
|
+
let codexSkillQuery = '';
|
|
51
|
+
let codexSkillError = '';
|
|
50
52
|
let selectedCodexSkill = null;
|
|
51
53
|
let codexRenderFrame = null;
|
|
52
54
|
let codexApprovalJumpIndex = 0;
|
|
55
|
+
let codexHistoryBeforeId = null;
|
|
56
|
+
let codexHistoryHasMore = false;
|
|
57
|
+
let codexHistoryLoading = false;
|
|
58
|
+
let codexHistoryUserScrolled = false;
|
|
59
|
+
let codexHistoryPrependAnchor = null;
|
|
53
60
|
const modifiers = { ctrl: false };
|
|
54
61
|
|
|
55
62
|
function log(msg) {
|
package/lib/web/session.js
CHANGED
|
@@ -140,7 +140,14 @@
|
|
|
140
140
|
codexSkillPanelOpen = false;
|
|
141
141
|
codexSkillItems = [];
|
|
142
142
|
codexSkillLoading = false;
|
|
143
|
+
codexSkillQuery = '';
|
|
144
|
+
codexSkillError = '';
|
|
143
145
|
selectedCodexSkill = null;
|
|
146
|
+
codexHistoryBeforeId = null;
|
|
147
|
+
codexHistoryHasMore = false;
|
|
148
|
+
codexHistoryLoading = false;
|
|
149
|
+
codexHistoryUserScrolled = false;
|
|
150
|
+
codexHistoryPrependAnchor = null;
|
|
144
151
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
145
152
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
146
153
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
@@ -150,6 +157,7 @@
|
|
|
150
157
|
codexState = { permissionMode: 'default', sandboxMode: 'default', effectivePermissionMode: null, effectiveSandboxMode: null, model: null, effort: null, status: 'idle', threadId: null, presentation: 'structured', models: [], canAbort: false, canCompact: false, compacting: false, canSwitchToTerminal: false, canSwitchToStructured: false };
|
|
151
158
|
setClaudeModeEnabled(false);
|
|
152
159
|
applyCodexState(codexState);
|
|
160
|
+
document.getElementById('codex-chat-container').onscroll = handleCodexHistoryScroll;
|
|
153
161
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
154
162
|
currentSocket = new WebSocket(protocol + '//' + window.location.host + '?sessionId=' + sessionId);
|
|
155
163
|
currentSocket.onmessage = (e) => {
|
|
@@ -157,8 +165,10 @@
|
|
|
157
165
|
if (msg.type === 'codex-snapshot' && msg.snapshot) {
|
|
158
166
|
codexMessages = msg.snapshot.messages || [];
|
|
159
167
|
codexPendingPermissions = msg.snapshot.pendingPermissions || [];
|
|
168
|
+
applyCodexHistoryPageMeta(msg.snapshot.historyPage);
|
|
160
169
|
applyCodexState(msg.snapshot.state || {});
|
|
161
170
|
}
|
|
171
|
+
if (msg.type === 'codex-history-page' && msg.page) applyCodexHistoryPage(msg.page);
|
|
162
172
|
if (msg.type === 'codex-event') {
|
|
163
173
|
applyCodexEvent(msg.event);
|
|
164
174
|
if (msg.event?.type === 'presentation' && msg.event.presentation === 'terminal') {
|
package/lib/web/styles.css
CHANGED
|
@@ -193,12 +193,22 @@
|
|
|
193
193
|
#codex-resume-panel, #codex-fork-panel { max-height: min(280px, 38dvh); overflow-y: auto; }
|
|
194
194
|
#codex-prompt-panel { max-height: min(440px, 52dvh); overflow-y: auto; }
|
|
195
195
|
#codex-skill-panel { max-height: min(360px, 46dvh); overflow-y: auto; }
|
|
196
|
-
.codex-skill-header { position: sticky; top: 0; z-index:
|
|
196
|
+
.codex-skill-header { position: sticky; top: 0; z-index: 3; padding: 9px 11px 10px; border-bottom: 1px solid rgba(255,255,255,.08); background: rgba(28,28,30,.97); color: var(--text-dim); font-size: 11px; font-weight: 800; }
|
|
197
|
+
.codex-skill-header-row, .codex-skill-section-title { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
198
|
+
.codex-skill-search { width: 100%; height: 34px; margin-top: 8px; padding: 0 11px; border: 1px solid rgba(255,255,255,.12); border-radius: 9px; outline: none; background: rgba(255,255,255,.07); color: #f5f5f7; font-size: 12px; box-sizing: border-box; }
|
|
199
|
+
.codex-skill-search::placeholder { color: #77777e; }
|
|
200
|
+
.codex-skill-search:focus { border-color: rgba(0,122,255,.65); background: rgba(255,255,255,.1); box-shadow: 0 0 0 2px rgba(0,122,255,.14); }
|
|
201
|
+
.codex-skill-section + .codex-skill-section { border-top: 1px solid rgba(255,255,255,.09); }
|
|
202
|
+
.codex-skill-section-title { position: sticky; top: 62px; z-index: 2; min-height: 26px; padding: 0 11px; background: rgba(36,36,39,.98); color: #8e8e93; font-size: 9px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }
|
|
197
203
|
.codex-skill-item { display: block; width: 100%; padding: 10px 11px; border: 0; border-bottom: 1px solid rgba(255,255,255,.06); background: transparent; color: #f5f5f7; text-align: left; cursor: pointer; }
|
|
198
204
|
.codex-skill-item.selected { background: rgba(0,122,255,.18); }
|
|
199
205
|
.codex-skill-item-name { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; font-weight: 800; }
|
|
200
|
-
.codex-skill-
|
|
206
|
+
.codex-skill-source { flex: 0 0 auto; padding: 2px 6px; border: 1px solid rgba(0,122,255,.28); border-radius: 999px; background: rgba(0,122,255,.1); color: #79b8ff; font-size: 9px; text-transform: uppercase; }
|
|
201
207
|
.codex-skill-description { display: block; margin-top: 4px; color: #a9a9b0; font-size: 11px; line-height: 1.35; white-space: normal; overflow-wrap: anywhere; }
|
|
208
|
+
.codex-skill-meta { display: flex; min-width: 0; margin-top: 7px; gap: 5px; flex-wrap: wrap; }
|
|
209
|
+
.codex-skill-directory { display: block; min-width: 0; max-width: 100%; flex: 1 1 180px; padding: 2px 6px; overflow: hidden; border: 1px solid rgba(255,255,255,.1); border-radius: 999px; background: rgba(255,255,255,.055); color: #8e8e93; font: 9px/1.25 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; text-overflow: ellipsis; white-space: nowrap; box-sizing: border-box; }
|
|
210
|
+
.codex-skill-empty, .codex-skill-error { padding: 12px; }
|
|
211
|
+
.codex-skill-error { color: #ff6b61; }
|
|
202
212
|
.codex-prompt-header { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 11px; border-bottom: 1px solid rgba(255,255,255,.08); background: rgba(28,28,30,.97); color: var(--text-dim); font-size: 11px; font-weight: 800; }
|
|
203
213
|
.codex-prompt-item { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: end; padding: 10px 11px; border-bottom: 1px solid rgba(255,255,255,.06); }
|
|
204
214
|
.codex-prompt-item:last-of-type { border-bottom: 0; }
|