glad-web 1.0.37 → 1.0.38
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 +16 -1
- package/lib/web/claude.js +32 -8
- package/lib/web/core.js +6 -6
- package/lib/web/styles.css +3 -3
- package/package.json +1 -1
|
@@ -183,6 +183,12 @@ function toolDetails(raw) {
|
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
function codexReconnectProgress(message) {
|
|
187
|
+
const match = String(message || '').match(/\bReconnecting(?:\.\.\.)?\s*(\d+)\s*\/\s*(\d+)\b/i);
|
|
188
|
+
if (!match) return null;
|
|
189
|
+
return { attempt: Number(match[1]), maximum: Number(match[2]) };
|
|
190
|
+
}
|
|
191
|
+
|
|
186
192
|
class CodexStructuredSession extends EventEmitter {
|
|
187
193
|
constructor({ id, tool, workingDir, name, logger, options = {} }) {
|
|
188
194
|
super();
|
|
@@ -203,6 +209,7 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
203
209
|
this.threadId = options.resume || null;
|
|
204
210
|
this.currentTurnId = null;
|
|
205
211
|
this.currentTurnStartedAt = null;
|
|
212
|
+
this.reconnectAbortTurnId = null;
|
|
206
213
|
this.threadTurns = new Map();
|
|
207
214
|
this.turnContexts = new Map();
|
|
208
215
|
this.providerItemContexts = new Map();
|
|
@@ -575,7 +582,15 @@ class CodexStructuredSession extends EventEmitter {
|
|
|
575
582
|
return;
|
|
576
583
|
}
|
|
577
584
|
if (method === 'error') {
|
|
578
|
-
|
|
585
|
+
const message = params.error?.message || 'Codex reported an error.';
|
|
586
|
+
this.append({ kind: 'event', level: 'error', text: message });
|
|
587
|
+
const reconnect = codexReconnectProgress(message);
|
|
588
|
+
const turnId = String(params.turnId || this.currentTurnId || 'active');
|
|
589
|
+
if (params.willRetry && reconnect?.attempt === 4 && reconnect.maximum === 5
|
|
590
|
+
&& this.reconnectAbortTurnId !== turnId && this.abort('Aborted after Codex reconnect attempt 4/5.')) {
|
|
591
|
+
this.reconnectAbortTurnId = turnId;
|
|
592
|
+
this.emitEvent({ type: 'runtime-disconnected', activeTurn: true, turnId });
|
|
593
|
+
}
|
|
579
594
|
if (!params.willRetry) { this.compacting = false; this.setStatus('idle'); }
|
|
580
595
|
return;
|
|
581
596
|
}
|
package/lib/web/claude.js
CHANGED
|
@@ -497,6 +497,24 @@
|
|
|
497
497
|
return html;
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
+
function parseMarkdownFenceOpener(line) {
|
|
501
|
+
const match = String(line || '').match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
|
|
502
|
+
if (!match) return null;
|
|
503
|
+
const marker = match[1];
|
|
504
|
+
const info = match[2].trim();
|
|
505
|
+
if (marker[0] === '`' && info.includes('`')) return null;
|
|
506
|
+
return {
|
|
507
|
+
character: marker[0],
|
|
508
|
+
length: marker.length,
|
|
509
|
+
language: info.split(/\s+/, 1)[0] || ''
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function isMarkdownFenceCloser(line, opener) {
|
|
514
|
+
const match = String(line || '').match(/^\s{0,3}(`+|~+)\s*$/);
|
|
515
|
+
return Boolean(match && match[1][0] === opener.character && match[1].length >= opener.length);
|
|
516
|
+
}
|
|
517
|
+
|
|
500
518
|
function splitMarkdownBlocks(markdown) {
|
|
501
519
|
const lines = String(markdown || '').replace(/\r\n/g, '\n').split('\n');
|
|
502
520
|
const blocks = [];
|
|
@@ -506,17 +524,16 @@
|
|
|
506
524
|
i++;
|
|
507
525
|
continue;
|
|
508
526
|
}
|
|
509
|
-
const fence = lines[i]
|
|
527
|
+
const fence = parseMarkdownFenceOpener(lines[i]);
|
|
510
528
|
if (fence) {
|
|
511
|
-
const language = fence[1] || '';
|
|
512
529
|
const content = [];
|
|
513
530
|
i++;
|
|
514
|
-
while (i < lines.length &&
|
|
531
|
+
while (i < lines.length && !isMarkdownFenceCloser(lines[i], fence)) {
|
|
515
532
|
content.push(lines[i]);
|
|
516
533
|
i++;
|
|
517
534
|
}
|
|
518
535
|
if (i < lines.length) i++;
|
|
519
|
-
blocks.push({ type: 'code', language, content: content.join('\n') });
|
|
536
|
+
blocks.push({ type: 'code', language: fence.language, content: content.join('\n') });
|
|
520
537
|
continue;
|
|
521
538
|
}
|
|
522
539
|
if (/^\s*[-*_]{3,}\s*$/.test(lines[i])) {
|
|
@@ -541,13 +558,15 @@
|
|
|
541
558
|
continue;
|
|
542
559
|
}
|
|
543
560
|
if (/^\s*([-*+])\s+/.test(lines[i]) || /^\s*\d+\.\s+/.test(lines[i])) {
|
|
544
|
-
const
|
|
561
|
+
const orderedMatch = lines[i].match(/^\s*(\d+)\.\s+/);
|
|
562
|
+
const ordered = Boolean(orderedMatch);
|
|
563
|
+
const start = ordered ? Number(orderedMatch[1]) : null;
|
|
545
564
|
const items = [];
|
|
546
565
|
while (i < lines.length && (ordered ? /^\s*\d+\.\s+/.test(lines[i]) : /^\s*[-*+]\s+/.test(lines[i]))) {
|
|
547
566
|
items.push(lines[i].replace(ordered ? /^\s*\d+\.\s+/ : /^\s*[-*+]\s+/, ''));
|
|
548
567
|
i++;
|
|
549
568
|
}
|
|
550
|
-
blocks.push({ type: ordered ? 'ol' : 'ul', items });
|
|
569
|
+
blocks.push({ type: ordered ? 'ol' : 'ul', items, ...(ordered ? { start } : {}) });
|
|
551
570
|
continue;
|
|
552
571
|
}
|
|
553
572
|
if (/^\s*>\s?/.test(lines[i])) {
|
|
@@ -561,7 +580,12 @@
|
|
|
561
580
|
}
|
|
562
581
|
const paragraph = [];
|
|
563
582
|
while (i < lines.length && lines[i].trim()) {
|
|
564
|
-
if (
|
|
583
|
+
if (parseMarkdownFenceOpener(lines[i]) || /^\s{0,3}#{1,4}\s+/.test(lines[i]) || /^\s*([-*+])\s+/.test(lines[i]) || /^\s*\d+\.\s+/.test(lines[i])) break;
|
|
584
|
+
paragraph.push(lines[i]);
|
|
585
|
+
i++;
|
|
586
|
+
}
|
|
587
|
+
// Always make progress even if a future block detector and parser disagree.
|
|
588
|
+
if (!paragraph.length) {
|
|
565
589
|
paragraph.push(lines[i]);
|
|
566
590
|
i++;
|
|
567
591
|
}
|
|
@@ -580,7 +604,7 @@
|
|
|
580
604
|
if (block.type === 'hr') return '<hr>';
|
|
581
605
|
if (block.type === 'header') return `<h${block.level}>${inlineMarkdown(block.text)}</h${block.level}>`;
|
|
582
606
|
if (block.type === 'ul') return `<ul>${block.items.map(item => `<li>${inlineMarkdown(item)}</li>`).join('')}</ul>`;
|
|
583
|
-
if (block.type === 'ol') return `<ol>${block.items.map(item => `<li>${inlineMarkdown(item)}</li>`).join('')}</ol>`;
|
|
607
|
+
if (block.type === 'ol') return `<ol${block.start !== 1 ? ` start="${block.start}"` : ''}>${block.items.map(item => `<li>${inlineMarkdown(item)}</li>`).join('')}</ol>`;
|
|
584
608
|
if (block.type === 'quote') return `<blockquote>${renderMarkdown(block.text)}</blockquote>`;
|
|
585
609
|
if (block.type === 'table') return renderMarkdownTable(block.rows);
|
|
586
610
|
return `<p>${inlineMarkdown(block.text).replace(/\n/g, '<br>')}</p>`;
|
package/lib/web/core.js
CHANGED
|
@@ -229,18 +229,18 @@
|
|
|
229
229
|
<h3>${escapeHtml(s.name)}${s.hasUnreadCompletion ? '<span class="completion-dot" title="Completed"></span>' : ''}${timerBadge} <button class="icon-btn" onclick="renameSession('${s.id}', decodePathValue('${encodedName}'), event)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg></button></h3>
|
|
230
230
|
<p>${escapeHtml(s.tool)}</p>
|
|
231
231
|
<p>${new Date(s.startTime).toLocaleTimeString()}</p>
|
|
232
|
-
<div class="session-dir-row">
|
|
233
|
-
<button class="copy-dir-btn" title="Copy directory" onclick="copySessionDirectory(decodePathValue('${encodedDir}'), event)">
|
|
234
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
|
235
|
-
</button>
|
|
236
|
-
<p title="${escapeHtml(workingDirectory)}">${escapeHtml(workingDirectory)}</p>
|
|
237
|
-
</div>
|
|
238
232
|
</div>
|
|
239
233
|
<div class="session-actions">
|
|
240
234
|
${renderServerChanSessionAction(s)}
|
|
241
235
|
<button class="btn-join" onclick="joinSession('${s.id}', decodePathValue('${encodedName}'), '${escapeHtml(s.toolKey || '')}')">Connect</button>
|
|
242
236
|
<button class="icon-btn btn-delete" onclick="deleteSession('${s.id}', event)"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg></button>
|
|
243
237
|
</div>
|
|
238
|
+
<div class="session-dir-row">
|
|
239
|
+
<button class="copy-dir-btn" title="Copy directory" onclick="copySessionDirectory(decodePathValue('${encodedDir}'), event)">
|
|
240
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
|
241
|
+
</button>
|
|
242
|
+
<p title="${escapeHtml(workingDirectory)}">${escapeHtml(workingDirectory)}</p>
|
|
243
|
+
</div>
|
|
244
244
|
</div>`;
|
|
245
245
|
});
|
|
246
246
|
list.innerHTML = html;
|
package/lib/web/styles.css
CHANGED
|
@@ -11,18 +11,18 @@
|
|
|
11
11
|
.header-action-btn.icon-only { width: 36px; padding: 0; }
|
|
12
12
|
.header-action-btn:active { background: #0062cc; transform: scale(.97); }
|
|
13
13
|
.btn-retry { background: #333; color: #fff; border: none; padding: 8px 16px; border-radius: 20px; margin-top: 10px; cursor: pointer; }
|
|
14
|
-
.session-card { background: var(--card-bg); border-radius: 12px; padding: 16px; margin-bottom: 12px; display:
|
|
14
|
+
.session-card { background: var(--card-bg); border-radius: 12px; padding: 12px 16px 8px; margin-bottom: 12px; display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px; row-gap: 0; align-items: center; transition: transform 0.1s; position: relative; }
|
|
15
15
|
.session-card:active { transform: scale(0.98); }
|
|
16
16
|
.completion-dot { width: 9px; height: 9px; border-radius: 50%; background: #ff3b30; flex-shrink: 0; }
|
|
17
17
|
.session-info { flex: 1; min-width: 0; }
|
|
18
18
|
.session-info h3 { margin: 0 0 4px 0; font-size: 17px; display: flex; align-items: center; gap: 8px; }
|
|
19
19
|
.session-info p { margin: 0; font-size: 13px; color: var(--text-dim); }
|
|
20
20
|
.timer-count-badge { display: inline-flex; align-items: center; gap: 3px; color: #fff; background: rgba(0,122,255,0.22); border: 1px solid rgba(0,122,255,0.34); border-radius: 999px; padding: 2px 7px; font-size: 11px; font-weight: 800; flex-shrink: 0; }
|
|
21
|
-
.session-dir-row { display: flex; align-items: center; gap: 6px;
|
|
21
|
+
.session-dir-row { grid-column: 1 / -1; display: flex; align-items: center; gap: 6px; width: 100%; min-width: 0; }
|
|
22
22
|
.session-dir-row p { flex: 1; min-width: 0; font-family: monospace; overflow-wrap: anywhere; }
|
|
23
23
|
.copy-dir-btn { color: var(--text-dim); background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; width: 28px; height: 28px; padding: 0; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; }
|
|
24
24
|
.copy-dir-btn:active { color: #fff; background: rgba(255,255,255,0.12); }
|
|
25
|
-
.session-actions { display: flex; gap: 12px; align-items: center;
|
|
25
|
+
.session-actions { display: flex; gap: 12px; align-items: center; }
|
|
26
26
|
.serverchan-toggle { width: 34px; height: 34px; padding: 0; border: 1px solid rgba(255,255,255,0.1); border-radius: 50%; background: rgba(255,255,255,0.05); color: var(--text-dim); cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
27
27
|
.serverchan-toggle.active { color: #34c759; background: rgba(52,199,89,0.13); }
|
|
28
28
|
.serverchan-toggle:active { color: #fff; background: rgba(255,255,255,0.12); }
|