glad-web 1.0.28 → 1.0.29

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.
@@ -167,6 +167,7 @@ class CodexStructuredSession extends EventEmitter {
167
167
  this.currentTurnId = null;
168
168
  this.currentTurnStartedAt = null;
169
169
  this.threadTurns = new Map();
170
+ this.providerItemContexts = new Map();
170
171
  this.tokenUsage = null;
171
172
  this.permissionMode = normalizePermissionMode(options.permissionMode);
172
173
  this.sandboxMode = normalizeSandboxMode(options.sandboxMode);
@@ -481,17 +482,27 @@ class CodexStructuredSession extends EventEmitter {
481
482
  if (method === 'item/plan/delta') {
482
483
  const providerId = String(params.itemId || '');
483
484
  const target = this.messages.find(item => item.providerId === providerId && item.kind === 'reasoning');
484
- if (target) this.patch(target.id, { text: String(target.text || '') + String(params.delta || '') });
485
- else this.append({ kind: 'reasoning', providerId, text: String(params.delta || ''), streaming: true });
485
+ const known = this.providerItemContexts.get(providerId) || {};
486
+ const threadId = params.threadId || target?.threadId || known.threadId || null;
487
+ const turnId = params.turnId || target?.turnId || known.turnId
488
+ || (threadId ? this.threadTurns.get(threadId)?.turnId : null) || this.currentTurnId;
489
+ if (providerId && (threadId || turnId)) this.providerItemContexts.set(providerId, { threadId, turnId });
490
+ if (target) this.patch(target.id, { text: String(target.text || '') + String(params.delta || ''), threadId, turnId });
491
+ else this.append({ kind: 'reasoning', providerId, text: String(params.delta || ''), threadId, turnId, streaming: true });
486
492
  return;
487
493
  }
488
494
  if (method.includes('agentMessage/delta') || method.includes('reasoning/textDelta') || method.includes('reasoning/summaryTextDelta')) {
489
495
  const kind = method.includes('agentMessage') ? 'assistant' : 'reasoning';
490
496
  const itemId = String(params.itemId || params.id || '');
491
497
  const target = this.messages.find(item => item.providerId === itemId && item.kind === kind);
498
+ const known = this.providerItemContexts.get(itemId) || {};
499
+ const threadId = params.threadId || target?.threadId || known.threadId || null;
500
+ const turnId = params.turnId || target?.turnId || known.turnId
501
+ || (threadId ? this.threadTurns.get(threadId)?.turnId : null) || this.currentTurnId;
492
502
  const delta = String(params.delta || '');
493
- if (target) this.patch(target.id, { text: (target.text || '') + delta });
494
- else this.append({ kind, providerId: itemId, text: delta, streaming: true });
503
+ if (itemId && (threadId || turnId)) this.providerItemContexts.set(itemId, { threadId, turnId });
504
+ if (target) this.patch(target.id, { text: (target.text || '') + delta, threadId, turnId });
505
+ else this.append({ kind, providerId: itemId, text: delta, threadId, turnId, streaming: true });
495
506
  return;
496
507
  }
497
508
  if (method.startsWith('item/')) {
@@ -519,6 +530,7 @@ class CodexStructuredSession extends EventEmitter {
519
530
  const threadId = raw.threadId || context.threadId || null;
520
531
  const trackedTurnId = threadId ? this.threadTurns.get(threadId)?.turnId : null;
521
532
  const turnId = raw.turnId || context.turnId || trackedTurnId || this.currentTurnId;
533
+ if (providerId && (threadId || turnId)) this.providerItemContexts.set(providerId, { threadId, turnId });
522
534
  const existingStartedAtMs = existing?.startedAtMs || existing?.createdAt || null;
523
535
  const startedAtMs = Number(context.startedAtMs || raw.startedAtMs || 0)
524
536
  || toTimestampMs(raw.startedAt || raw.createdAt) || existingStartedAtMs;
@@ -836,6 +848,7 @@ class CodexStructuredSession extends EventEmitter {
836
848
  this.tokenUsage = thread?.tokenUsage || thread?.token_usage || this.tokenUsage;
837
849
  this.messages = [];
838
850
  this.completedPermissions = [];
851
+ this.providerItemContexts.clear();
839
852
  for (const turn of thread?.turns || []) {
840
853
  const status = turn.status === 'failed' ? 'failed' : turn.status === 'interrupted' ? 'cancelled' : 'completed';
841
854
  const startedAt = Number(turn.startedAt || turn.createdAt || 0);
@@ -846,6 +859,7 @@ class CodexStructuredSession extends EventEmitter {
846
859
  this.append({ kind: 'turn-start', turnId: turn.id, ...(startedAtMs ? { createdAt: startedAtMs } : {}) });
847
860
  for (const item of turn.items || []) {
848
861
  this.applyProviderItem({ ...item, turnId: turn.id }, status === 'failed' ? 'failed' : 'completed', {
862
+ threadId: this.threadId,
849
863
  startedAtMs,
850
864
  completedAtMs
851
865
  });
@@ -131,6 +131,11 @@
131
131
  .codex-work-group > summary::before { content: '›'; width: 12px; color: #8e8e93; font-size: 17px; transform-origin: center; }
132
132
  .codex-work-group[open] > summary::before { transform: rotate(90deg); }
133
133
  .codex-work-group-body { padding-left: 2px; }
134
+ .codex-subagent-group { border-left-color: rgba(100,210,255,.24); }
135
+ .codex-subagent-group > summary { color: #8e9aa6; }
136
+ .codex-subagent-group > summary::before { color: #64a9d1; }
137
+ .codex-subagent-message { margin: 7px 0; padding: 7px 9px; border-left: 1px solid rgba(255,255,255,.1); color: #c7c7cc; font-size: 12px; line-height: 1.42; overflow-wrap: anywhere; }
138
+ .codex-subagent-message.task { color: #8e8e93; font-style: italic; }
134
139
  .codex-patch-file { border-top: 1px solid rgba(255,255,255,.08); }
135
140
  .codex-patch-file:first-child { border-top: 0; }
136
141
  .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; }
@@ -2203,6 +2208,46 @@
2203
2208
  const key = items.map(item => item.id || item.providerId || '').join('-');
2204
2209
  return `<details class="codex-work-group" data-codex-key="group-${escapeHtml(key)}"><summary>${label} · ${items.length} ${items.length === 1 ? 'tool' : 'tools'}</summary><div class="codex-work-group-body">${tools}</div></details>`;
2205
2210
  }
2211
+ function isCodexSubagentItem(item) {
2212
+ return Boolean(item?.threadId && codexState.threadId && item.threadId !== codexState.threadId);
2213
+ }
2214
+ function renderCodexSubagentGroup(threadId, items, permissionById, usedPermissions) {
2215
+ const turnEnds = items.filter(item => item.kind === 'turn-end');
2216
+ const content = items.filter(item => !['reasoning', 'turn-start', 'turn-end'].includes(item.kind));
2217
+ const tools = content.filter(item => item.kind === 'tool');
2218
+ const messages = content.filter(item => item.kind === 'assistant' || item.kind === 'user');
2219
+ const running = tools.some(item => codexToolStatus(item) === 'running')
2220
+ || items.filter(item => item.kind === 'turn-start').length > turnEnds.length;
2221
+ const startedAt = Math.min(...items.map(item => Number(item.startedAtMs || item.createdAt || Infinity)));
2222
+ const completedAt = Math.max(...turnEnds.map(item => Number(item.createdAt || 0)),
2223
+ ...content.map(item => Number(item.completedAtMs || item.updatedAt || 0)));
2224
+ const duration = formatCodexDuration(Number.isFinite(startedAt) && completedAt >= startedAt
2225
+ ? completedAt - startedAt : 0);
2226
+ const counts = [];
2227
+ if (tools.length) counts.push(`${tools.length} ${tools.length === 1 ? 'tool' : 'tools'}`);
2228
+ if (messages.length) counts.push(`${messages.length} ${messages.length === 1 ? 'message' : 'messages'}`);
2229
+ const label = running ? 'Subagent working' : duration ? `Subagent worked for ${duration}` : 'Subagent worked';
2230
+ const body = [];
2231
+ for (let i = 0; i < content.length;) {
2232
+ const item = content[i];
2233
+ if (item.kind === 'tool') {
2234
+ const group = [];
2235
+ const turnId = item.turnId;
2236
+ while (i < content.length && content[i].kind === 'tool' && content[i].turnId === turnId) group.push(content[i++]);
2237
+ const turnEnd = turnEnds.find(candidate => candidate.turnId === turnId) || null;
2238
+ body.push(renderCodexToolGroup(group, permissionById, usedPermissions, turnEnd));
2239
+ continue;
2240
+ }
2241
+ if (item.kind === 'assistant' || item.kind === 'user') {
2242
+ body.push(`<div class="codex-subagent-message${item.kind === 'user' ? ' task' : ''}">${renderMarkdown(item.text || '')}</div>`);
2243
+ } else if (item.text) {
2244
+ body.push(`<div class="codex-subagent-message">${codexText(item.text)}</div>`);
2245
+ }
2246
+ i += 1;
2247
+ }
2248
+ const suffix = counts.length ? ` · ${counts.join(' · ')}` : '';
2249
+ return `<details class="codex-work-group codex-subagent-group" data-codex-key="subagent-${escapeHtml(threadId)}"><summary>${escapeHtml(label + suffix)}</summary><div class="codex-work-group-body">${body.join('')}</div></details>`;
2250
+ }
2206
2251
  function renderCodexMessageTime(item, finalOnly = false) {
2207
2252
  if (!item || (finalOnly && item.streaming)) return '';
2208
2253
  const timestamp = Number(finalOnly ? (item.completedAtMs || item.updatedAt || item.createdAt) : item.createdAt);
@@ -2265,9 +2310,26 @@
2265
2310
  const usedPermissions = new Set();
2266
2311
  const turnEndById = new Map(codexMessages.filter(item => item.kind === 'turn-end' && item.turnId)
2267
2312
  .map(item => [String(item.turnId), item]));
2313
+ const subagentItems = new Map();
2314
+ for (const item of codexMessages) {
2315
+ if (!isCodexSubagentItem(item)) continue;
2316
+ const threadId = String(item.threadId);
2317
+ if (!subagentItems.has(threadId)) subagentItems.set(threadId, []);
2318
+ subagentItems.get(threadId).push(item);
2319
+ }
2320
+ const renderedSubagents = new Set();
2268
2321
  const visible = codexMessages.filter(item => !['reasoning', 'turn-start', 'turn-end'].includes(item.kind));
2269
2322
  for (let i = 0; i < visible.length;) {
2270
2323
  const item = visible[i];
2324
+ if (isCodexSubagentItem(item)) {
2325
+ const threadId = String(item.threadId);
2326
+ if (!renderedSubagents.has(threadId)) {
2327
+ renderedSubagents.add(threadId);
2328
+ parts.push(renderCodexSubagentGroup(threadId, subagentItems.get(threadId) || [], permissionById, usedPermissions));
2329
+ }
2330
+ i += 1;
2331
+ continue;
2332
+ }
2271
2333
  if (item.kind === 'tool') {
2272
2334
  const tools = [];
2273
2335
  const turnId = item.turnId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glad-web",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
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": {