glad-web 1.0.32 → 1.0.33
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 +55 -3
- package/lib/commands/web.js +1 -1
- package/lib/server/routes/providers.js +10 -0
- package/lib/session/session-manager.js +8 -2
- package/lib/web/codex.js +84 -2
- package/lib/web/composer.js +4 -1
- package/lib/web/core.js +4 -0
- package/lib/web/index.html +2 -0
- package/lib/web/session.js +5 -0
- package/lib/web/styles.css +14 -3
- package/package.json +1 -1
|
@@ -69,6 +69,21 @@ function textFromInputItems(content) {
|
|
|
69
69
|
.join('\n');
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
function skillsFromInputItems(content) {
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const skills = [];
|
|
75
|
+
for (const item of Array.isArray(content) ? content : []) {
|
|
76
|
+
if (!item || item.type !== 'skill') continue;
|
|
77
|
+
const name = String(item.name || '').trim();
|
|
78
|
+
const path = String(item.path || '').trim();
|
|
79
|
+
const key = `${name}\n${path}`;
|
|
80
|
+
if (!name || !path || seen.has(key)) continue;
|
|
81
|
+
seen.add(key);
|
|
82
|
+
skills.push({ name, path });
|
|
83
|
+
}
|
|
84
|
+
return skills;
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
function recentUserQuestions(thread, limit = 2) {
|
|
73
88
|
const questions = [];
|
|
74
89
|
const turns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
@@ -597,7 +612,9 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
597
612
|
...timing, toolStatus: inferredToolStatus || raw.status || 'running' }
|
|
598
613
|
: kind === 'compaction' ? { providerId, threadId, turnId, ...timing,
|
|
599
614
|
compactionStatus: inferredStatus || raw.status || 'running' }
|
|
600
|
-
: { text, threadId, turnId, streaming: false,
|
|
615
|
+
: { text, threadId, turnId, streaming: false,
|
|
616
|
+
...(kind === 'user' ? { skills: skillsFromInputItems(raw.content) } : {}),
|
|
617
|
+
...(completedAtMs ? { completedAtMs } : {}) };
|
|
601
618
|
if (existing) {
|
|
602
619
|
this.patch(existing.id, patch);
|
|
603
620
|
} else if (kind === 'user') {
|
|
@@ -630,6 +647,37 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
630
647
|
return models;
|
|
631
648
|
}
|
|
632
649
|
|
|
650
|
+
async listSkills(forceReload = false) {
|
|
651
|
+
await this.ensureProcess();
|
|
652
|
+
const result = await this.request('skills/list', {
|
|
653
|
+
cwds: [this.workingDir],
|
|
654
|
+
forceReload: Boolean(forceReload)
|
|
655
|
+
});
|
|
656
|
+
const entries = Array.isArray(result?.data) ? result.data : [];
|
|
657
|
+
const entry = entries.find(item => item?.cwd === this.workingDir) || entries[0] || {};
|
|
658
|
+
return {
|
|
659
|
+
skills: (Array.isArray(entry.skills) ? entry.skills : []).filter(item => item?.enabled !== false),
|
|
660
|
+
errors: Array.isArray(entry.errors) ? entry.errors : []
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
async resolveSkillInputs(skills) {
|
|
665
|
+
const requested = (Array.isArray(skills) ? skills : []).slice(0, 8);
|
|
666
|
+
if (!requested.length) return [];
|
|
667
|
+
const available = await this.listSkills(false);
|
|
668
|
+
const allowed = new Map(available.skills.map(item => [`${item.name}\n${item.path}`, item]));
|
|
669
|
+
const seen = new Set();
|
|
670
|
+
const resolved = [];
|
|
671
|
+
for (const item of requested) {
|
|
672
|
+
const key = `${String(item?.name || '')}\n${String(item?.path || '')}`;
|
|
673
|
+
const skill = allowed.get(key);
|
|
674
|
+
if (!skill || seen.has(key)) continue;
|
|
675
|
+
seen.add(key);
|
|
676
|
+
resolved.push({ type: 'skill', name: skill.name, path: skill.path });
|
|
677
|
+
}
|
|
678
|
+
return resolved;
|
|
679
|
+
}
|
|
680
|
+
|
|
633
681
|
async refreshConfigDefaults() {
|
|
634
682
|
const result = await this.request('config/read', { cwd: this.workingDir, includeLayers: false });
|
|
635
683
|
const config = result?.config || {};
|
|
@@ -838,7 +886,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
838
886
|
return this.getControlState();
|
|
839
887
|
}
|
|
840
888
|
|
|
841
|
-
async sendUserMessage(text, attachments = []) {
|
|
889
|
+
async sendUserMessage(text, attachments = [], skills = []) {
|
|
842
890
|
const prompt = String(text || '').trim();
|
|
843
891
|
const images = (Array.isArray(attachments) ? attachments : [])
|
|
844
892
|
.filter(item => item && typeof item.path === 'string' && item.path);
|
|
@@ -848,7 +896,10 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
848
896
|
this.append({
|
|
849
897
|
kind: 'user',
|
|
850
898
|
text: prompt || '📷 Image attachment',
|
|
851
|
-
attachments: images.map(item => ({ id: item.id, name: item.name || 'image' }))
|
|
899
|
+
attachments: images.map(item => ({ id: item.id, name: item.name || 'image' })),
|
|
900
|
+
skills: (Array.isArray(skills) ? skills : []).map(item => ({
|
|
901
|
+
name: String(item?.name || ''), path: String(item?.path || '')
|
|
902
|
+
})).filter(item => item.name && item.path)
|
|
852
903
|
});
|
|
853
904
|
try {
|
|
854
905
|
await this.ensureProcess();
|
|
@@ -867,6 +918,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
867
918
|
}
|
|
868
919
|
this.setStatus('running');
|
|
869
920
|
const input = [];
|
|
921
|
+
input.push(...await this.resolveSkillInputs(skills));
|
|
870
922
|
if (prompt) input.push({ type: 'text', text: prompt });
|
|
871
923
|
for (const image of images) input.push({ type: 'localImage', path: image.path });
|
|
872
924
|
const params = { threadId: this.threadId, input, cwd: this.workingDir, summary: 'auto' };
|
package/lib/commands/web.js
CHANGED
|
@@ -359,7 +359,7 @@ async function webCommand(options) {
|
|
|
359
359
|
sessionManager.abortClaude(sessionId);
|
|
360
360
|
}
|
|
361
361
|
if (payload.type === 'codex-input') {
|
|
362
|
-
sessionManager.sendCodexInput(sessionId, payload.text || '', payload.attachmentIds || [])
|
|
362
|
+
sessionManager.sendCodexInput(sessionId, payload.text || '', payload.attachmentIds || [], payload.skills || [])
|
|
363
363
|
.catch(error => logger.error(`Codex input error: ${error.message}`));
|
|
364
364
|
}
|
|
365
365
|
if (payload.type === 'codex-permission') {
|
|
@@ -68,6 +68,16 @@ function registerProviderRoutes(app, { sessionManager }) {
|
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
app.get('/api/sessions/:id/codex-skills', async (req, res) => {
|
|
72
|
+
try {
|
|
73
|
+
const result = await sessionManager.listCodexSkills(req.params.id, req.query?.forceReload === 'true');
|
|
74
|
+
if (!result) return res.status(404).json({ error: 'Codex session not found' });
|
|
75
|
+
res.json({ success: true, ...result });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
res.status(400).json({ error: error.message });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
71
81
|
app.post('/api/sessions/:id/codex-abort', (req, res) => {
|
|
72
82
|
const success = sessionManager.abortCodex(req.params.id);
|
|
73
83
|
if (!success) return res.status(409).json({ error: 'Codex session is idle or unavailable' });
|
|
@@ -294,14 +294,14 @@ class SessionManager extends EventEmitter {
|
|
|
294
294
|
this.scheduleImageCleanup(id, attachmentIds);
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
async sendCodexInput(id, text, attachmentIds = []) {
|
|
297
|
+
async sendCodexInput(id, text, attachmentIds = [], skills = []) {
|
|
298
298
|
const session = this.get(id);
|
|
299
299
|
if (!session || session.kind !== 'codex-structured') return false;
|
|
300
300
|
const attachments = this.getCodexImageAttachments(id, attachmentIds);
|
|
301
301
|
const prompt = String(text || '');
|
|
302
302
|
if (!prompt.trim() && attachments.length === 0) return false;
|
|
303
303
|
this.markSessionInput(session, prompt || '[image attachment]');
|
|
304
|
-
const sent = await session.sendUserMessage(prompt, attachments);
|
|
304
|
+
const sent = await session.sendUserMessage(prompt, attachments, skills);
|
|
305
305
|
if (sent && attachments.length) this.scheduleCodexImageCleanup(id, attachments.map(item => item.id));
|
|
306
306
|
return sent;
|
|
307
307
|
}
|
|
@@ -437,6 +437,12 @@ class SessionManager extends EventEmitter {
|
|
|
437
437
|
return session.listPromptHistory(options);
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
listCodexSkills(id, forceReload = false) {
|
|
441
|
+
const session = this.get(id);
|
|
442
|
+
if (!session || session.kind !== 'codex-structured') return null;
|
|
443
|
+
return session.listSkills(forceReload);
|
|
444
|
+
}
|
|
445
|
+
|
|
440
446
|
switchCodexPresentation(id, presentation) {
|
|
441
447
|
const session = this.get(id);
|
|
442
448
|
if (!session || session.kind !== 'codex-structured') return Promise.resolve(false);
|
package/lib/web/codex.js
CHANGED
|
@@ -199,10 +199,15 @@
|
|
|
199
199
|
const label = `${formatCodexTokens(remaining)} / ${formatCodexTokens(total)}(${Math.round(percent)}%)`;
|
|
200
200
|
return `<span class="codex-context-meter${level}" style="--context-remaining:${percent}%" title="${escapeHtml(`Context remaining: ${label}`)}" aria-label="${escapeHtml(`Context remaining: ${label}`)}">${escapeHtml(label)}</span>`;
|
|
201
201
|
}
|
|
202
|
+
function renderCodexMessageSkills(item) {
|
|
203
|
+
const skills = Array.isArray(item?.skills) ? item.skills : [];
|
|
204
|
+
return skills.map(skill => `<span class="codex-message-skill" title="${escapeHtml(skill.path || skill.name || '')}">Skill · ${escapeHtml(skill.name || 'unknown')}</span>`).join('');
|
|
205
|
+
}
|
|
202
206
|
function renderCodexMessageMeta(item, finalOnly = false, context = null) {
|
|
203
207
|
const time = renderCodexMessageTime(item, finalOnly);
|
|
208
|
+
const skills = renderCodexMessageSkills(item);
|
|
204
209
|
const meter = renderCodexContextMeter(context);
|
|
205
|
-
return time || meter ? `<div class="codex-message-meta">${time}${meter}</div>` : '';
|
|
210
|
+
return time || skills || meter ? `<div class="codex-message-meta">${time}${skills}${meter}</div>` : '';
|
|
206
211
|
}
|
|
207
212
|
function syncCodexDom(current, next) {
|
|
208
213
|
if (!current || !next) return;
|
|
@@ -305,9 +310,12 @@
|
|
|
305
310
|
i += 1;
|
|
306
311
|
}
|
|
307
312
|
for (const request of codexPendingPermissions) if (!usedPermissions.has(request.id)) parts.push(renderCodexPermission(request));
|
|
313
|
+
const skillBubble = selectedCodexSkill && codexState.status === 'idle'
|
|
314
|
+
? `<div class="codex-skill-bubble" role="status" aria-label="Selected skill: ${escapeHtml(selectedCodexSkill.name)}"><span class="codex-skill-bubble-name">Skill · ${escapeHtml(selectedCodexSkill.name)}</span><button type="button" class="codex-skill-bubble-close" onclick="clearCodexSkillSelection()" title="Remove selected skill" aria-label="Remove selected skill">×</button></div>`
|
|
315
|
+
: '';
|
|
308
316
|
const working = `<div class="codex-working-indicator" role="status" aria-label="Codex is working" title="Codex is working"${codexState.status === 'running' ? '' : ' style="display:none"'}></div>`;
|
|
309
317
|
const template = document.createElement('template');
|
|
310
|
-
template.innerHTML = `<div class="codex-conversation">${working}${parts.join('') || '<div class="codex-message event">Send a message to start Codex.</div>'}</div>`;
|
|
318
|
+
template.innerHTML = `<div class="codex-conversation">${skillBubble}${working}${parts.join('') || '<div class="codex-message event">Send a message to start Codex.</div>'}</div>`;
|
|
311
319
|
const next = template.content.firstElementChild;
|
|
312
320
|
const current = container.firstElementChild;
|
|
313
321
|
if (!current) container.appendChild(next);
|
|
@@ -342,6 +350,8 @@
|
|
|
342
350
|
if (abort) abort.disabled = !codexState.canAbort;
|
|
343
351
|
const compact = document.getElementById('codex-compact-btn');
|
|
344
352
|
if (compact) { compact.disabled = !codexState.canCompact; compact.textContent = codexState.compacting ? 'Compacting' : 'Compact'; }
|
|
353
|
+
const skills = document.getElementById('codex-skills-btn');
|
|
354
|
+
if (skills) { skills.disabled = !(codexState.presentation === 'structured' && codexState.status === 'idle'); skills.classList.toggle('primary', Boolean(selectedCodexSkill)); }
|
|
345
355
|
const fork = document.getElementById('codex-fork-btn');
|
|
346
356
|
if (fork) fork.disabled = !(codexState.presentation === 'structured' && codexState.status === 'idle');
|
|
347
357
|
const terminal = document.getElementById('codex-terminal-switch');
|
|
@@ -400,10 +410,12 @@
|
|
|
400
410
|
codexResumePanelOpen = false;
|
|
401
411
|
codexForkPanelOpen = false;
|
|
402
412
|
codexPromptPanelOpen = false;
|
|
413
|
+
codexSkillPanelOpen = false;
|
|
403
414
|
codexModelCandidate = codexState.model || codexState.models?.[0]?.id || null;
|
|
404
415
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
405
416
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
406
417
|
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
418
|
+
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
407
419
|
renderCodexModelPanel();
|
|
408
420
|
updateTerminalControlsHeight();
|
|
409
421
|
}
|
|
@@ -460,9 +472,11 @@
|
|
|
460
472
|
codexModelPanelOpen = false;
|
|
461
473
|
codexForkPanelOpen = false;
|
|
462
474
|
codexPromptPanelOpen = false;
|
|
475
|
+
codexSkillPanelOpen = false;
|
|
463
476
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
464
477
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
465
478
|
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
479
|
+
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
466
480
|
const panel = document.getElementById('codex-resume-panel');
|
|
467
481
|
panel.classList.toggle('active', codexResumePanelOpen);
|
|
468
482
|
updateTerminalControlsHeight();
|
|
@@ -475,9 +489,11 @@
|
|
|
475
489
|
codexModelPanelOpen = false;
|
|
476
490
|
codexResumePanelOpen = false;
|
|
477
491
|
codexPromptPanelOpen = false;
|
|
492
|
+
codexSkillPanelOpen = false;
|
|
478
493
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
479
494
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
480
495
|
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
496
|
+
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
481
497
|
const panel = document.getElementById('codex-fork-panel');
|
|
482
498
|
panel.classList.toggle('active', codexForkPanelOpen);
|
|
483
499
|
updateTerminalControlsHeight();
|
|
@@ -531,9 +547,11 @@
|
|
|
531
547
|
codexModelPanelOpen = false;
|
|
532
548
|
codexResumePanelOpen = false;
|
|
533
549
|
codexForkPanelOpen = false;
|
|
550
|
+
codexSkillPanelOpen = false;
|
|
534
551
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
535
552
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
536
553
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
554
|
+
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
537
555
|
if (!codexPromptPanelOpen) {
|
|
538
556
|
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
539
557
|
updateTerminalControlsHeight();
|
|
@@ -584,6 +602,70 @@
|
|
|
584
602
|
alert('Copy failed.');
|
|
585
603
|
}
|
|
586
604
|
}
|
|
605
|
+
|
|
606
|
+
function renderCodexSkillPanel(error = '') {
|
|
607
|
+
const panel = document.getElementById('codex-skill-panel');
|
|
608
|
+
if (!panel) return;
|
|
609
|
+
panel.classList.toggle('active', codexSkillPanelOpen);
|
|
610
|
+
if (!codexSkillPanelOpen) { panel.innerHTML = ''; return; }
|
|
611
|
+
const items = codexSkillItems.map((item, index) => {
|
|
612
|
+
const selected = selectedCodexSkill?.name === item.name && selectedCodexSkill?.path === item.path;
|
|
613
|
+
const description = item.interface?.shortDescription || item.shortDescription || item.description || '';
|
|
614
|
+
return `<button type="button" class="codex-skill-item${selected ? ' selected' : ''}" onclick="selectCodexSkill(${index})"><span class="codex-skill-item-name"><span>${escapeHtml(item.interface?.displayName || item.name)}</span><span class="codex-skill-scope">${escapeHtml(item.scope || '')}</span></span>${description ? `<span class="codex-skill-description">${escapeHtml(description)}</span>` : ''}</button>`;
|
|
615
|
+
}).join('');
|
|
616
|
+
const empty = !items && !codexSkillLoading && !error
|
|
617
|
+
? '<div class="claude-resume-meta" style="padding:12px;">No enabled skills found for this folder.</div>' : '';
|
|
618
|
+
panel.innerHTML = `<div class="codex-skill-header"><span>Available skills</span><span>${codexSkillLoading ? 'Loading…' : codexSkillItems.length}</span></div>${error ? `<div class="claude-resume-meta" style="padding:12px;color:#ff6b61;">${escapeHtml(error)}</div>` : ''}${items}${empty}`;
|
|
619
|
+
updateTerminalControlsHeight();
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
async function toggleCodexSkillPanel() {
|
|
623
|
+
if (!(codexState.presentation === 'structured' && codexState.status === 'idle')) return;
|
|
624
|
+
codexSkillPanelOpen = !codexSkillPanelOpen;
|
|
625
|
+
codexModelPanelOpen = false;
|
|
626
|
+
codexResumePanelOpen = false;
|
|
627
|
+
codexForkPanelOpen = false;
|
|
628
|
+
codexPromptPanelOpen = false;
|
|
629
|
+
document.getElementById('codex-model-panel').classList.remove('active');
|
|
630
|
+
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
631
|
+
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
632
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
633
|
+
if (!codexSkillPanelOpen) {
|
|
634
|
+
renderCodexSkillPanel();
|
|
635
|
+
updateTerminalControlsHeight();
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
codexSkillLoading = true;
|
|
639
|
+
codexSkillItems = [];
|
|
640
|
+
renderCodexSkillPanel();
|
|
641
|
+
try {
|
|
642
|
+
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-skills?forceReload=true`, {}, 30000);
|
|
643
|
+
const data = await res.json();
|
|
644
|
+
if (!res.ok || !data.success) throw new Error(data.error || 'Failed to load skills');
|
|
645
|
+
codexSkillItems = data.skills || [];
|
|
646
|
+
codexSkillLoading = false;
|
|
647
|
+
renderCodexSkillPanel((data.errors || []).map(item => item.message || String(item)).filter(Boolean).join(' · '));
|
|
648
|
+
} catch (error) {
|
|
649
|
+
codexSkillLoading = false;
|
|
650
|
+
renderCodexSkillPanel(error.message);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function selectCodexSkill(index) {
|
|
655
|
+
const skill = codexSkillItems[index];
|
|
656
|
+
if (!skill) return;
|
|
657
|
+
const alreadySelected = selectedCodexSkill?.name === skill.name && selectedCodexSkill?.path === skill.path;
|
|
658
|
+
selectedCodexSkill = alreadySelected ? null : { name: skill.name, path: skill.path };
|
|
659
|
+
codexSkillPanelOpen = false;
|
|
660
|
+
renderCodexSkillPanel();
|
|
661
|
+
applyCodexState({});
|
|
662
|
+
updateTerminalControlsHeight();
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function clearCodexSkillSelection() {
|
|
666
|
+
selectedCodexSkill = null;
|
|
667
|
+
applyCodexState({});
|
|
668
|
+
}
|
|
587
669
|
async function selectCodexResumeThread(threadId) {
|
|
588
670
|
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-resume`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ threadId }) }, 30000);
|
|
589
671
|
if (!res.ok) { alert((await res.json()).error || 'Unable to resume Codex thread'); return; }
|
package/lib/web/composer.js
CHANGED
|
@@ -210,13 +210,16 @@
|
|
|
210
210
|
currentSocket.send(JSON.stringify({
|
|
211
211
|
type: 'codex-input',
|
|
212
212
|
text: val,
|
|
213
|
-
attachmentIds: readyImageAttachments.map(item => item.id)
|
|
213
|
+
attachmentIds: readyImageAttachments.map(item => item.id),
|
|
214
|
+
skills: selectedCodexSkill ? [{ name: selectedCodexSkill.name, path: selectedCodexSkill.path }] : []
|
|
214
215
|
}));
|
|
215
216
|
}
|
|
216
217
|
inputEl.value = '';
|
|
217
218
|
inputEl.style.height = '38px';
|
|
218
219
|
selectedImageAttachments = [];
|
|
220
|
+
selectedCodexSkill = null;
|
|
219
221
|
renderImageAttachments();
|
|
222
|
+
renderCodexChat();
|
|
220
223
|
return;
|
|
221
224
|
}
|
|
222
225
|
if (val) {
|
package/lib/web/core.js
CHANGED
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
let codexPromptTotal = 0;
|
|
45
45
|
let codexPromptLoading = false;
|
|
46
46
|
let codexExpandedPrompts = new Set();
|
|
47
|
+
let codexSkillPanelOpen = false;
|
|
48
|
+
let codexSkillItems = [];
|
|
49
|
+
let codexSkillLoading = false;
|
|
50
|
+
let selectedCodexSkill = null;
|
|
47
51
|
let codexRenderFrame = null;
|
|
48
52
|
let codexApprovalJumpIndex = 0;
|
|
49
53
|
const modifiers = { ctrl: false };
|
package/lib/web/index.html
CHANGED
|
@@ -136,6 +136,7 @@
|
|
|
136
136
|
<option value="default">Default</option><option value="untrusted">Untrusted</option><option value="on-request">On request</option><option value="never">Never ask</option>
|
|
137
137
|
</select>
|
|
138
138
|
</label>
|
|
139
|
+
<button id="codex-skills-btn" class="claude-ctrl-btn" onclick="toggleCodexSkillPanel()" title="Choose a skill for the next message">Skills</button>
|
|
139
140
|
</div>
|
|
140
141
|
</div>
|
|
141
142
|
<div id="codex-state-bar"></div>
|
|
@@ -143,6 +144,7 @@
|
|
|
143
144
|
<div id="codex-resume-panel"></div>
|
|
144
145
|
<div id="codex-fork-panel"></div>
|
|
145
146
|
<div id="codex-prompt-panel"></div>
|
|
147
|
+
<div id="codex-skill-panel"></div>
|
|
146
148
|
</div>
|
|
147
149
|
<div id="timed-send-panel">
|
|
148
150
|
<div class="timed-row">
|
package/lib/web/session.js
CHANGED
|
@@ -137,10 +137,15 @@
|
|
|
137
137
|
codexPromptTotal = 0;
|
|
138
138
|
codexPromptLoading = false;
|
|
139
139
|
codexExpandedPrompts = new Set();
|
|
140
|
+
codexSkillPanelOpen = false;
|
|
141
|
+
codexSkillItems = [];
|
|
142
|
+
codexSkillLoading = false;
|
|
143
|
+
selectedCodexSkill = null;
|
|
140
144
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
141
145
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
142
146
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
143
147
|
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
148
|
+
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
144
149
|
document.getElementById('codex-control-rail').scrollLeft = 0;
|
|
145
150
|
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 };
|
|
146
151
|
setClaudeModeEnabled(false);
|
package/lib/web/styles.css
CHANGED
|
@@ -88,12 +88,15 @@
|
|
|
88
88
|
.codex-conversation { width: min(100%, var(--chat-content-max)); margin: 0 auto; }
|
|
89
89
|
.codex-working-indicator, .claude-working-indicator { position: sticky; top: 0; z-index: 4; width: 28px; height: 28px; margin: 0 0 -28px auto; border: 1px solid rgba(255,255,255,.1); border-radius: 50%; background: rgba(28,28,30,.68); box-shadow: 0 5px 16px rgba(0,0,0,.24); backdrop-filter: blur(8px); pointer-events: none; }
|
|
90
90
|
.codex-working-indicator::after, .claude-working-indicator::after { content: ''; position: absolute; inset: 8px; border: 1.5px solid rgba(255,255,255,.7); border-top-color: transparent; border-radius: 50%; animation: codex-spin .8s linear infinite; }
|
|
91
|
+
.codex-skill-bubble { position: sticky; top: 0; z-index: 5; display: flex; width: max-content; max-width: min(280px, 72vw); min-height: 28px; margin: 0 0 -30px auto; padding: 0 4px 0 10px; align-items: center; gap: 7px; border: 1px solid rgba(0,122,255,.42); border-radius: 15px; background: rgba(21,47,78,.92); color: #eaf3ff; box-shadow: 0 5px 18px rgba(0,0,0,.3); backdrop-filter: blur(10px); font-size: 11px; font-weight: 750; }
|
|
92
|
+
.codex-skill-bubble-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
93
|
+
.codex-skill-bubble-close { width: 23px; height: 23px; padding: 0; border: 0; border-radius: 50%; background: rgba(255,255,255,.1); color: #fff; cursor: pointer; font-size: 16px; line-height: 1; }
|
|
91
94
|
.codex-message-block { max-width: 100%; margin: 0 0 12px; }
|
|
92
95
|
.codex-message-block.user { max-width: 92%; margin-left: auto; }
|
|
93
96
|
.codex-message { max-width: 100%; margin: 0; color: #f5f5f7; font-size: 14px; line-height: 1.45; overflow-wrap: anywhere; }
|
|
94
97
|
.codex-message.user { padding: 8px 12px; border: 1px solid rgba(0,122,255,.32); border-radius: 12px; background: rgba(0,122,255,.24); }
|
|
95
98
|
.codex-message.event { color: var(--text-dim); text-align: center; font-size: 12px; }
|
|
96
|
-
.codex-message-meta { display: flex; width: 100%; align-items: center; gap: 7px; margin-top: 4px; padding: 0 2px; box-sizing: border-box; }
|
|
99
|
+
.codex-message-meta { display: flex; width: 100%; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 4px; padding: 0 2px; box-sizing: border-box; }
|
|
97
100
|
.codex-message-block.user .codex-message-meta { justify-content: flex-end; }
|
|
98
101
|
.codex-message-time { display: block; flex: 0 0 auto; width: max-content; color: #8e8e93; font-size: 10px; font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums; }
|
|
99
102
|
.codex-context-meter { --context-color: #30d158; position: relative; isolation: isolate; display: inline-flex; min-width: 128px; height: 16px; align-items: center; justify-content: center; overflow: hidden; border: 1px solid color-mix(in srgb, var(--context-color) 42%, transparent); border-radius: 8px; background: rgba(255,255,255,.055); color: #d9fbe2; font: 700 9px/1 ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
@@ -177,8 +180,8 @@
|
|
|
177
180
|
.codex-select-control:focus-within .codex-select-label { background: rgba(255,255,255,.14); border-color: rgba(0,122,255,.45); }
|
|
178
181
|
.codex-select { appearance: none; position: absolute; inset: 0; width: 100%; min-width: 0; height: 100%; opacity: 0; cursor: pointer; }
|
|
179
182
|
.codex-select option { background: #1c1c1e; color: #fff; }
|
|
180
|
-
#codex-model-panel, #codex-resume-panel, #codex-fork-panel, #codex-prompt-panel { display: none; margin-top: 8px; border: 1px solid rgba(255,255,255,.1); background: rgba(28,28,30,.99); border-radius: 8px; box-shadow: 0 16px 36px rgba(0,0,0,.34); overflow: hidden; }
|
|
181
|
-
#codex-model-panel.active, #codex-resume-panel.active, #codex-fork-panel.active, #codex-prompt-panel.active { display: block; }
|
|
183
|
+
#codex-model-panel, #codex-resume-panel, #codex-fork-panel, #codex-prompt-panel, #codex-skill-panel { display: none; margin-top: 8px; border: 1px solid rgba(255,255,255,.1); background: rgba(28,28,30,.99); border-radius: 8px; box-shadow: 0 16px 36px rgba(0,0,0,.34); overflow: hidden; }
|
|
184
|
+
#codex-model-panel.active, #codex-resume-panel.active, #codex-fork-panel.active, #codex-prompt-panel.active, #codex-skill-panel.active { display: block; }
|
|
182
185
|
#codex-model-panel { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); max-height: min(300px, 42dvh); }
|
|
183
186
|
#codex-model-panel.active { display: grid; }
|
|
184
187
|
.codex-picker-column { min-width: 0; overflow-y: auto; }
|
|
@@ -189,6 +192,13 @@
|
|
|
189
192
|
#codex-state-bar::-webkit-scrollbar { display: none; }
|
|
190
193
|
#codex-resume-panel, #codex-fork-panel { max-height: min(280px, 38dvh); overflow-y: auto; }
|
|
191
194
|
#codex-prompt-panel { max-height: min(440px, 52dvh); overflow-y: auto; }
|
|
195
|
+
#codex-skill-panel { max-height: min(360px, 46dvh); overflow-y: auto; }
|
|
196
|
+
.codex-skill-header { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 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; }
|
|
197
|
+
.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
|
+
.codex-skill-item.selected { background: rgba(0,122,255,.18); }
|
|
199
|
+
.codex-skill-item-name { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; font-weight: 800; }
|
|
200
|
+
.codex-skill-scope { color: #79b8ff; font-size: 10px; text-transform: uppercase; }
|
|
201
|
+
.codex-skill-description { display: block; margin-top: 4px; color: #a9a9b0; font-size: 11px; line-height: 1.35; white-space: normal; overflow-wrap: anywhere; }
|
|
192
202
|
.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; }
|
|
193
203
|
.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); }
|
|
194
204
|
.codex-prompt-item:last-of-type { border-bottom: 0; }
|
|
@@ -206,6 +216,7 @@
|
|
|
206
216
|
.claude-message-block.user > .claude-message { max-width: 100%; }
|
|
207
217
|
.claude-message-time { display: block; width: max-content; margin-top: 4px; padding: 0 2px; color: #8e8e93; font-size: 10px; font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums; }
|
|
208
218
|
.claude-message-block.user .claude-message-time { margin-left: auto; }
|
|
219
|
+
.codex-message-skill { display: inline-flex; max-width: min(220px, 58vw); min-height: 16px; align-items: center; padding: 1px 6px; border: 1px solid rgba(0,122,255,.32); border-radius: 999px; background: rgba(0,122,255,.12); color: #8fc5ff; font-size: 9px; font-weight: 750; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
209
220
|
.claude-message-attachments { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
|
|
210
221
|
.claude-message-attachment { max-width: 100%; border: 1px solid rgba(255,255,255,.14); border-radius: 999px; padding: 3px 7px; color: #d1d5db; background: rgba(255,255,255,.07); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
211
222
|
.claude-message.user { margin-left: auto; background: rgba(0,122,255,0.24); border: 1px solid rgba(0,122,255,0.32); color: #fff; border-radius: 12px; padding-top: 7px; padding-bottom: 7px; }
|