glad-web 1.0.37 → 1.0.39
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 +54 -16
- 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
|
@@ -486,15 +486,47 @@
|
|
|
486
486
|
}
|
|
487
487
|
|
|
488
488
|
function inlineMarkdown(text) {
|
|
489
|
+
const protectedSegments = [];
|
|
490
|
+
const protect = value => `\uE000${protectedSegments.push(value) - 1}\uE001`;
|
|
491
|
+
const restore = value => value.replace(/\uE000(\d+)\uE001/g, (_match, index) => protectedSegments[Number(index)] || '');
|
|
492
|
+
const formatText = value => {
|
|
493
|
+
const codeSegments = [];
|
|
494
|
+
const protectCode = code => `\uE002${codeSegments.push(code) - 1}\uE003`;
|
|
495
|
+
const restoreCode = formatted => formatted.replace(/\uE002(\d+)\uE003/g, (_match, index) => codeSegments[Number(index)] || '');
|
|
496
|
+
let formatted = value.replace(/`([^`]+)`/g, (_match, code) => protectCode(`<code>${code}</code>`));
|
|
497
|
+
formatted = formatted.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
498
|
+
formatted = formatted.replace(/(^|[^\p{L}\p{N}_])__([^_\n]+)__(?![\p{L}\p{N}_])/gu, '$1<strong>$2</strong>');
|
|
499
|
+
formatted = formatted.replace(/\*([^*\n]+)\*/g, '<em>$1</em>');
|
|
500
|
+
formatted = formatted.replace(/(^|[^\p{L}\p{N}_])_([^_\n]+)_(?![\p{L}\p{N}_])/gu, '$1<em>$2</em>');
|
|
501
|
+
return restoreCode(formatted);
|
|
502
|
+
};
|
|
503
|
+
|
|
489
504
|
let html = escapeHtml(text || '');
|
|
490
|
-
html = html.replace(/`([^`]+)
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
505
|
+
html = html.replace(/`([^`]+)`|!\[([^\]]*)\]\((https?:\/\/[^)\s]+)\)|\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g,
|
|
506
|
+
(_match, code, alt, imageUrl, label, linkUrl) => {
|
|
507
|
+
if (code !== undefined) return protect(`<code>${code}</code>`);
|
|
508
|
+
if (imageUrl !== undefined) return protect(`<img src="${imageUrl}" alt="${alt}">`);
|
|
509
|
+
return protect(`<a href="${linkUrl}" target="_blank" rel="noopener noreferrer">${formatText(label)}</a>`);
|
|
510
|
+
});
|
|
511
|
+
return restore(formatText(html));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function parseMarkdownFenceOpener(line) {
|
|
515
|
+
const match = String(line || '').match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
|
|
516
|
+
if (!match) return null;
|
|
517
|
+
const marker = match[1];
|
|
518
|
+
const info = match[2].trim();
|
|
519
|
+
if (marker[0] === '`' && info.includes('`')) return null;
|
|
520
|
+
return {
|
|
521
|
+
character: marker[0],
|
|
522
|
+
length: marker.length,
|
|
523
|
+
language: info.split(/\s+/, 1)[0] || ''
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function isMarkdownFenceCloser(line, opener) {
|
|
528
|
+
const match = String(line || '').match(/^\s{0,3}(`+|~+)\s*$/);
|
|
529
|
+
return Boolean(match && match[1][0] === opener.character && match[1].length >= opener.length);
|
|
498
530
|
}
|
|
499
531
|
|
|
500
532
|
function splitMarkdownBlocks(markdown) {
|
|
@@ -506,17 +538,16 @@
|
|
|
506
538
|
i++;
|
|
507
539
|
continue;
|
|
508
540
|
}
|
|
509
|
-
const fence = lines[i]
|
|
541
|
+
const fence = parseMarkdownFenceOpener(lines[i]);
|
|
510
542
|
if (fence) {
|
|
511
|
-
const language = fence[1] || '';
|
|
512
543
|
const content = [];
|
|
513
544
|
i++;
|
|
514
|
-
while (i < lines.length &&
|
|
545
|
+
while (i < lines.length && !isMarkdownFenceCloser(lines[i], fence)) {
|
|
515
546
|
content.push(lines[i]);
|
|
516
547
|
i++;
|
|
517
548
|
}
|
|
518
549
|
if (i < lines.length) i++;
|
|
519
|
-
blocks.push({ type: 'code', language, content: content.join('\n') });
|
|
550
|
+
blocks.push({ type: 'code', language: fence.language, content: content.join('\n') });
|
|
520
551
|
continue;
|
|
521
552
|
}
|
|
522
553
|
if (/^\s*[-*_]{3,}\s*$/.test(lines[i])) {
|
|
@@ -541,13 +572,15 @@
|
|
|
541
572
|
continue;
|
|
542
573
|
}
|
|
543
574
|
if (/^\s*([-*+])\s+/.test(lines[i]) || /^\s*\d+\.\s+/.test(lines[i])) {
|
|
544
|
-
const
|
|
575
|
+
const orderedMatch = lines[i].match(/^\s*(\d+)\.\s+/);
|
|
576
|
+
const ordered = Boolean(orderedMatch);
|
|
577
|
+
const start = ordered ? Number(orderedMatch[1]) : null;
|
|
545
578
|
const items = [];
|
|
546
579
|
while (i < lines.length && (ordered ? /^\s*\d+\.\s+/.test(lines[i]) : /^\s*[-*+]\s+/.test(lines[i]))) {
|
|
547
580
|
items.push(lines[i].replace(ordered ? /^\s*\d+\.\s+/ : /^\s*[-*+]\s+/, ''));
|
|
548
581
|
i++;
|
|
549
582
|
}
|
|
550
|
-
blocks.push({ type: ordered ? 'ol' : 'ul', items });
|
|
583
|
+
blocks.push({ type: ordered ? 'ol' : 'ul', items, ...(ordered ? { start } : {}) });
|
|
551
584
|
continue;
|
|
552
585
|
}
|
|
553
586
|
if (/^\s*>\s?/.test(lines[i])) {
|
|
@@ -561,7 +594,12 @@
|
|
|
561
594
|
}
|
|
562
595
|
const paragraph = [];
|
|
563
596
|
while (i < lines.length && lines[i].trim()) {
|
|
564
|
-
if (
|
|
597
|
+
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;
|
|
598
|
+
paragraph.push(lines[i]);
|
|
599
|
+
i++;
|
|
600
|
+
}
|
|
601
|
+
// Always make progress even if a future block detector and parser disagree.
|
|
602
|
+
if (!paragraph.length) {
|
|
565
603
|
paragraph.push(lines[i]);
|
|
566
604
|
i++;
|
|
567
605
|
}
|
|
@@ -580,7 +618,7 @@
|
|
|
580
618
|
if (block.type === 'hr') return '<hr>';
|
|
581
619
|
if (block.type === 'header') return `<h${block.level}>${inlineMarkdown(block.text)}</h${block.level}>`;
|
|
582
620
|
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>`;
|
|
621
|
+
if (block.type === 'ol') return `<ol${block.start !== 1 ? ` start="${block.start}"` : ''}>${block.items.map(item => `<li>${inlineMarkdown(item)}</li>`).join('')}</ol>`;
|
|
584
622
|
if (block.type === 'quote') return `<blockquote>${renderMarkdown(block.text)}</blockquote>`;
|
|
585
623
|
if (block.type === 'table') return renderMarkdownTable(block.rows);
|
|
586
624
|
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); }
|