glad-web 1.0.31 → 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 +180 -18
- package/lib/commands/web.js +1 -1
- package/lib/server/routes/providers.js +23 -0
- package/lib/session/session-manager.js +14 -2
- package/lib/web/codex.js +179 -2
- package/lib/web/composer.js +4 -1
- package/lib/web/core.js +11 -0
- package/lib/web/index.html +4 -0
- package/lib/web/session.js +13 -0
- package/lib/web/styles.css +29 -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 : [];
|
|
@@ -85,6 +100,27 @@ function recentUserQuestions(thread, limit = 2) {
|
|
|
85
100
|
return questions;
|
|
86
101
|
}
|
|
87
102
|
|
|
103
|
+
function userPromptsFromThread(thread, fallbackTimestamp = null) {
|
|
104
|
+
const prompts = [];
|
|
105
|
+
const threadId = String(thread?.id || '');
|
|
106
|
+
for (const turn of Array.isArray(thread?.turns) ? thread.turns : []) {
|
|
107
|
+
const turnTimestamp = toTimestampMs(turn.startedAt || turn.createdAt || turn.completedAt || turn.updatedAt)
|
|
108
|
+
|| fallbackTimestamp;
|
|
109
|
+
for (const item of Array.isArray(turn?.items) ? turn.items : []) {
|
|
110
|
+
if (item?.type !== 'userMessage') continue;
|
|
111
|
+
const prompt = (textFromInputItems(item.content) || item.text || '').trim();
|
|
112
|
+
if (!prompt) continue;
|
|
113
|
+
prompts.push({
|
|
114
|
+
id: String(item.id || `${threadId}:${turn.id || 'turn'}:${prompts.length}`),
|
|
115
|
+
threadId,
|
|
116
|
+
text: prompt,
|
|
117
|
+
createdAt: toTimestampMs(item.createdAt || item.updatedAt) || turnTimestamp || null
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return prompts;
|
|
122
|
+
}
|
|
123
|
+
|
|
88
124
|
function toolDetails(raw) {
|
|
89
125
|
if (raw.type === 'commandExecution') {
|
|
90
126
|
return {
|
|
@@ -195,6 +231,8 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
195
231
|
this.inputSeq = 0;
|
|
196
232
|
this.completionReadInputSeq = 0;
|
|
197
233
|
this.timedInputs = new Map();
|
|
234
|
+
this.promptHistoryCache = null;
|
|
235
|
+
this.deferredWarnings = null;
|
|
198
236
|
this.ptyManager = {
|
|
199
237
|
workingDir,
|
|
200
238
|
isRunning: () => this.isRunning(),
|
|
@@ -491,7 +529,9 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
491
529
|
return;
|
|
492
530
|
}
|
|
493
531
|
if (method === 'warning' || method === 'guardianWarning') {
|
|
494
|
-
|
|
532
|
+
const warning = { kind: 'event', level: 'warning', text: params.message || params.warning || 'Codex warning.' };
|
|
533
|
+
if (this.deferredWarnings) this.deferredWarnings.push(warning);
|
|
534
|
+
else this.append(warning);
|
|
495
535
|
return;
|
|
496
536
|
}
|
|
497
537
|
if (method === 'item/commandExecution/outputDelta' || method === 'item/fileChange/outputDelta') {
|
|
@@ -572,7 +612,9 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
572
612
|
...timing, toolStatus: inferredToolStatus || raw.status || 'running' }
|
|
573
613
|
: kind === 'compaction' ? { providerId, threadId, turnId, ...timing,
|
|
574
614
|
compactionStatus: inferredStatus || raw.status || 'running' }
|
|
575
|
-
: { text, threadId, turnId, streaming: false,
|
|
615
|
+
: { text, threadId, turnId, streaming: false,
|
|
616
|
+
...(kind === 'user' ? { skills: skillsFromInputItems(raw.content) } : {}),
|
|
617
|
+
...(completedAtMs ? { completedAtMs } : {}) };
|
|
576
618
|
if (existing) {
|
|
577
619
|
this.patch(existing.id, patch);
|
|
578
620
|
} else if (kind === 'user') {
|
|
@@ -605,6 +647,37 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
605
647
|
return models;
|
|
606
648
|
}
|
|
607
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
|
+
|
|
608
681
|
async refreshConfigDefaults() {
|
|
609
682
|
const result = await this.request('config/read', { cwd: this.workingDir, includeLayers: false });
|
|
610
683
|
const config = result?.config || {};
|
|
@@ -655,6 +728,68 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
655
728
|
return items;
|
|
656
729
|
}
|
|
657
730
|
|
|
731
|
+
async listPromptHistory({ offset = 0, limit = 30 } = {}) {
|
|
732
|
+
await this.ensureProcess();
|
|
733
|
+
const safeOffset = Math.max(0, Math.min(199, Number(offset) || 0));
|
|
734
|
+
const safeLimit = Math.max(1, Math.min(30, Number(limit) || 30));
|
|
735
|
+
const cacheFresh = this.promptHistoryCache
|
|
736
|
+
&& Date.now() - this.promptHistoryCache.loadedAt < 15000;
|
|
737
|
+
|
|
738
|
+
if (!cacheFresh) {
|
|
739
|
+
const prompts = [];
|
|
740
|
+
let cursor = null;
|
|
741
|
+
let pageCount = 0;
|
|
742
|
+
let capped = false;
|
|
743
|
+
do {
|
|
744
|
+
const result = await this.request('thread/list', {
|
|
745
|
+
cursor,
|
|
746
|
+
limit: 20,
|
|
747
|
+
sortKey: 'updated_at',
|
|
748
|
+
sortDirection: 'desc',
|
|
749
|
+
archived: false,
|
|
750
|
+
cwd: this.workingDir
|
|
751
|
+
});
|
|
752
|
+
const threads = (result?.data || []).filter(item => !item.parentThreadId);
|
|
753
|
+
const histories = await Promise.all(threads.map(async item => {
|
|
754
|
+
try {
|
|
755
|
+
const history = await this.request('thread/read', { threadId: item.id, includeTurns: true });
|
|
756
|
+
const fallbackTimestamp = toTimestampMs(item.updatedAt || item.createdAt);
|
|
757
|
+
return userPromptsFromThread(history?.thread || { id: item.id, turns: [] }, fallbackTimestamp)
|
|
758
|
+
.map(prompt => ({ ...prompt, threadId: prompt.threadId || item.id }));
|
|
759
|
+
} catch (error) {
|
|
760
|
+
this.logger.debugInfo?.(`[codex-app-server] unable to read prompt history for ${item.id}: ${error.message}`);
|
|
761
|
+
return [];
|
|
762
|
+
}
|
|
763
|
+
}));
|
|
764
|
+
prompts.push(...histories.flat());
|
|
765
|
+
cursor = result?.nextCursor || null;
|
|
766
|
+
pageCount += 1;
|
|
767
|
+
if (prompts.length >= 200 || pageCount >= 5) {
|
|
768
|
+
capped = Boolean(cursor) || prompts.length > 200;
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
} while (cursor);
|
|
772
|
+
|
|
773
|
+
prompts.sort((a, b) => Number(b.createdAt || 0) - Number(a.createdAt || 0));
|
|
774
|
+
this.promptHistoryCache = {
|
|
775
|
+
loadedAt: Date.now(),
|
|
776
|
+
items: prompts.slice(0, 200),
|
|
777
|
+
capped
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const items = this.promptHistoryCache.items.slice(safeOffset, safeOffset + safeLimit);
|
|
782
|
+
const nextOffset = safeOffset + items.length;
|
|
783
|
+
return {
|
|
784
|
+
items,
|
|
785
|
+
offset: safeOffset,
|
|
786
|
+
nextOffset,
|
|
787
|
+
total: this.promptHistoryCache.items.length,
|
|
788
|
+
hasMore: nextOffset < this.promptHistoryCache.items.length,
|
|
789
|
+
capped: this.promptHistoryCache.capped
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
658
793
|
contextStatus(tokenUsage = this.tokenUsage) {
|
|
659
794
|
const usage = tokenUsage || {};
|
|
660
795
|
const selectedModel = this.models.find(item => item.id === this.model);
|
|
@@ -751,16 +886,20 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
751
886
|
return this.getControlState();
|
|
752
887
|
}
|
|
753
888
|
|
|
754
|
-
async sendUserMessage(text, attachments = []) {
|
|
889
|
+
async sendUserMessage(text, attachments = [], skills = []) {
|
|
755
890
|
const prompt = String(text || '').trim();
|
|
756
891
|
const images = (Array.isArray(attachments) ? attachments : [])
|
|
757
892
|
.filter(item => item && typeof item.path === 'string' && item.path);
|
|
758
893
|
if ((!prompt && images.length === 0) || this.presentation !== 'structured' || this.status !== 'idle') return false;
|
|
759
894
|
this.hasUnreadCompletion = false;
|
|
895
|
+
this.promptHistoryCache = null;
|
|
760
896
|
this.append({
|
|
761
897
|
kind: 'user',
|
|
762
898
|
text: prompt || '📷 Image attachment',
|
|
763
|
-
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)
|
|
764
903
|
});
|
|
765
904
|
try {
|
|
766
905
|
await this.ensureProcess();
|
|
@@ -779,6 +918,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
779
918
|
}
|
|
780
919
|
this.setStatus('running');
|
|
781
920
|
const input = [];
|
|
921
|
+
input.push(...await this.resolveSkillInputs(skills));
|
|
782
922
|
if (prompt) input.push({ type: 'text', text: prompt });
|
|
783
923
|
for (const image of images) input.push({ type: 'localImage', path: image.path });
|
|
784
924
|
const params = { threadId: this.threadId, input, cwd: this.workingDir, summary: 'auto' };
|
|
@@ -882,25 +1022,46 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
882
1022
|
const target = String(threadId || this.threadId || '').trim();
|
|
883
1023
|
if (!target || this.presentation !== 'structured' || this.status !== 'idle') return false;
|
|
884
1024
|
await this.ensureProcess();
|
|
1025
|
+
const selectedModel = this.model;
|
|
1026
|
+
const selectedEffort = this.effort;
|
|
1027
|
+
const resumeWithModelOverride = Boolean(this.hasModelOverride && selectedModel);
|
|
1028
|
+
const resumeWithEffortOverride = Boolean(this.hasEffortOverride && selectedEffort);
|
|
885
1029
|
const params = { threadId: target, cwd: this.workingDir };
|
|
886
1030
|
if (this.permissionMode) params.approvalPolicy = this.permissionMode;
|
|
887
1031
|
if (this.sandboxMode) params.sandbox = this.sandboxMode;
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
this.
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1032
|
+
if (resumeWithModelOverride) params.model = selectedModel;
|
|
1033
|
+
if (resumeWithEffortOverride) params.config = { model_reasoning_effort: selectedEffort };
|
|
1034
|
+
this.deferredWarnings = [];
|
|
1035
|
+
try {
|
|
1036
|
+
const result = await this.request('thread/resume', params);
|
|
1037
|
+
this.threadId = result.thread?.id || target;
|
|
1038
|
+
this.hasModelOverride = resumeWithModelOverride;
|
|
1039
|
+
this.hasEffortOverride = resumeWithEffortOverride;
|
|
1040
|
+
this.model = result.model || result.thread?.model || selectedModel;
|
|
1041
|
+
this.effort = result.reasoningEffort || result.thread?.reasoningEffort || selectedEffort;
|
|
1042
|
+
this.effectivePermissionMode = result.approvalPolicy || this.effectivePermissionMode;
|
|
1043
|
+
this.effectiveSandboxMode = sandboxModeFromPolicy(result.sandbox) || this.effectiveSandboxMode;
|
|
1044
|
+
const history = await this.request('thread/read', { threadId: this.threadId, includeTurns: true });
|
|
1045
|
+
this.restoreThreadHistory(history?.thread, `Resumed Codex thread ${this.threadId}`, {
|
|
1046
|
+
preserveModel: resumeWithModelOverride,
|
|
1047
|
+
preserveEffort: resumeWithEffortOverride
|
|
1048
|
+
});
|
|
1049
|
+
const warnings = this.deferredWarnings;
|
|
1050
|
+
this.deferredWarnings = null;
|
|
1051
|
+
for (const warning of warnings) this.append(warning);
|
|
1052
|
+
this.promptHistoryCache = null;
|
|
1053
|
+
return true;
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
const warnings = this.deferredWarnings || [];
|
|
1056
|
+
this.deferredWarnings = null;
|
|
1057
|
+
for (const warning of warnings) this.append(warning);
|
|
1058
|
+
throw error;
|
|
1059
|
+
}
|
|
899
1060
|
}
|
|
900
1061
|
|
|
901
|
-
restoreThreadHistory(thread, eventText = '') {
|
|
902
|
-
this.model = thread?.model || this.model;
|
|
903
|
-
this.effort = thread?.reasoningEffort || thread?.reasoning_effort || this.effort;
|
|
1062
|
+
restoreThreadHistory(thread, eventText = '', options = {}) {
|
|
1063
|
+
if (!options.preserveModel) this.model = thread?.model || this.model;
|
|
1064
|
+
if (!options.preserveEffort) this.effort = thread?.reasoningEffort || thread?.reasoning_effort || this.effort;
|
|
904
1065
|
this.tokenUsage = thread?.tokenUsage || thread?.token_usage || this.tokenUsage;
|
|
905
1066
|
this.messages = [];
|
|
906
1067
|
this.completedPermissions = [];
|
|
@@ -948,6 +1109,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
948
1109
|
this.effectivePermissionMode = result.approvalPolicy || this.effectivePermissionMode;
|
|
949
1110
|
this.effectiveSandboxMode = sandboxModeFromPolicy(result.sandbox) || this.effectiveSandboxMode;
|
|
950
1111
|
this.restoreThreadHistory(forkedThread, `Forked from Codex thread ${sourceThreadId}`);
|
|
1112
|
+
this.promptHistoryCache = null;
|
|
951
1113
|
return { threadId: this.threadId };
|
|
952
1114
|
}
|
|
953
1115
|
|
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') {
|
|
@@ -55,6 +55,29 @@ function registerProviderRoutes(app, { sessionManager }) {
|
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
app.get('/api/sessions/:id/codex-prompts', async (req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
const result = await sessionManager.listCodexPrompts(req.params.id, {
|
|
61
|
+
offset: req.query?.offset,
|
|
62
|
+
limit: req.query?.limit
|
|
63
|
+
});
|
|
64
|
+
if (!result) return res.status(404).json({ error: 'Codex session not found' });
|
|
65
|
+
res.json({ success: true, ...result });
|
|
66
|
+
} catch (error) {
|
|
67
|
+
res.status(400).json({ error: error.message });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
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
|
+
|
|
58
81
|
app.post('/api/sessions/:id/codex-abort', (req, res) => {
|
|
59
82
|
const success = sessionManager.abortCodex(req.params.id);
|
|
60
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
|
}
|
|
@@ -431,6 +431,18 @@ class SessionManager extends EventEmitter {
|
|
|
431
431
|
return session.listResumeThreads();
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
listCodexPrompts(id, options) {
|
|
435
|
+
const session = this.get(id);
|
|
436
|
+
if (!session || session.kind !== 'codex-structured') return null;
|
|
437
|
+
return session.listPromptHistory(options);
|
|
438
|
+
}
|
|
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
|
+
|
|
434
446
|
switchCodexPresentation(id, presentation) {
|
|
435
447
|
const session = this.get(id);
|
|
436
448
|
if (!session || session.kind !== 'codex-structured') return Promise.resolve(false);
|
package/lib/web/codex.js
CHANGED
|
@@ -103,6 +103,9 @@
|
|
|
103
103
|
const detail = running ? 'Compacting context…' : ['Completed', time].filter(Boolean).join(' · ');
|
|
104
104
|
return `<div class="codex-compaction-card${running ? ' running' : ''}" data-codex-key="compaction-${escapeHtml(item.id || item.providerId || '')}"><span class="codex-compaction-icon" aria-hidden="true">⇣</span><div><div class="codex-compaction-title">${running ? 'Compacting context' : 'Context compacted'}</div><div class="codex-compaction-meta">${escapeHtml(detail)}</div></div></div>`;
|
|
105
105
|
}
|
|
106
|
+
function renderCodexWarning(item) {
|
|
107
|
+
return `<div class="codex-warning-card" data-codex-key="warning-${escapeHtml(item.id || '')}" role="status"><span class="codex-warning-icon" aria-hidden="true">!</span><div><div class="codex-warning-title">Codex warning</div><div class="codex-warning-text">${codexText(item.text || 'Codex reported a warning.')}</div></div></div>`;
|
|
108
|
+
}
|
|
106
109
|
function renderCodexTool(item, permission = null) {
|
|
107
110
|
const status = codexToolStatus(item);
|
|
108
111
|
const isError = status === 'failed' || Boolean(item.error) || (item.exitCode != null && item.exitCode !== 0);
|
|
@@ -196,10 +199,15 @@
|
|
|
196
199
|
const label = `${formatCodexTokens(remaining)} / ${formatCodexTokens(total)}(${Math.round(percent)}%)`;
|
|
197
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>`;
|
|
198
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
|
+
}
|
|
199
206
|
function renderCodexMessageMeta(item, finalOnly = false, context = null) {
|
|
200
207
|
const time = renderCodexMessageTime(item, finalOnly);
|
|
208
|
+
const skills = renderCodexMessageSkills(item);
|
|
201
209
|
const meter = renderCodexContextMeter(context);
|
|
202
|
-
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>` : '';
|
|
203
211
|
}
|
|
204
212
|
function syncCodexDom(current, next) {
|
|
205
213
|
if (!current || !next) return;
|
|
@@ -297,13 +305,17 @@
|
|
|
297
305
|
else if (item.kind === 'user') parts.push(`<div class="codex-message-block user" data-codex-key="message-${escapeHtml(item.id || '')}"><div class="codex-message user claude-md">${renderMarkdown(item.text || '')}</div>${renderCodexMessageMeta(item)}</div>`);
|
|
298
306
|
else if (item.kind === 'compaction') parts.push(renderCodexCompaction(item));
|
|
299
307
|
else if (item.kind === 'status') parts.push(renderCodexStatus(item));
|
|
308
|
+
else if (item.kind === 'event' && item.level === 'warning') parts.push(renderCodexWarning(item));
|
|
300
309
|
else parts.push(`<div class="codex-message ${escapeHtml(item.level === 'error' ? 'event error' : item.kind || 'event')}">${codexText(item.text)}</div>`);
|
|
301
310
|
i += 1;
|
|
302
311
|
}
|
|
303
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
|
+
: '';
|
|
304
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>`;
|
|
305
317
|
const template = document.createElement('template');
|
|
306
|
-
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>`;
|
|
307
319
|
const next = template.content.firstElementChild;
|
|
308
320
|
const current = container.firstElementChild;
|
|
309
321
|
if (!current) container.appendChild(next);
|
|
@@ -338,6 +350,8 @@
|
|
|
338
350
|
if (abort) abort.disabled = !codexState.canAbort;
|
|
339
351
|
const compact = document.getElementById('codex-compact-btn');
|
|
340
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)); }
|
|
341
355
|
const fork = document.getElementById('codex-fork-btn');
|
|
342
356
|
if (fork) fork.disabled = !(codexState.presentation === 'structured' && codexState.status === 'idle');
|
|
343
357
|
const terminal = document.getElementById('codex-terminal-switch');
|
|
@@ -395,9 +409,13 @@
|
|
|
395
409
|
codexModelPanelOpen = !codexModelPanelOpen;
|
|
396
410
|
codexResumePanelOpen = false;
|
|
397
411
|
codexForkPanelOpen = false;
|
|
412
|
+
codexPromptPanelOpen = false;
|
|
413
|
+
codexSkillPanelOpen = false;
|
|
398
414
|
codexModelCandidate = codexState.model || codexState.models?.[0]?.id || null;
|
|
399
415
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
400
416
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
417
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
418
|
+
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
401
419
|
renderCodexModelPanel();
|
|
402
420
|
updateTerminalControlsHeight();
|
|
403
421
|
}
|
|
@@ -453,8 +471,12 @@
|
|
|
453
471
|
codexResumePanelOpen = !codexResumePanelOpen;
|
|
454
472
|
codexModelPanelOpen = false;
|
|
455
473
|
codexForkPanelOpen = false;
|
|
474
|
+
codexPromptPanelOpen = false;
|
|
475
|
+
codexSkillPanelOpen = false;
|
|
456
476
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
457
477
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
478
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
479
|
+
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
458
480
|
const panel = document.getElementById('codex-resume-panel');
|
|
459
481
|
panel.classList.toggle('active', codexResumePanelOpen);
|
|
460
482
|
updateTerminalControlsHeight();
|
|
@@ -466,8 +488,12 @@
|
|
|
466
488
|
codexForkPanelOpen = !codexForkPanelOpen;
|
|
467
489
|
codexModelPanelOpen = false;
|
|
468
490
|
codexResumePanelOpen = false;
|
|
491
|
+
codexPromptPanelOpen = false;
|
|
492
|
+
codexSkillPanelOpen = false;
|
|
469
493
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
470
494
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
495
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
496
|
+
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
471
497
|
const panel = document.getElementById('codex-fork-panel');
|
|
472
498
|
panel.classList.toggle('active', codexForkPanelOpen);
|
|
473
499
|
updateTerminalControlsHeight();
|
|
@@ -489,6 +515,157 @@
|
|
|
489
515
|
} catch (e) { panel.innerHTML = `<div class="claude-resume-meta" style="padding:12px;color:#ff6b61;">${escapeHtml(e.message)}</div>`; }
|
|
490
516
|
updateTerminalControlsHeight();
|
|
491
517
|
}
|
|
518
|
+
function formatCodexPromptTime(timestamp) {
|
|
519
|
+
const value = Number(timestamp || 0);
|
|
520
|
+
if (!value) return '';
|
|
521
|
+
const date = new Date(value);
|
|
522
|
+
return Number.isNaN(date.getTime()) ? '' : date.toLocaleString([], {
|
|
523
|
+
month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
function renderCodexPromptPanel(error = '') {
|
|
527
|
+
const panel = document.getElementById('codex-prompt-panel');
|
|
528
|
+
if (!panel) return;
|
|
529
|
+
const previousScrollTop = panel.scrollTop;
|
|
530
|
+
panel.classList.toggle('active', codexPromptPanelOpen);
|
|
531
|
+
if (!codexPromptPanelOpen) return;
|
|
532
|
+
const countLabel = codexPromptTotal ? `${codexPromptItems.length} / ${codexPromptTotal}${codexPromptTotal >= 200 ? ' max' : ''}` : '';
|
|
533
|
+
const items = codexPromptItems.map((item, index) => {
|
|
534
|
+
const expanded = codexExpandedPrompts.has(index);
|
|
535
|
+
return `<div class="codex-prompt-item"><button type="button" class="codex-prompt-text${expanded ? ' expanded' : ''}" onclick="toggleCodexPromptExpanded(${index})" title="${expanded ? 'Collapse prompt' : 'Expand prompt'}">${escapeHtml(item.text || '')}</button><div class="codex-prompt-actions"><span class="codex-prompt-time">${escapeHtml(formatCodexPromptTime(item.createdAt))}</span><button type="button" class="small-btn codex-prompt-copy" data-codex-prompt-copy="${index}" onclick="copyCodexPrompt(${index})">Copy</button></div></div>`;
|
|
536
|
+
}).join('');
|
|
537
|
+
const empty = !items && !codexPromptLoading && !error
|
|
538
|
+
? '<div class="claude-resume-meta" style="padding:12px;">No text prompts found for this folder.</div>' : '';
|
|
539
|
+
const footer = codexPromptHasMore || codexPromptLoading
|
|
540
|
+
? `<div class="codex-prompt-footer"><button type="button" class="small-btn primary" onclick="loadMoreCodexPrompts()"${codexPromptLoading ? ' disabled' : ''}>${codexPromptLoading ? 'Loading…' : 'Load more'}</button></div>` : '';
|
|
541
|
+
panel.innerHTML = `<div class="codex-prompt-header"><span>Prompt history</span><span>${escapeHtml(countLabel)}</span></div>${error ? `<div class="claude-resume-meta" style="padding:12px;color:#ff6b61;">${escapeHtml(error)}</div>` : ''}${items}${empty}${footer}`;
|
|
542
|
+
panel.scrollTop = previousScrollTop;
|
|
543
|
+
updateTerminalControlsHeight();
|
|
544
|
+
}
|
|
545
|
+
async function toggleCodexPromptPanel() {
|
|
546
|
+
codexPromptPanelOpen = !codexPromptPanelOpen;
|
|
547
|
+
codexModelPanelOpen = false;
|
|
548
|
+
codexResumePanelOpen = false;
|
|
549
|
+
codexForkPanelOpen = false;
|
|
550
|
+
codexSkillPanelOpen = false;
|
|
551
|
+
document.getElementById('codex-model-panel').classList.remove('active');
|
|
552
|
+
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
553
|
+
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
554
|
+
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
555
|
+
if (!codexPromptPanelOpen) {
|
|
556
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
557
|
+
updateTerminalControlsHeight();
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
codexPromptItems = [];
|
|
561
|
+
codexPromptNextOffset = 0;
|
|
562
|
+
codexPromptHasMore = false;
|
|
563
|
+
codexPromptTotal = 0;
|
|
564
|
+
codexExpandedPrompts = new Set();
|
|
565
|
+
renderCodexPromptPanel();
|
|
566
|
+
await loadMoreCodexPrompts();
|
|
567
|
+
}
|
|
568
|
+
async function loadMoreCodexPrompts() {
|
|
569
|
+
if (codexPromptLoading || !codexPromptPanelOpen || codexPromptNextOffset >= 200) return;
|
|
570
|
+
codexPromptLoading = true;
|
|
571
|
+
renderCodexPromptPanel();
|
|
572
|
+
try {
|
|
573
|
+
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-prompts?offset=${codexPromptNextOffset}&limit=30`, {}, 60000);
|
|
574
|
+
const data = await res.json();
|
|
575
|
+
if (!res.ok || !data.success) throw new Error(data.error || 'Failed to load prompt history');
|
|
576
|
+
codexPromptItems.push(...(data.items || []));
|
|
577
|
+
codexPromptNextOffset = Number(data.nextOffset || codexPromptItems.length);
|
|
578
|
+
codexPromptHasMore = Boolean(data.hasMore) && codexPromptNextOffset < 200;
|
|
579
|
+
codexPromptTotal = Math.min(200, Number(data.total || codexPromptItems.length));
|
|
580
|
+
codexPromptLoading = false;
|
|
581
|
+
renderCodexPromptPanel();
|
|
582
|
+
} catch (error) {
|
|
583
|
+
codexPromptLoading = false;
|
|
584
|
+
renderCodexPromptPanel(error.message);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
function toggleCodexPromptExpanded(index) {
|
|
588
|
+
if (codexExpandedPrompts.has(index)) codexExpandedPrompts.delete(index);
|
|
589
|
+
else codexExpandedPrompts.add(index);
|
|
590
|
+
renderCodexPromptPanel();
|
|
591
|
+
}
|
|
592
|
+
async function copyCodexPrompt(index) {
|
|
593
|
+
const prompt = codexPromptItems[index]?.text;
|
|
594
|
+
if (!prompt) return;
|
|
595
|
+
try {
|
|
596
|
+
await copyTextToClipboard(prompt);
|
|
597
|
+
const button = document.querySelector(`[data-codex-prompt-copy="${index}"]`);
|
|
598
|
+
if (!button) return;
|
|
599
|
+
button.textContent = 'Copied';
|
|
600
|
+
setTimeout(() => { if (button.isConnected) button.textContent = 'Copy'; }, 1200);
|
|
601
|
+
} catch (_) {
|
|
602
|
+
alert('Copy failed.');
|
|
603
|
+
}
|
|
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
|
+
}
|
|
492
669
|
async function selectCodexResumeThread(threadId) {
|
|
493
670
|
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-resume`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ threadId }) }, 30000);
|
|
494
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
|
@@ -37,6 +37,17 @@
|
|
|
37
37
|
let codexModelCandidate = null;
|
|
38
38
|
let codexResumePanelOpen = false;
|
|
39
39
|
let codexForkPanelOpen = false;
|
|
40
|
+
let codexPromptPanelOpen = false;
|
|
41
|
+
let codexPromptItems = [];
|
|
42
|
+
let codexPromptNextOffset = 0;
|
|
43
|
+
let codexPromptHasMore = false;
|
|
44
|
+
let codexPromptTotal = 0;
|
|
45
|
+
let codexPromptLoading = false;
|
|
46
|
+
let codexExpandedPrompts = new Set();
|
|
47
|
+
let codexSkillPanelOpen = false;
|
|
48
|
+
let codexSkillItems = [];
|
|
49
|
+
let codexSkillLoading = false;
|
|
50
|
+
let selectedCodexSkill = null;
|
|
40
51
|
let codexRenderFrame = null;
|
|
41
52
|
let codexApprovalJumpIndex = 0;
|
|
42
53
|
const modifiers = { ctrl: false };
|
package/lib/web/index.html
CHANGED
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
<button id="codex-fork-btn" class="claude-ctrl-btn primary" onclick="toggleCodexForkPanel()" title="Fork a Codex thread in this conversation">Fork</button>
|
|
123
123
|
</div>
|
|
124
124
|
<div class="codex-control-page">
|
|
125
|
+
<button id="codex-prompts-btn" class="claude-ctrl-btn" onclick="toggleCodexPromptPanel()" title="Browse and copy recent prompts">Prompts</button>
|
|
125
126
|
<button id="codex-compact-btn" class="claude-ctrl-btn" onclick="compactCodexContext()" title="Compact the current Codex context">Compact</button>
|
|
126
127
|
<label class="codex-select-control" title="Sandbox mode">
|
|
127
128
|
<span class="codex-select-label" aria-hidden="true">Sandbox</span>
|
|
@@ -135,12 +136,15 @@
|
|
|
135
136
|
<option value="default">Default</option><option value="untrusted">Untrusted</option><option value="on-request">On request</option><option value="never">Never ask</option>
|
|
136
137
|
</select>
|
|
137
138
|
</label>
|
|
139
|
+
<button id="codex-skills-btn" class="claude-ctrl-btn" onclick="toggleCodexSkillPanel()" title="Choose a skill for the next message">Skills</button>
|
|
138
140
|
</div>
|
|
139
141
|
</div>
|
|
140
142
|
<div id="codex-state-bar"></div>
|
|
141
143
|
<div id="codex-model-panel"></div>
|
|
142
144
|
<div id="codex-resume-panel"></div>
|
|
143
145
|
<div id="codex-fork-panel"></div>
|
|
146
|
+
<div id="codex-prompt-panel"></div>
|
|
147
|
+
<div id="codex-skill-panel"></div>
|
|
144
148
|
</div>
|
|
145
149
|
<div id="timed-send-panel">
|
|
146
150
|
<div class="timed-row">
|
package/lib/web/session.js
CHANGED
|
@@ -130,9 +130,22 @@
|
|
|
130
130
|
codexModelCandidate = null;
|
|
131
131
|
codexResumePanelOpen = false;
|
|
132
132
|
codexForkPanelOpen = false;
|
|
133
|
+
codexPromptPanelOpen = false;
|
|
134
|
+
codexPromptItems = [];
|
|
135
|
+
codexPromptNextOffset = 0;
|
|
136
|
+
codexPromptHasMore = false;
|
|
137
|
+
codexPromptTotal = 0;
|
|
138
|
+
codexPromptLoading = false;
|
|
139
|
+
codexExpandedPrompts = new Set();
|
|
140
|
+
codexSkillPanelOpen = false;
|
|
141
|
+
codexSkillItems = [];
|
|
142
|
+
codexSkillLoading = false;
|
|
143
|
+
selectedCodexSkill = null;
|
|
133
144
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
134
145
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
135
146
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
147
|
+
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
148
|
+
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
136
149
|
document.getElementById('codex-control-rail').scrollLeft = 0;
|
|
137
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 };
|
|
138
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; }
|
|
@@ -105,6 +108,10 @@
|
|
|
105
108
|
.codex-compaction-title { font-size: 12px; font-weight: 800; }
|
|
106
109
|
.codex-compaction-meta { margin-top: 3px; color: #a9a9b0; font-size: 10px; font-variant-numeric: tabular-nums; }
|
|
107
110
|
.codex-compaction-card.running .codex-compaction-icon { animation: codex-pulse 1.1s ease-in-out infinite alternate; }
|
|
111
|
+
.codex-warning-card { display: flex; align-items: flex-start; gap: 10px; margin: 10px 0 14px; padding: 10px 12px; border: 1px solid rgba(255,204,0,.34); border-radius: 10px; background: linear-gradient(135deg, rgba(255,204,0,.13), rgba(255,159,10,.07)); color: #f5f5f7; }
|
|
112
|
+
.codex-warning-icon { display: grid; width: 28px; height: 28px; flex: 0 0 28px; place-items: center; border-radius: 50%; background: rgba(255,204,0,.16); color: #ffd60a; font-size: 15px; font-weight: 900; }
|
|
113
|
+
.codex-warning-title { color: #ffe680; font-size: 12px; font-weight: 800; }
|
|
114
|
+
.codex-warning-text { margin-top: 3px; color: #d8d3bf; font-size: 11px; line-height: 1.4; overflow-wrap: anywhere; }
|
|
108
115
|
.codex-status-card { margin: 10px 0 14px; border: 1px solid rgba(100,210,255,.2); border-radius: 10px; background: rgba(28,28,30,.72); padding: 11px; color: #f5f5f7; }
|
|
109
116
|
.codex-status-title { margin-bottom: 9px; color: #64d2ff; font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; }
|
|
110
117
|
.codex-status-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }
|
|
@@ -173,8 +180,8 @@
|
|
|
173
180
|
.codex-select-control:focus-within .codex-select-label { background: rgba(255,255,255,.14); border-color: rgba(0,122,255,.45); }
|
|
174
181
|
.codex-select { appearance: none; position: absolute; inset: 0; width: 100%; min-width: 0; height: 100%; opacity: 0; cursor: pointer; }
|
|
175
182
|
.codex-select option { background: #1c1c1e; color: #fff; }
|
|
176
|
-
#codex-model-panel, #codex-resume-panel, #codex-fork-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; }
|
|
177
|
-
#codex-model-panel.active, #codex-resume-panel.active, #codex-fork-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; }
|
|
178
185
|
#codex-model-panel { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); max-height: min(300px, 42dvh); }
|
|
179
186
|
#codex-model-panel.active { display: grid; }
|
|
180
187
|
.codex-picker-column { min-width: 0; overflow-y: auto; }
|
|
@@ -184,6 +191,24 @@
|
|
|
184
191
|
#codex-state-bar { display: flex; align-items: center; gap: 7px; min-height: 24px; margin-top: 8px; color: var(--text-dim); font-size: 11px; overflow-x: auto; scrollbar-width: none; white-space: nowrap; }
|
|
185
192
|
#codex-state-bar::-webkit-scrollbar { display: none; }
|
|
186
193
|
#codex-resume-panel, #codex-fork-panel { max-height: min(280px, 38dvh); overflow-y: auto; }
|
|
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; }
|
|
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; }
|
|
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); }
|
|
204
|
+
.codex-prompt-item:last-of-type { border-bottom: 0; }
|
|
205
|
+
.codex-prompt-text { display: -webkit-box; width: 100%; min-width: 0; max-height: calc(1.42em * 4); overflow: hidden; padding: 0; border: 0; background: transparent; color: #f5f5f7; font: 12px/1.42 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; text-align: left; white-space: pre-wrap; overflow-wrap: anywhere; -webkit-box-orient: vertical; -webkit-line-clamp: 4; cursor: pointer; }
|
|
206
|
+
.codex-prompt-text.expanded { display: block; max-height: none; -webkit-line-clamp: initial; }
|
|
207
|
+
.codex-prompt-actions { display: flex; min-width: 64px; flex-direction: column; align-items: flex-end; gap: 6px; }
|
|
208
|
+
.codex-prompt-time { color: #77777e; font-size: 10px; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
209
|
+
.codex-prompt-copy { min-width: 54px; min-height: 28px; padding: 0 9px; }
|
|
210
|
+
.codex-prompt-footer { padding: 9px 11px; text-align: center; }
|
|
211
|
+
.codex-prompt-footer .small-btn { min-width: 112px; }
|
|
187
212
|
.claude-message { max-width: 92%; margin: 0 0 10px 0; padding: 10px 12px; border-radius: 8px; overflow-wrap: anywhere; line-height: 1.45; font-size: 14px; }
|
|
188
213
|
.claude-message-block { max-width: 100%; margin: 0 0 12px; }
|
|
189
214
|
.claude-message-block > .claude-message { margin-bottom: 0; }
|
|
@@ -191,6 +216,7 @@
|
|
|
191
216
|
.claude-message-block.user > .claude-message { max-width: 100%; }
|
|
192
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; }
|
|
193
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; }
|
|
194
220
|
.claude-message-attachments { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
|
|
195
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; }
|
|
196
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; }
|