glad-web 1.0.21 → 1.0.23
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/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/bin/cli.js +1 -1
- package/lib/ai-tools/registry.js +1 -1
- package/lib/codex/structured-session.js +668 -0
- package/lib/commands/web.js +63 -2
- package/lib/session/session-manager.js +74 -11
- package/lib/web/index.html +419 -13
- package/package.json +5 -4
package/lib/web/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="apple-touch-icon" href="logo.svg">
|
|
11
11
|
<link rel="stylesheet" href="https://unpkg.com/xterm@5.3.0/css/xterm.css" />
|
|
12
12
|
<style>
|
|
13
|
-
:root { --primary: #007aff; --bg: #000; --card-bg: #1c1c1e; --text: #fff; --text-dim: #8e8e93; --terminal-controls-rest-height: 108px; }
|
|
13
|
+
:root { --primary: #007aff; --bg: #000; --card-bg: #1c1c1e; --text: #fff; --text-dim: #8e8e93; --terminal-controls-rest-height: 108px; --chat-content-max: 900px; --control-content-max: 1100px; }
|
|
14
14
|
body, html { margin: 0; padding: 0; height: 100dvh; width: 100vw; background: var(--bg); color: var(--text); overflow: hidden; overflow-anchor: none; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
|
|
15
15
|
.view { display: none; height: 100%; width: 100%; flex-direction: column; }
|
|
16
16
|
.view.active { display: flex; }
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
#session-title { flex: 1; text-align: center; font-weight: 600; font-size: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 10px; min-width: 0; }
|
|
49
49
|
#terminal-controls { position: absolute; top: 100%; left: 0; right: 0; z-index: 2200; background: linear-gradient(180deg, rgba(18,18,18,0.98) 0%, rgba(18,18,18,0.92) 78%, rgba(18,18,18,0) 100%); border-bottom: 1px solid rgba(255,255,255,0.06); }
|
|
50
50
|
#terminal-controls-spacer { flex: 0 0 var(--terminal-controls-rest-height); height: var(--terminal-controls-rest-height); background: #000; }
|
|
51
|
-
#input-row { display: flex; align-items: flex-end; padding: 10px 14px 8px 14px; }
|
|
51
|
+
#input-row { display: flex; align-items: flex-end; width: min(100%, var(--control-content-max)); margin: 0 auto; padding: 10px 14px 8px 14px; box-sizing: border-box; }
|
|
52
52
|
#cmd-input { flex: 1; min-height: 38px; max-height: 150px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.1); border-radius: 19px; color: #fff; padding: 9px 16px; font-size: 16px; outline: none; resize: none; overflow-y: auto; line-height: 20px; box-sizing: border-box; transition: background 0.18s ease, border-color 0.18s ease; }
|
|
53
53
|
#cmd-input::placeholder { color: rgba(255,255,255,0.45); }
|
|
54
54
|
#cmd-input:focus { background: rgba(255,255,255,0.1); border-color: rgba(0,122,255,0.42); color: #fff; }
|
|
@@ -78,6 +78,63 @@
|
|
|
78
78
|
#terminal-container { flex: 1; min-height: 0; width: 100%; background: #000; position: relative; overflow: hidden; overflow-anchor: none; }
|
|
79
79
|
#terminal { height: 100%; width: 100%; }
|
|
80
80
|
#claude-chat-container { display: none; flex: 1; min-height: 0; overflow-y: auto; background: #050505; padding: 12px 12px 24px 12px; box-sizing: border-box; -webkit-overflow-scrolling: touch; }
|
|
81
|
+
#codex-chat-container { display: none; flex: 1; min-height: 0; overflow-y: auto; background: #050505; padding: 12px 12px 24px 12px; box-sizing: border-box; -webkit-overflow-scrolling: touch; }
|
|
82
|
+
.codex-conversation { width: min(100%, var(--chat-content-max)); margin: 0 auto; }
|
|
83
|
+
.codex-message { max-width: 100%; margin: 0 0 12px; color: #f5f5f7; font-size: 14px; line-height: 1.45; overflow-wrap: anywhere; }
|
|
84
|
+
.codex-message.user { max-width: 92%; margin-left: auto; padding: 8px 12px; border: 1px solid rgba(0,122,255,.32); border-radius: 12px; background: rgba(0,122,255,.24); }
|
|
85
|
+
.codex-message.event { color: var(--text-dim); text-align: center; font-size: 12px; }
|
|
86
|
+
.codex-tool { border: 1px solid rgba(255,255,255,.1); border-radius: 8px; background: rgba(255,255,255,.045); overflow: hidden; margin: 8px 0; color: #d1d5db; }
|
|
87
|
+
.codex-tool summary { list-style: none; cursor: pointer; }
|
|
88
|
+
.codex-tool summary::-webkit-details-marker { display: none; }
|
|
89
|
+
.codex-tool-header { display: flex; align-items: center; min-width: 0; gap: 8px; min-height: 34px; padding: 6px 9px; }
|
|
90
|
+
.codex-tool-icon { width: 19px; flex: 0 0 19px; color: #c7c7cc; font: 700 11px/1 ui-monospace, SFMono-Regular, Menlo, monospace; text-align: center; }
|
|
91
|
+
.codex-tool-title { flex: 0 1 auto; min-width: 0; color: #f5f5f7; font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
92
|
+
.codex-tool-command { flex: 1; min-width: 0; color: #a9a9b0; font: 12px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
93
|
+
.codex-tool-state { flex: 0 0 auto; color: #8e8e93; font-size: 11px; }
|
|
94
|
+
.codex-tool-state.running::before { content: ''; display: inline-block; width: 9px; height: 9px; margin-right: 5px; border: 1.5px solid #8e8e93; border-top-color: transparent; border-radius: 50%; animation: codex-spin .8s linear infinite; vertical-align: -1px; }
|
|
95
|
+
.codex-tool.error { border-color: rgba(255,59,48,.38); background: rgba(255,59,48,.07); }
|
|
96
|
+
.codex-tool-body { border-top: 1px solid rgba(255,255,255,.08); padding: 9px 10px; }
|
|
97
|
+
.codex-tool-code { margin: 0; max-height: 320px; overflow: auto; color: #c7c7cc; white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.42 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
98
|
+
.codex-work-group { margin: 8px 0; border-left: 1px solid rgba(255,255,255,.12); padding-left: 8px; }
|
|
99
|
+
.codex-work-group > summary { list-style: none; display: flex; align-items: center; gap: 7px; min-height: 30px; color: #a9a9b0; cursor: pointer; font-size: 12px; }
|
|
100
|
+
.codex-work-group > summary::-webkit-details-marker { display: none; }
|
|
101
|
+
.codex-work-group > summary::before { content: '›'; width: 12px; color: #8e8e93; font-size: 17px; transform-origin: center; }
|
|
102
|
+
.codex-work-group[open] > summary::before { transform: rotate(90deg); }
|
|
103
|
+
.codex-work-group-body { padding-left: 2px; }
|
|
104
|
+
.codex-patch-file { border-top: 1px solid rgba(255,255,255,.08); }
|
|
105
|
+
.codex-patch-file:first-child { border-top: 0; }
|
|
106
|
+
.codex-patch-file > summary { display: flex; align-items: center; gap: 8px; padding: 8px 10px; color: #d1d5db; font: 12px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
107
|
+
.codex-patch-file .path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
108
|
+
.codex-patch-kind { color: #8e8e93; font-family: system-ui, sans-serif; }
|
|
109
|
+
.codex-diff { overflow-x: auto; border-top: 1px solid rgba(255,255,255,.08); background: rgba(0,0,0,.24); font: 11px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
110
|
+
.codex-diff-line { min-width: max-content; padding: 0 10px; white-space: pre; color: #c7c7cc; }
|
|
111
|
+
.codex-diff-line.add { color: #b7e4c7; background: rgba(52,199,89,.1); }
|
|
112
|
+
.codex-diff-line.del { color: #ffb3ad; background: rgba(255,59,48,.1); }
|
|
113
|
+
.codex-diff-line.hunk { color: #64b5f6; background: rgba(0,122,255,.08); }
|
|
114
|
+
.codex-permission-result { color: #a9a9b0; font-size: 12px; margin-top: 7px; }
|
|
115
|
+
.codex-inline-permission { border-top: 1px solid rgba(255,204,0,.28); padding: 9px 10px 10px; background: rgba(255,204,0,.055); }
|
|
116
|
+
.codex-inline-permission-title { color: #f5f5f7; font-size: 12px; font-weight: 700; }
|
|
117
|
+
.codex-inline-permission .claude-permission-actions { margin-top: 8px; }
|
|
118
|
+
@keyframes codex-spin { to { transform: rotate(360deg); } }
|
|
119
|
+
#codex-control-panel { display: none; width: min(100%, var(--control-content-max)); margin: 0 auto; padding: 8px 14px 10px; border-bottom: 1px solid #222; background: #121212; box-sizing: border-box; }
|
|
120
|
+
.codex-control-row { display: grid; grid-template-columns: repeat(5, minmax(0, 116px)); justify-content: center; gap: 7px; align-items: center; }
|
|
121
|
+
.codex-control-row > * { width: 100%; }
|
|
122
|
+
.codex-select-control { position: relative; display: block; min-width: 0; height: 32px; }
|
|
123
|
+
.codex-select-label { display: flex; width: 100%; height: 100%; align-items: center; justify-content: center; box-sizing: border-box; border: 1px solid rgba(255,255,255,.1); border-radius: 16px; background: rgba(255,255,255,.08); color: #f5f5f7; font-size: 11px; font-weight: 800; white-space: nowrap; }
|
|
124
|
+
.codex-select-control:focus-within .codex-select-label { background: rgba(255,255,255,.14); border-color: rgba(0,122,255,.45); }
|
|
125
|
+
.codex-select { appearance: none; position: absolute; inset: 0; width: 100%; min-width: 0; height: 100%; opacity: 0; cursor: pointer; }
|
|
126
|
+
.codex-select option { background: #1c1c1e; color: #fff; }
|
|
127
|
+
#codex-model-panel, #codex-resume-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; }
|
|
128
|
+
#codex-model-panel.active, #codex-resume-panel.active { display: block; }
|
|
129
|
+
#codex-model-panel { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); max-height: min(300px, 42dvh); }
|
|
130
|
+
#codex-model-panel.active { display: grid; }
|
|
131
|
+
.codex-picker-column { min-width: 0; overflow-y: auto; }
|
|
132
|
+
.codex-picker-column + .codex-picker-column { border-left: 1px solid rgba(255,255,255,.08); }
|
|
133
|
+
.codex-picker-option { display: block; width: 100%; min-height: 38px; padding: 8px 10px; border: 0; border-bottom: 1px solid rgba(255,255,255,.06); background: transparent; color: #f5f5f7; text-align: left; font-size: 12px; font-weight: 700; overflow-wrap: anywhere; }
|
|
134
|
+
.codex-picker-option.selected { background: rgba(0,122,255,.18); color: #fff; }
|
|
135
|
+
#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; }
|
|
136
|
+
#codex-state-bar::-webkit-scrollbar { display: none; }
|
|
137
|
+
#codex-resume-panel { max-height: min(280px, 38dvh); overflow-y: auto; }
|
|
81
138
|
.claude-context-size-badge { position: sticky; top: 0; z-index: 2; width: max-content; max-width: 100%; margin: 0 0 8px auto; border: 1px solid rgba(255,255,255,0.1); background: rgba(28,28,30,0.94); border-radius: 999px; padding: 4px 9px; color: #d1d5db; font-size: 11px; font-weight: 800; box-shadow: 0 8px 18px rgba(0,0,0,0.24); }
|
|
82
139
|
.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; }
|
|
83
140
|
.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; }
|
|
@@ -226,6 +283,37 @@
|
|
|
226
283
|
.step-grid { grid-template-columns: 1fr; }
|
|
227
284
|
.timed-row { grid-template-columns: 1fr; }
|
|
228
285
|
}
|
|
286
|
+
@media (max-width: 480px) {
|
|
287
|
+
:root { --chat-content-max: 100%; --control-content-max: 100%; }
|
|
288
|
+
#nav-bar { padding: 8px 10px; }
|
|
289
|
+
#back-btn { font-size: 15px; }
|
|
290
|
+
#back-btn svg { width: 22px; height: 22px; }
|
|
291
|
+
#session-title { font-size: 15px; padding: 0 7px; }
|
|
292
|
+
#nav-bar > div:last-child { gap: 5px !important; }
|
|
293
|
+
#nav-bar > div:last-child .icon-btn { min-width: 34px; min-height: 30px; padding: 5px 7px !important; }
|
|
294
|
+
#input-row { padding-left: 10px; padding-right: 10px; }
|
|
295
|
+
#codex-control-panel { padding-left: 8px; padding-right: 8px; }
|
|
296
|
+
.codex-control-row { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 5px; }
|
|
297
|
+
.codex-control-row .claude-ctrl-btn { height: 36px; padding-left: 4px; padding-right: 4px; }
|
|
298
|
+
.codex-select-control { height: 36px; }
|
|
299
|
+
#codex-chat-container { padding: 10px 10px calc(22px + env(safe-area-inset-bottom)); }
|
|
300
|
+
.codex-message.user { max-width: 94%; }
|
|
301
|
+
.claude-permission-actions { justify-content: flex-start; }
|
|
302
|
+
}
|
|
303
|
+
@media (max-width: 430px) {
|
|
304
|
+
#nav-bar > div:last-child .icon-btn:not(#codex-terminal-switch) { width: 34px; font-size: 0 !important; gap: 0 !important; }
|
|
305
|
+
#nav-bar > div:last-child .icon-btn:not(#codex-terminal-switch) svg { width: 16px; height: 16px; }
|
|
306
|
+
#codex-terminal-switch { padding-left: 8px !important; padding-right: 8px !important; }
|
|
307
|
+
}
|
|
308
|
+
@media (min-width: 768px) and (max-width: 1199px) {
|
|
309
|
+
:root { --chat-content-max: 780px; --control-content-max: 920px; }
|
|
310
|
+
#codex-chat-container { padding: 16px 18px 30px; }
|
|
311
|
+
#input-row, #codex-control-panel { padding-left: 18px; padding-right: 18px; }
|
|
312
|
+
}
|
|
313
|
+
@media (min-width: 1200px) {
|
|
314
|
+
:root { --chat-content-max: 960px; --control-content-max: 1100px; }
|
|
315
|
+
#codex-chat-container { padding: 18px 22px 34px; }
|
|
316
|
+
}
|
|
229
317
|
</style>
|
|
230
318
|
</head>
|
|
231
319
|
<body>
|
|
@@ -264,6 +352,7 @@
|
|
|
264
352
|
</button>
|
|
265
353
|
<div id="session-title">Terminal</div>
|
|
266
354
|
<div style="display: flex; align-items: center; gap: 8px;">
|
|
355
|
+
<button id="codex-terminal-switch" class="icon-btn" onclick="toggleCodexPresentation()" title="Switch Codex interface" style="display:none; color: var(--primary); font-size: 12px; font-weight: 700; background: rgba(0, 122, 255, 0.1); padding: 5px 9px; border-radius: 15px; gap: 4px;">TERM</button>
|
|
267
356
|
<button class="icon-btn" onclick="showHistory()" title="History" style="color: var(--primary); font-size: 12px; font-weight: 700; background: rgba(0, 122, 255, 0.1); padding: 5px 9px; border-radius: 15px; gap: 4px;">
|
|
268
357
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"></path><polyline points="3 3 3 9 9 9"></polyline><path d="M12 7v5l3 2"></path></svg>
|
|
269
358
|
HIS
|
|
@@ -321,6 +410,28 @@
|
|
|
321
410
|
<div id="claude-usage-panel"></div>
|
|
322
411
|
<div id="claude-resume-panel"></div>
|
|
323
412
|
</div>
|
|
413
|
+
<div id="codex-control-panel">
|
|
414
|
+
<div class="codex-control-row">
|
|
415
|
+
<label class="codex-select-control" title="Sandbox mode">
|
|
416
|
+
<span class="codex-select-label" aria-hidden="true">Sandbox</span>
|
|
417
|
+
<select id="codex-sandbox-select" class="codex-select" aria-label="Sandbox mode" onchange="updateCodexSettingsFromControls()">
|
|
418
|
+
<option value="default">Default</option><option value="read-only">Read only</option><option value="workspace-write">Workspace write</option><option value="danger-full-access">Full access</option>
|
|
419
|
+
</select>
|
|
420
|
+
</label>
|
|
421
|
+
<label class="codex-select-control" title="Approval policy">
|
|
422
|
+
<span class="codex-select-label" aria-hidden="true">Ask</span>
|
|
423
|
+
<select id="codex-permission-select" class="codex-select" aria-label="Approval policy" onchange="updateCodexSettingsFromControls()">
|
|
424
|
+
<option value="default">Default</option><option value="untrusted">Untrusted</option><option value="on-request">On request</option><option value="never">Never ask</option>
|
|
425
|
+
</select>
|
|
426
|
+
</label>
|
|
427
|
+
<button id="codex-model-btn" class="claude-ctrl-btn" onclick="toggleCodexModelPanel()" title="Model and effort">Model</button>
|
|
428
|
+
<button id="codex-abort-btn" class="claude-ctrl-btn danger" onclick="abortCodexSession()" title="Abort current Codex turn">Abort</button>
|
|
429
|
+
<button id="codex-resume-btn" class="claude-ctrl-btn primary" onclick="toggleCodexResumePanel()" title="Resume a Codex thread">Resume</button>
|
|
430
|
+
</div>
|
|
431
|
+
<div id="codex-state-bar"></div>
|
|
432
|
+
<div id="codex-model-panel"></div>
|
|
433
|
+
<div id="codex-resume-panel"></div>
|
|
434
|
+
</div>
|
|
324
435
|
<div id="timed-send-panel">
|
|
325
436
|
<div class="timed-row">
|
|
326
437
|
<select id="timed-hours" onchange="updateTimedSendPreview()" title="Hours"></select>
|
|
@@ -360,6 +471,7 @@
|
|
|
360
471
|
<div id="terminal-controls-spacer"></div>
|
|
361
472
|
<div id="terminal-container"><div id="terminal"></div></div>
|
|
362
473
|
<div id="claude-chat-container"></div>
|
|
474
|
+
<div id="codex-chat-container"></div>
|
|
363
475
|
</div>
|
|
364
476
|
|
|
365
477
|
<!-- Plain Text History View -->
|
|
@@ -509,6 +621,12 @@
|
|
|
509
621
|
};
|
|
510
622
|
let claudeResumePanelOpen = false;
|
|
511
623
|
let claudeResumeItemsLoaded = false;
|
|
624
|
+
let codexMessages = [];
|
|
625
|
+
let codexPendingPermissions = [];
|
|
626
|
+
let codexState = { permissionMode: 'default', sandboxMode: 'default', effectivePermissionMode: null, effectiveSandboxMode: null, model: null, effort: null, status: 'idle', threadId: null, presentation: 'structured', models: [], canAbort: false, canSwitchToTerminal: false, canSwitchToStructured: false };
|
|
627
|
+
let codexModelPanelOpen = false;
|
|
628
|
+
let codexModelCandidate = null;
|
|
629
|
+
let codexResumePanelOpen = false;
|
|
512
630
|
const modifiers = { ctrl: false };
|
|
513
631
|
|
|
514
632
|
function log(msg) {
|
|
@@ -800,25 +918,30 @@
|
|
|
800
918
|
return activeToolKey === 'claude-code';
|
|
801
919
|
}
|
|
802
920
|
|
|
921
|
+
function isCodexSession() {
|
|
922
|
+
return activeToolKey === 'codex';
|
|
923
|
+
}
|
|
924
|
+
|
|
803
925
|
function setClaudeModeEnabled(enabled) {
|
|
804
|
-
|
|
926
|
+
const codexChat = isCodexSession() && codexState.presentation === 'structured';
|
|
927
|
+
const structured = enabled || codexChat;
|
|
928
|
+
document.getElementById('terminal-container').style.display = structured ? 'none' : '';
|
|
805
929
|
document.getElementById('claude-chat-container').style.display = enabled ? 'block' : 'none';
|
|
930
|
+
document.getElementById('codex-chat-container').style.display = codexChat ? 'block' : 'none';
|
|
806
931
|
document.getElementById('claude-control-panel').style.display = enabled ? 'block' : 'none';
|
|
932
|
+
document.getElementById('codex-control-panel').style.display = codexChat ? 'block' : 'none';
|
|
933
|
+
document.getElementById('codex-terminal-switch').style.display = isCodexSession() ? '' : 'none';
|
|
807
934
|
document.getElementById('timer-btn').style.display = '';
|
|
808
|
-
document.getElementById('shortcut-rail').style.display =
|
|
809
|
-
document.getElementById('scroll-controls').style.display =
|
|
810
|
-
document.getElementById('cmd-input').placeholder = enabled ? 'Message Claude...' : 'Type a message...';
|
|
935
|
+
document.getElementById('shortcut-rail').style.display = structured ? 'none' : '';
|
|
936
|
+
document.getElementById('scroll-controls').style.display = structured ? 'none' : '';
|
|
937
|
+
document.getElementById('cmd-input').placeholder = enabled ? 'Message Claude...' : codexChat ? 'Message Codex...' : 'Type a message...';
|
|
811
938
|
updateTerminalControlsHeight();
|
|
812
939
|
}
|
|
813
940
|
|
|
814
941
|
function updateTerminalControlsHeight() {
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
const baseHeight = claudeResumePanelOpen ? 308 : ((claudePickerOpen || claudeUsagePanelOpen) ? 322 : 154);
|
|
821
|
-
document.documentElement.style.setProperty('--terminal-controls-rest-height', `${baseHeight + (timedOpen ? 144 : 0)}px`);
|
|
942
|
+
const controls = document.getElementById('terminal-controls');
|
|
943
|
+
const measuredHeight = controls ? Math.ceil(controls.getBoundingClientRect().height) : 0;
|
|
944
|
+
document.documentElement.style.setProperty('--terminal-controls-rest-height', `${Math.max(0, measuredHeight)}px`);
|
|
822
945
|
}
|
|
823
946
|
|
|
824
947
|
function shortModelLabel(value, resolved) {
|
|
@@ -1903,6 +2026,239 @@
|
|
|
1903
2026
|
}));
|
|
1904
2027
|
}
|
|
1905
2028
|
|
|
2029
|
+
function codexText(text) { return escapeHtml(text || '').replace(/\n/g, '<br>'); }
|
|
2030
|
+
function codexJson(value) {
|
|
2031
|
+
if (typeof value === 'string') return value;
|
|
2032
|
+
try { return JSON.stringify(value || {}, null, 2); } catch (_) { return String(value || ''); }
|
|
2033
|
+
}
|
|
2034
|
+
function codexToolStatus(item) {
|
|
2035
|
+
const status = item.toolStatus || 'running';
|
|
2036
|
+
if (status === 'inProgress') return 'running';
|
|
2037
|
+
if (status === 'failed' || status === 'declined') return status;
|
|
2038
|
+
if (status === 'completed') return item.exitCode && item.exitCode !== 0 ? 'failed' : 'completed';
|
|
2039
|
+
return status;
|
|
2040
|
+
}
|
|
2041
|
+
function renderCodexDiff(diff) {
|
|
2042
|
+
return `<div class="codex-diff">${String(diff || '').split('\n').map(line => {
|
|
2043
|
+
const type = line.startsWith('+++') || line.startsWith('---') ? 'hunk' : line.startsWith('+') ? 'add' : line.startsWith('-') ? 'del' : line.startsWith('@@') ? 'hunk' : '';
|
|
2044
|
+
return `<div class="codex-diff-line ${type}">${escapeHtml(line || ' ')}</div>`;
|
|
2045
|
+
}).join('')}</div>`;
|
|
2046
|
+
}
|
|
2047
|
+
function normalizeCodexChanges(changes) {
|
|
2048
|
+
if (Array.isArray(changes)) return changes.filter(Boolean).map(change => [change.path || 'File', change]);
|
|
2049
|
+
if (changes && typeof changes === 'object') return Object.entries(changes);
|
|
2050
|
+
return [];
|
|
2051
|
+
}
|
|
2052
|
+
function codexChangeDiff(change = {}) {
|
|
2053
|
+
if (typeof change.diff === 'string') return change.diff;
|
|
2054
|
+
if (typeof change.unified_diff === 'string') return change.unified_diff;
|
|
2055
|
+
const kind = change.kind && typeof change.kind === 'object' ? change.kind : {};
|
|
2056
|
+
const type = change.type || kind.type || '';
|
|
2057
|
+
const oldText = change.modify?.old_content ?? change.old_content ?? change.oldContent ?? (type === 'delete' ? change.content : change.delete?.content) ?? '';
|
|
2058
|
+
const newText = change.modify?.new_content ?? change.new_content ?? change.newContent ?? (type === 'add' ? change.content : change.add?.content) ?? '';
|
|
2059
|
+
if (!oldText && !newText) return '';
|
|
2060
|
+
return `${String(oldText).split('\n').map(line => `-${line}`).join('\n')}\n${String(newText).split('\n').map(line => `+${line}`).join('\n')}`;
|
|
2061
|
+
}
|
|
2062
|
+
function renderCodexPatch(item) {
|
|
2063
|
+
const entries = normalizeCodexChanges(item.changes || item.input?.changes);
|
|
2064
|
+
if (!entries.length) return `<div class="codex-tool-body"><pre class="codex-tool-code">${escapeHtml(codexJson(item.input))}</pre></div>`;
|
|
2065
|
+
return entries.map(([path, change]) => {
|
|
2066
|
+
const kind = change?.type || change?.kind?.type || 'edit';
|
|
2067
|
+
const move = change?.move_path || change?.kind?.move_path;
|
|
2068
|
+
const diff = codexChangeDiff(change);
|
|
2069
|
+
return `<details class="codex-patch-file"><summary><span class="path">${escapeHtml(path)}${move ? ` → ${escapeHtml(move)}` : ''}</span><span class="codex-patch-kind">${escapeHtml(kind)}</span></summary>${diff ? renderCodexDiff(diff) : ''}</details>`;
|
|
2070
|
+
}).join('');
|
|
2071
|
+
}
|
|
2072
|
+
function renderCodexPermission(request, inline = false) {
|
|
2073
|
+
if (!request) return '';
|
|
2074
|
+
const pending = request.status === 'pending';
|
|
2075
|
+
const labels = { approved: 'Allowed once', approved_for_session: 'Allowed for session', denied: 'Denied', abort: 'Stopped' };
|
|
2076
|
+
const detail = request.reason || (request.input && Object.keys(request.input).length ? codexJson(request.input) : '');
|
|
2077
|
+
const content = `${inline ? `<div class="codex-inline-permission-title">${escapeHtml(request.title || 'Permission required')}</div>` : ''}${detail ? `<div>${codexText(detail)}</div>` : ''}${pending ? `<div class="claude-permission-actions"><button class="small-btn primary" onclick="respondCodexPermission('${escapeHtml(request.id)}', 'approved')">Yes</button><button class="small-btn" onclick="respondCodexPermission('${escapeHtml(request.id)}', 'approved_for_session')">Yes, for session</button><button class="small-btn danger" onclick="respondCodexPermission('${escapeHtml(request.id)}', 'abort')">Stop and explain</button></div>` : `<div class="codex-permission-result">${escapeHtml(labels[request.decision] || request.status)}</div>`}`;
|
|
2078
|
+
if (inline) return `<div class="codex-inline-permission">${content}</div>`;
|
|
2079
|
+
return `<div class="claude-tool claude-permission"><div class="claude-tool-header"><strong>${escapeHtml(request.title || 'Permission required')}</strong></div><div class="claude-tool-body">${content}</div></div>`;
|
|
2080
|
+
}
|
|
2081
|
+
function renderCodexTool(item, permission = null) {
|
|
2082
|
+
const status = codexToolStatus(item);
|
|
2083
|
+
const isError = status === 'failed' || Boolean(item.error) || (item.exitCode != null && item.exitCode !== 0);
|
|
2084
|
+
const runningClass = status === 'running' ? ' running' : '';
|
|
2085
|
+
if (item.name === 'CodexPatch') {
|
|
2086
|
+
return `<div class="codex-tool${isError ? ' error' : ''}">${renderCodexPatch(item)}${permission ? renderCodexPermission(permission, true) : ''}</div>`;
|
|
2087
|
+
}
|
|
2088
|
+
const command = item.name === 'CodexBash' ? item.command : item.title || item.tool || '';
|
|
2089
|
+
const icon = item.name === 'CodexBash' ? '>_' : item.name === 'McpTool' ? 'MCP' : item.name === 'Agent' ? 'A' : '•';
|
|
2090
|
+
const title = item.name === 'CodexBash' ? 'Command' : item.title || item.name || 'Tool';
|
|
2091
|
+
const result = item.result || (item.name === 'McpTool' || item.name === 'Agent' ? codexJson(item.input) : '');
|
|
2092
|
+
return `<details class="codex-tool${isError ? ' error' : ''}"><summary class="codex-tool-header"><span class="codex-tool-icon">${escapeHtml(icon)}</span><span class="codex-tool-title">${escapeHtml(title)}</span>${command && command !== title ? `<span class="codex-tool-command">${escapeHtml(command)}</span>` : '<span class="codex-tool-command"></span>'}<span class="codex-tool-state${runningClass}">${escapeHtml(status === 'completed' ? '' : status)}</span></summary>${result ? `<div class="codex-tool-body"><pre class="codex-tool-code">${escapeHtml(result)}</pre></div>` : ''}${permission ? renderCodexPermission(permission, true) : ''}</details>`;
|
|
2093
|
+
}
|
|
2094
|
+
function renderCodexToolGroup(items, permissionById, usedPermissions) {
|
|
2095
|
+
const running = items.some(item => codexToolStatus(item) === 'running');
|
|
2096
|
+
const failed = items.some(item => codexToolStatus(item) === 'failed' || item.error);
|
|
2097
|
+
const startedAt = Math.min(...items.map(item => Number(item.createdAt || Date.now())));
|
|
2098
|
+
const seconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000));
|
|
2099
|
+
const tools = items.map(item => {
|
|
2100
|
+
const permission = permissionById.get(String(item.providerId || ''));
|
|
2101
|
+
if (permission) usedPermissions.add(permission.id);
|
|
2102
|
+
return renderCodexTool(item, permission);
|
|
2103
|
+
}).join('');
|
|
2104
|
+
return `<details class="codex-work-group"${running ? ' open' : ''}><summary>${running ? 'Working' : failed ? 'Work finished with errors' : `Worked for ${seconds}s`} · ${items.length} tools</summary><div class="codex-work-group-body">${tools}</div></details>`;
|
|
2105
|
+
}
|
|
2106
|
+
function renderCodexChat() {
|
|
2107
|
+
const container = document.getElementById('codex-chat-container');
|
|
2108
|
+
if (!container) return;
|
|
2109
|
+
const parts = [];
|
|
2110
|
+
const permissionById = new Map(codexPendingPermissions.map(item => [String(item.id), item]));
|
|
2111
|
+
const usedPermissions = new Set();
|
|
2112
|
+
const visible = codexMessages.filter(item => !['reasoning', 'turn-start', 'turn-end'].includes(item.kind));
|
|
2113
|
+
for (let i = 0; i < visible.length;) {
|
|
2114
|
+
const item = visible[i];
|
|
2115
|
+
if (item.kind === 'tool') {
|
|
2116
|
+
const tools = [];
|
|
2117
|
+
while (i < visible.length && visible[i].kind === 'tool') tools.push(visible[i++]);
|
|
2118
|
+
if (tools.length > 1) parts.push(renderCodexToolGroup(tools, permissionById, usedPermissions));
|
|
2119
|
+
else {
|
|
2120
|
+
const permission = permissionById.get(String(tools[0].providerId || ''));
|
|
2121
|
+
if (permission) usedPermissions.add(permission.id);
|
|
2122
|
+
parts.push(renderCodexTool(tools[0], permission));
|
|
2123
|
+
}
|
|
2124
|
+
continue;
|
|
2125
|
+
}
|
|
2126
|
+
if (item.kind === 'assistant') parts.push(`<div class="codex-message assistant claude-md">${renderMarkdown(item.text || '')}</div>`);
|
|
2127
|
+
else if (item.kind === 'user') parts.push(`<div class="codex-message user claude-md">${renderMarkdown(item.text || '')}</div>`);
|
|
2128
|
+
else parts.push(`<div class="codex-message ${escapeHtml(item.level === 'error' ? 'event error' : item.kind || 'event')}">${codexText(item.text)}</div>`);
|
|
2129
|
+
i += 1;
|
|
2130
|
+
}
|
|
2131
|
+
for (const request of codexPendingPermissions) if (!usedPermissions.has(request.id)) parts.push(renderCodexPermission(request));
|
|
2132
|
+
if (codexState.status === 'running') {
|
|
2133
|
+
parts.push('<div class="claude-status">Codex is working...</div>');
|
|
2134
|
+
}
|
|
2135
|
+
container.innerHTML = `<div class="codex-conversation">${parts.join('') || '<div class="codex-message event">Send a message to start Codex.</div>'}</div>`;
|
|
2136
|
+
requestAnimationFrame(() => { container.scrollTop = container.scrollHeight; });
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
function applyCodexState(state = {}) {
|
|
2140
|
+
const presentationChanged = state.presentation !== undefined && state.presentation !== codexState.presentation;
|
|
2141
|
+
codexState = { ...codexState, ...state };
|
|
2142
|
+
const permission = document.getElementById('codex-permission-select');
|
|
2143
|
+
if (permission) {
|
|
2144
|
+
const defaultOption = permission.querySelector('option[value="default"]');
|
|
2145
|
+
const labels = { untrusted: 'Untrusted', 'on-request': 'On request', never: 'Never ask' };
|
|
2146
|
+
if (defaultOption) defaultOption.textContent = codexState.effectivePermissionMode
|
|
2147
|
+
? `Default (${labels[codexState.effectivePermissionMode] || codexState.effectivePermissionMode})`
|
|
2148
|
+
: 'Default';
|
|
2149
|
+
permission.value = codexState.permissionMode || 'default';
|
|
2150
|
+
}
|
|
2151
|
+
const sandbox = document.getElementById('codex-sandbox-select');
|
|
2152
|
+
if (sandbox) {
|
|
2153
|
+
const defaultOption = sandbox.querySelector('option[value="default"]');
|
|
2154
|
+
const labels = { 'read-only': 'Read only', 'workspace-write': 'Workspace write', 'danger-full-access': 'Full access' };
|
|
2155
|
+
if (defaultOption) defaultOption.textContent = codexState.effectiveSandboxMode
|
|
2156
|
+
? `Default (${labels[codexState.effectiveSandboxMode] || codexState.effectiveSandboxMode})`
|
|
2157
|
+
: 'Default';
|
|
2158
|
+
sandbox.value = codexState.sandboxMode || 'default';
|
|
2159
|
+
}
|
|
2160
|
+
const modelButton = document.getElementById('codex-model-btn');
|
|
2161
|
+
if (modelButton) modelButton.textContent = 'Model';
|
|
2162
|
+
const abort = document.getElementById('codex-abort-btn');
|
|
2163
|
+
if (abort) abort.disabled = !codexState.canAbort;
|
|
2164
|
+
const terminal = document.getElementById('codex-terminal-switch');
|
|
2165
|
+
if (terminal) { terminal.textContent = codexState.presentation === 'terminal' ? 'CHAT' : 'TERM'; terminal.disabled = codexState.presentation === 'structured' && !codexState.canSwitchToTerminal; terminal.title = codexState.presentation === 'terminal' ? 'Return to Codex chat' : 'Switch to Codex terminal'; }
|
|
2166
|
+
renderCodexStateBar();
|
|
2167
|
+
renderCodexModelPanel();
|
|
2168
|
+
if (presentationChanged) setClaudeModeEnabled(isClaudeSession());
|
|
2169
|
+
renderCodexChat();
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
function renderCodexStateBar() {
|
|
2173
|
+
const el = document.getElementById('codex-state-bar');
|
|
2174
|
+
if (!el) return;
|
|
2175
|
+
const pending = Number(codexState.pendingPermissionCount || codexPendingPermissions.filter(item => item.status === 'pending').length) || 0;
|
|
2176
|
+
const parts = [];
|
|
2177
|
+
if (pending || codexState.status === 'waiting_approval') parts.push(`<span class="claude-state-pill warn">${pending || 1} approval${pending === 1 ? '' : 's'}</span>`);
|
|
2178
|
+
else if (codexState.status === 'running') parts.push('<span class="claude-state-pill">Working</span>');
|
|
2179
|
+
el.innerHTML = parts.join('');
|
|
2180
|
+
el.style.display = parts.length ? 'flex' : 'none';
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
function toggleCodexModelPanel() {
|
|
2184
|
+
codexModelPanelOpen = !codexModelPanelOpen;
|
|
2185
|
+
codexResumePanelOpen = false;
|
|
2186
|
+
codexModelCandidate = codexState.model || codexState.models?.[0]?.id || null;
|
|
2187
|
+
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
2188
|
+
renderCodexModelPanel();
|
|
2189
|
+
updateTerminalControlsHeight();
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
function renderCodexModelPanel() {
|
|
2193
|
+
const panel = document.getElementById('codex-model-panel');
|
|
2194
|
+
if (!panel) return;
|
|
2195
|
+
panel.classList.toggle('active', codexModelPanelOpen);
|
|
2196
|
+
if (!codexModelPanelOpen) { panel.innerHTML = ''; return; }
|
|
2197
|
+
const models = codexState.models || [];
|
|
2198
|
+
const candidate = models.find(item => item.id === codexModelCandidate) || models.find(item => item.id === codexState.model) || models[0];
|
|
2199
|
+
if (!candidate) { panel.innerHTML = '<div class="claude-resume-meta" style="padding:12px;">Loading models...</div>'; return; }
|
|
2200
|
+
codexModelCandidate = candidate.id;
|
|
2201
|
+
const efforts = candidate.efforts?.length ? candidate.efforts : [candidate.defaultEffort || 'medium'];
|
|
2202
|
+
panel.innerHTML = `<div class="codex-picker-column">${models.map(item => `<button class="codex-picker-option${item.id === candidate.id ? ' selected' : ''}" onclick="selectCodexModelCandidate(decodePathValue('${encodePathValue(item.id)}'))">${escapeHtml(item.label || item.id)}</button>`).join('')}</div><div class="codex-picker-column">${efforts.map(effort => `<button class="codex-picker-option${candidate.id === codexState.model && effort === codexState.effort ? ' selected' : ''}" onclick="chooseCodexModelEffort(decodePathValue('${encodePathValue(candidate.id)}'), decodePathValue('${encodePathValue(effort)}'))">${escapeHtml(effort)}</button>`).join('')}</div>`;
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
function selectCodexModelCandidate(model) { codexModelCandidate = model; renderCodexModelPanel(); }
|
|
2206
|
+
function chooseCodexModelEffort(model, effort) {
|
|
2207
|
+
codexModelPanelOpen = false;
|
|
2208
|
+
applyCodexState({ model, effort });
|
|
2209
|
+
sendCodexSettings({ model, effort });
|
|
2210
|
+
updateTerminalControlsHeight();
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
function applyCodexEvent(event) {
|
|
2214
|
+
if (!event) return;
|
|
2215
|
+
if (event.type === 'message' && event.message) codexMessages.push(event.message);
|
|
2216
|
+
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); }
|
|
2217
|
+
else if (event.type === 'history-reset') codexMessages = event.messages || [];
|
|
2218
|
+
else if (event.type === 'permission-request' && event.request) { codexPendingPermissions = [...codexPendingPermissions.filter(item => item.id !== event.request.id), event.request]; }
|
|
2219
|
+
else if (event.type === 'permission-updated' && event.request) codexPendingPermissions = codexPendingPermissions.map(item => item.id === event.request.id ? event.request : item);
|
|
2220
|
+
if (event.state) applyCodexState(event.state); else renderCodexChat();
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
function sendCodexSettings(settings) { if (currentSocket?.readyState === 1) currentSocket.send(JSON.stringify({ type: 'codex-settings', settings })); }
|
|
2224
|
+
function updateCodexSettingsFromControls() {
|
|
2225
|
+
const permissionMode = document.getElementById('codex-permission-select').value;
|
|
2226
|
+
const sandboxMode = document.getElementById('codex-sandbox-select').value;
|
|
2227
|
+
applyCodexState({ permissionMode, sandboxMode });
|
|
2228
|
+
sendCodexSettings({ permissionMode, sandboxMode });
|
|
2229
|
+
}
|
|
2230
|
+
function abortCodexSession() { if (currentSocket?.readyState === 1) currentSocket.send(JSON.stringify({ type: 'codex-abort' })); }
|
|
2231
|
+
function respondCodexPermission(id, decision) { if (currentSocket?.readyState === 1) currentSocket.send(JSON.stringify({ type: 'codex-permission', id, decision })); }
|
|
2232
|
+
async function toggleCodexResumePanel() {
|
|
2233
|
+
codexResumePanelOpen = !codexResumePanelOpen;
|
|
2234
|
+
codexModelPanelOpen = false;
|
|
2235
|
+
document.getElementById('codex-model-panel').classList.remove('active');
|
|
2236
|
+
const panel = document.getElementById('codex-resume-panel');
|
|
2237
|
+
panel.classList.toggle('active', codexResumePanelOpen);
|
|
2238
|
+
updateTerminalControlsHeight();
|
|
2239
|
+
if (!codexResumePanelOpen) return;
|
|
2240
|
+
panel.innerHTML = '<div class="claude-resume-meta" style="padding:12px;">Loading sessions...</div>';
|
|
2241
|
+
try {
|
|
2242
|
+
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-resume-threads`, {}, 30000);
|
|
2243
|
+
const data = await res.json();
|
|
2244
|
+
if (!res.ok || !data.success) throw new Error(data.error || 'Failed to load Codex sessions');
|
|
2245
|
+
const items = data.items || [];
|
|
2246
|
+
panel.innerHTML = items.length ? items.map(item => `<button class="claude-resume-item" onclick="selectCodexResumeThread(decodePathValue('${encodePathValue(item.id)}'))"><div class="claude-resume-title"><span>${escapeHtml((item.preview || 'Codex session').slice(0, 48))}${item.current ? ' · current' : ''}</span><span>${escapeHtml(item.updatedAt ? new Date(item.updatedAt).toLocaleString() : '')}</span></div><div class="claude-resume-meta">${escapeHtml(item.id.slice(0, 12))}</div></button>`).join('') : '<div class="claude-resume-meta" style="padding:12px;">No Codex sessions found for this folder.</div>';
|
|
2247
|
+
} catch (e) { panel.innerHTML = `<div class="claude-resume-meta" style="padding:12px;color:#ff6b61;">${escapeHtml(e.message)}</div>`; }
|
|
2248
|
+
}
|
|
2249
|
+
async function selectCodexResumeThread(threadId) {
|
|
2250
|
+
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-resume`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ threadId }) }, 30000);
|
|
2251
|
+
if (!res.ok) { alert((await res.json()).error || 'Unable to resume Codex thread'); return; }
|
|
2252
|
+
codexResumePanelOpen = false;
|
|
2253
|
+
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
2254
|
+
updateTerminalControlsHeight();
|
|
2255
|
+
}
|
|
2256
|
+
async function toggleCodexPresentation() {
|
|
2257
|
+
const presentation = codexState.presentation === 'terminal' ? 'structured' : 'terminal';
|
|
2258
|
+
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-presentation`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ presentation }) }, 30000);
|
|
2259
|
+
if (!res.ok) alert((await res.json()).error || 'Unable to switch Codex interface');
|
|
2260
|
+
}
|
|
2261
|
+
|
|
1906
2262
|
function joinSession(id, sessionName, toolKey = null) {
|
|
1907
2263
|
stopTimedInputTimers();
|
|
1908
2264
|
activeSessionId = id;
|
|
@@ -1926,6 +2282,10 @@
|
|
|
1926
2282
|
initClaudeSession(sessionId);
|
|
1927
2283
|
return;
|
|
1928
2284
|
}
|
|
2285
|
+
if (isCodexSession() && codexState.presentation === 'structured') {
|
|
2286
|
+
initCodexSession(sessionId);
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
1929
2289
|
if (typeof Terminal === 'undefined') {
|
|
1930
2290
|
alert('Terminal library not loaded yet.');
|
|
1931
2291
|
showLobby();
|
|
@@ -1959,6 +2319,14 @@
|
|
|
1959
2319
|
const msg = JSON.parse(e.data);
|
|
1960
2320
|
if (msg.type === 'output') term.write(msg.data);
|
|
1961
2321
|
if (msg.type === 'reset') term.reset();
|
|
2322
|
+
if (msg.type === 'codex-event' && isCodexSession()) {
|
|
2323
|
+
applyCodexEvent(msg.event);
|
|
2324
|
+
if (msg.event?.type === 'presentation' && msg.event.presentation === 'structured') {
|
|
2325
|
+
currentSocket.close();
|
|
2326
|
+
currentSocket = null;
|
|
2327
|
+
initCodexSession(sessionId);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
1962
2330
|
if (msg.type === 'exit') showLobby();
|
|
1963
2331
|
};
|
|
1964
2332
|
}
|
|
@@ -2010,6 +2378,38 @@
|
|
|
2010
2378
|
};
|
|
2011
2379
|
}
|
|
2012
2380
|
|
|
2381
|
+
function initCodexSession(sessionId) {
|
|
2382
|
+
codexMessages = [];
|
|
2383
|
+
codexPendingPermissions = [];
|
|
2384
|
+
codexModelPanelOpen = false;
|
|
2385
|
+
codexModelCandidate = null;
|
|
2386
|
+
codexResumePanelOpen = false;
|
|
2387
|
+
document.getElementById('codex-model-panel').classList.remove('active');
|
|
2388
|
+
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
2389
|
+
codexState = { permissionMode: 'default', sandboxMode: 'default', effectivePermissionMode: null, effectiveSandboxMode: null, model: null, effort: null, status: 'idle', threadId: null, presentation: 'structured', models: [], canAbort: false, canSwitchToTerminal: false, canSwitchToStructured: false };
|
|
2390
|
+
setClaudeModeEnabled(false);
|
|
2391
|
+
applyCodexState(codexState);
|
|
2392
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
2393
|
+
currentSocket = new WebSocket(protocol + '//' + window.location.host + '?sessionId=' + sessionId);
|
|
2394
|
+
currentSocket.onmessage = (e) => {
|
|
2395
|
+
const msg = JSON.parse(e.data);
|
|
2396
|
+
if (msg.type === 'codex-snapshot' && msg.snapshot) {
|
|
2397
|
+
codexMessages = msg.snapshot.messages || [];
|
|
2398
|
+
codexPendingPermissions = msg.snapshot.pendingPermissions || [];
|
|
2399
|
+
applyCodexState(msg.snapshot.state || {});
|
|
2400
|
+
}
|
|
2401
|
+
if (msg.type === 'codex-event') {
|
|
2402
|
+
applyCodexEvent(msg.event);
|
|
2403
|
+
if (msg.event?.type === 'presentation' && msg.event.presentation === 'terminal') {
|
|
2404
|
+
currentSocket.close();
|
|
2405
|
+
currentSocket = null;
|
|
2406
|
+
initTerminal(sessionId);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
if (msg.type === 'exit') showLobby();
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2013
2413
|
function sendWS(data) {
|
|
2014
2414
|
if (currentSocket && currentSocket.readyState === 1) currentSocket.send(JSON.stringify({ type: 'input', data }));
|
|
2015
2415
|
}
|
|
@@ -2228,6 +2628,12 @@
|
|
|
2228
2628
|
inputEl.style.height = '38px';
|
|
2229
2629
|
return;
|
|
2230
2630
|
}
|
|
2631
|
+
if (val && isCodexSession() && codexState.presentation === 'structured') {
|
|
2632
|
+
if (currentSocket && currentSocket.readyState === 1) currentSocket.send(JSON.stringify({ type: 'codex-input', text: val }));
|
|
2633
|
+
inputEl.value = '';
|
|
2634
|
+
inputEl.style.height = '38px';
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2231
2637
|
if (val) {
|
|
2232
2638
|
const formattedVal = val.replace(/\n/g, '\r');
|
|
2233
2639
|
sendWS(formattedVal);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glad-web",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"description": "Glad transforms terminal-based AI coding tools into a polished, mobile-friendly local Web interface.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
],
|
|
28
28
|
"author": "Glad Team",
|
|
29
29
|
"license": "MIT",
|
|
30
|
-
"homepage": "https://github.com/
|
|
30
|
+
"homepage": "https://github.com/Anonymous/Glad",
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
33
|
-
"url": "git+https://github.com/
|
|
33
|
+
"url": "git+https://github.com/Anonymous/Glad.git"
|
|
34
34
|
},
|
|
35
35
|
"bugs": {
|
|
36
|
-
"url": "https://github.com/
|
|
36
|
+
"url": "https://github.com/Anonymous/Glad/issues"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=18.0.0"
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"files": [
|
|
42
42
|
"bin/",
|
|
43
43
|
"lib/",
|
|
44
|
+
"!lib/**/*.test.js",
|
|
44
45
|
"assets/logo.svg",
|
|
45
46
|
"README.md"
|
|
46
47
|
],
|