glad-web 1.0.34 → 1.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/codex/structured-session.js +52 -2
- package/lib/commands/web.js +6 -0
- package/lib/web/codex.js +46 -2
- package/lib/web/core.js +5 -0
- package/lib/web/session.js +8 -0
- package/package.json +1 -1
|
@@ -4,6 +4,8 @@ const readline = require('readline');
|
|
|
4
4
|
const crypto = require('crypto');
|
|
5
5
|
const PTYManager = require('../session/pty-manager');
|
|
6
6
|
|
|
7
|
+
const CODEX_MESSAGE_PAGE_BYTES = 200 * 1024;
|
|
8
|
+
|
|
7
9
|
const PERMISSION_MODES = new Set(['untrusted', 'on-request', 'never']);
|
|
8
10
|
const SANDBOX_MODES = new Set(['read-only', 'workspace-write', 'danger-full-access']);
|
|
9
11
|
const FALLBACK_EFFORTS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'ultra'];
|
|
@@ -250,14 +252,60 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
snapshot() {
|
|
255
|
+
const historyPage = this.getMessagePage();
|
|
256
|
+
const { messages, ...historyPageMeta } = historyPage;
|
|
253
257
|
return { id: this.id, name: this.name, tool: this.tool.displayName, toolKey: this.tool.key,
|
|
254
|
-
status: this.status, state: this.getControlState(), messages:
|
|
258
|
+
status: this.status, state: this.getControlState(), messages, historyPage: historyPageMeta,
|
|
255
259
|
pendingPermissions: [
|
|
256
260
|
...this.completedPermissions,
|
|
257
261
|
...Array.from(this.pendingPermissions.values()).map(item => item.public)
|
|
258
262
|
] };
|
|
259
263
|
}
|
|
260
264
|
|
|
265
|
+
getMessagePage(beforeId = null, maxBytes = CODEX_MESSAGE_PAGE_BYTES) {
|
|
266
|
+
const requestedBeforeId = beforeId == null ? '' : String(beforeId);
|
|
267
|
+
let end = this.messages.length;
|
|
268
|
+
if (requestedBeforeId) {
|
|
269
|
+
const beforeIndex = this.messages.findIndex(item => String(item.id || '') === requestedBeforeId);
|
|
270
|
+
if (beforeIndex < 0) {
|
|
271
|
+
return { messages: [], hasMore: false, beforeId: null, bytes: 2, maxBytes };
|
|
272
|
+
}
|
|
273
|
+
end = beforeIndex;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (end <= 0) return { messages: [], hasMore: false, beforeId: null, bytes: 2, maxBytes };
|
|
277
|
+
|
|
278
|
+
const groupStarts = [0];
|
|
279
|
+
for (let i = 1; i < end; i++) {
|
|
280
|
+
const item = this.messages[i];
|
|
281
|
+
if (item.kind === 'turn-start' && (!item.threadId || item.threadId === this.threadId)) {
|
|
282
|
+
groupStarts.push(i);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
groupStarts.push(end);
|
|
286
|
+
|
|
287
|
+
let start = end;
|
|
288
|
+
let selectedBytes = 2;
|
|
289
|
+
for (let groupIndex = groupStarts.length - 2; groupIndex >= 0; groupIndex--) {
|
|
290
|
+
const candidateStart = groupStarts[groupIndex];
|
|
291
|
+
const candidate = this.messages.slice(candidateStart, end);
|
|
292
|
+
const candidateBytes = Buffer.byteLength(JSON.stringify(candidate), 'utf8');
|
|
293
|
+
if (start < end && candidateBytes > maxBytes) break;
|
|
294
|
+
start = candidateStart;
|
|
295
|
+
selectedBytes = candidateBytes;
|
|
296
|
+
if (candidateBytes >= maxBytes) break;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const messages = this.messages.slice(start, end);
|
|
300
|
+
return {
|
|
301
|
+
messages,
|
|
302
|
+
hasMore: start > 0,
|
|
303
|
+
beforeId: messages[0]?.id || null,
|
|
304
|
+
bytes: selectedBytes,
|
|
305
|
+
maxBytes
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
261
309
|
getControlState() {
|
|
262
310
|
const activeSubagentCount = Array.from(this.threadTurns.entries())
|
|
263
311
|
.filter(([threadId, turn]) => threadId !== this.threadId && turn?.status === 'running').length;
|
|
@@ -1088,7 +1136,9 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
1088
1136
|
...(completedAtMs ? { createdAt: completedAtMs } : {}) });
|
|
1089
1137
|
}
|
|
1090
1138
|
if (eventText) this.append({ kind: 'event', level: 'info', text: eventText });
|
|
1091
|
-
|
|
1139
|
+
const historyPage = this.getMessagePage();
|
|
1140
|
+
const { messages, ...historyPageMeta } = historyPage;
|
|
1141
|
+
this.emitEvent({ type: 'history-reset', messages, historyPage: historyPageMeta });
|
|
1092
1142
|
this.emitEvent({ type: 'state', state: this.getControlState() });
|
|
1093
1143
|
}
|
|
1094
1144
|
|
package/lib/commands/web.js
CHANGED
|
@@ -377,6 +377,12 @@ async function webCommand(options) {
|
|
|
377
377
|
if (payload.type === 'codex-compact') {
|
|
378
378
|
sessionManager.compactCodexContext(sessionId).catch(error => logger.error(`Codex compact error: ${error.message}`));
|
|
379
379
|
}
|
|
380
|
+
if (payload.type === 'codex-history-before') {
|
|
381
|
+
const codex = sessionManager.get(sessionId);
|
|
382
|
+
if (codex && codex.kind === 'codex-structured' && codex.presentation === 'structured') {
|
|
383
|
+
ws.send(JSON.stringify({ type: 'codex-history-page', page: codex.getMessagePage(payload.beforeId) }));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
380
386
|
if (payload.type === 'codex-abort') {
|
|
381
387
|
sessionManager.abortCodex(sessionId);
|
|
382
388
|
}
|
package/lib/web/codex.js
CHANGED
|
@@ -320,7 +320,46 @@
|
|
|
320
320
|
const current = container.firstElementChild;
|
|
321
321
|
if (!current) container.appendChild(next);
|
|
322
322
|
else syncCodexDom(current, next);
|
|
323
|
-
|
|
323
|
+
if (codexHistoryPrependAnchor) {
|
|
324
|
+
const anchor = codexHistoryPrependAnchor;
|
|
325
|
+
codexHistoryPrependAnchor = null;
|
|
326
|
+
container.scrollTop = anchor.scrollTop + Math.max(0, container.scrollHeight - anchor.scrollHeight);
|
|
327
|
+
} else {
|
|
328
|
+
container.scrollTop = shouldStickToBottom ? container.scrollHeight : previousScrollTop;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function applyCodexHistoryPageMeta(page = {}) {
|
|
333
|
+
codexHistoryBeforeId = page?.beforeId || codexMessages[0]?.id || null;
|
|
334
|
+
codexHistoryHasMore = Boolean(page?.hasMore);
|
|
335
|
+
codexHistoryLoading = false;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function handleCodexHistoryScroll(event) {
|
|
339
|
+
if (event?.isTrusted) codexHistoryUserScrolled = true;
|
|
340
|
+
if (!codexHistoryUserScrolled || !codexHistoryHasMore || codexHistoryLoading) return;
|
|
341
|
+
const container = event?.currentTarget || document.getElementById('codex-chat-container');
|
|
342
|
+
const scrollRange = Math.max(0, container.scrollHeight - container.clientHeight);
|
|
343
|
+
if (container.scrollTop > scrollRange * 0.5) return;
|
|
344
|
+
if (!codexHistoryBeforeId || currentSocket?.readyState !== 1) return;
|
|
345
|
+
codexHistoryLoading = true;
|
|
346
|
+
currentSocket.send(JSON.stringify({ type: 'codex-history-before', beforeId: codexHistoryBeforeId }));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function applyCodexHistoryPage(page = {}) {
|
|
350
|
+
const older = Array.isArray(page.messages) ? page.messages : [];
|
|
351
|
+
const knownIds = new Set(codexMessages.map(item => String(item.id || '')));
|
|
352
|
+
const uniqueOlder = older.filter(item => item?.id && !knownIds.has(String(item.id)));
|
|
353
|
+
const container = document.getElementById('codex-chat-container');
|
|
354
|
+
if (uniqueOlder.length && container) {
|
|
355
|
+
codexHistoryPrependAnchor = {
|
|
356
|
+
scrollTop: container.scrollTop,
|
|
357
|
+
scrollHeight: container.scrollHeight
|
|
358
|
+
};
|
|
359
|
+
codexMessages = [...uniqueOlder, ...codexMessages];
|
|
360
|
+
}
|
|
361
|
+
applyCodexHistoryPageMeta(page);
|
|
362
|
+
if (uniqueOlder.length) renderCodexChat();
|
|
324
363
|
}
|
|
325
364
|
|
|
326
365
|
function applyCodexState(state = {}) {
|
|
@@ -445,7 +484,12 @@
|
|
|
445
484
|
if (!event) return;
|
|
446
485
|
if (event.type === 'message' && event.message) codexMessages.push(event.message);
|
|
447
486
|
else if (event.type === 'message-updated' && event.message) { const i = codexMessages.findIndex(item => item.id === event.message.id); if (i >= 0) codexMessages[i] = event.message; else codexMessages.push(event.message); }
|
|
448
|
-
else if (event.type === 'history-reset')
|
|
487
|
+
else if (event.type === 'history-reset') {
|
|
488
|
+
codexMessages = event.messages || [];
|
|
489
|
+
codexHistoryUserScrolled = false;
|
|
490
|
+
codexHistoryPrependAnchor = null;
|
|
491
|
+
applyCodexHistoryPageMeta(event.historyPage);
|
|
492
|
+
}
|
|
449
493
|
else if (event.type === 'permission-request' && event.request) { codexPendingPermissions = [...codexPendingPermissions.filter(item => item.id !== event.request.id), event.request]; }
|
|
450
494
|
else if (event.type === 'permission-updated' && event.request) codexPendingPermissions = codexPendingPermissions.map(item => item.id === event.request.id ? event.request : item);
|
|
451
495
|
if (event.state) applyCodexState(event.state); else renderCodexChat();
|
package/lib/web/core.js
CHANGED
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
let selectedCodexSkill = null;
|
|
53
53
|
let codexRenderFrame = null;
|
|
54
54
|
let codexApprovalJumpIndex = 0;
|
|
55
|
+
let codexHistoryBeforeId = null;
|
|
56
|
+
let codexHistoryHasMore = false;
|
|
57
|
+
let codexHistoryLoading = false;
|
|
58
|
+
let codexHistoryUserScrolled = false;
|
|
59
|
+
let codexHistoryPrependAnchor = null;
|
|
55
60
|
const modifiers = { ctrl: false };
|
|
56
61
|
|
|
57
62
|
function log(msg) {
|
package/lib/web/session.js
CHANGED
|
@@ -143,6 +143,11 @@
|
|
|
143
143
|
codexSkillQuery = '';
|
|
144
144
|
codexSkillError = '';
|
|
145
145
|
selectedCodexSkill = null;
|
|
146
|
+
codexHistoryBeforeId = null;
|
|
147
|
+
codexHistoryHasMore = false;
|
|
148
|
+
codexHistoryLoading = false;
|
|
149
|
+
codexHistoryUserScrolled = false;
|
|
150
|
+
codexHistoryPrependAnchor = null;
|
|
146
151
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
147
152
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
148
153
|
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
@@ -152,6 +157,7 @@
|
|
|
152
157
|
codexState = { permissionMode: 'default', sandboxMode: 'default', effectivePermissionMode: null, effectiveSandboxMode: null, model: null, effort: null, status: 'idle', threadId: null, presentation: 'structured', models: [], canAbort: false, canCompact: false, compacting: false, canSwitchToTerminal: false, canSwitchToStructured: false };
|
|
153
158
|
setClaudeModeEnabled(false);
|
|
154
159
|
applyCodexState(codexState);
|
|
160
|
+
document.getElementById('codex-chat-container').onscroll = handleCodexHistoryScroll;
|
|
155
161
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
156
162
|
currentSocket = new WebSocket(protocol + '//' + window.location.host + '?sessionId=' + sessionId);
|
|
157
163
|
currentSocket.onmessage = (e) => {
|
|
@@ -159,8 +165,10 @@
|
|
|
159
165
|
if (msg.type === 'codex-snapshot' && msg.snapshot) {
|
|
160
166
|
codexMessages = msg.snapshot.messages || [];
|
|
161
167
|
codexPendingPermissions = msg.snapshot.pendingPermissions || [];
|
|
168
|
+
applyCodexHistoryPageMeta(msg.snapshot.historyPage);
|
|
162
169
|
applyCodexState(msg.snapshot.state || {});
|
|
163
170
|
}
|
|
171
|
+
if (msg.type === 'codex-history-page' && msg.page) applyCodexHistoryPage(msg.page);
|
|
164
172
|
if (msg.type === 'codex-event') {
|
|
165
173
|
applyCodexEvent(msg.event);
|
|
166
174
|
if (msg.event?.type === 'presentation' && msg.event.presentation === 'terminal') {
|