@yeaft/webchat-agent 0.1.1105 → 1.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.1105",
3
+ "version": "1.0.2",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -413,41 +413,24 @@ export class DebugTrace {
413
413
  }
414
414
 
415
415
  /**
416
- * Fetch the recent debug history for the YeaftDebugPanel. Returns one
417
- * record per LLM loop (ordered oldest → newest) with the structured
418
- * fields the panel expects. JSON columns are parsed; truncated /
419
- * malformed payloads degrade to null instead of failing the call.
416
+ * Fetch debug history for the YeaftDebugPanel.
420
417
  *
421
- * @param {{ limit?: number, dreamLimit?: number, sessionId?: string|null, threadId?: string|null }} [opts]
422
- * @returns {{ loops: object[], turns: object[], dreamEvents: object[] }}
418
+ * Default mode returns recent loop details for backward compatibility.
419
+ * `indexOnly` returns all matching request summaries without loop payloads
420
+ * so the panel can list every past request cheaply. `limit` is intentionally
421
+ * ignored in index-only mode; the returned `limit` only reports the requested
422
+ * retention window used by older/detail paths. `detailTurnId` returns the
423
+ * full loop/tool payload for one request on demand.
424
+ *
425
+ * @param {{ limit?: number, dreamLimit?: number, sessionId?: string|null, threadId?: string|null, indexOnly?: boolean, detailTurnId?: string|null }} [opts]
426
+ * @returns {{ loops: object[], turns: object[], dreamEvents: object[], hasMore?: boolean, indexOnly?: boolean, detailTurnId?: string|null }}
423
427
  */
424
- fetchRecentDebugHistory({ limit = 100, dreamLimit = 5, sessionId = null, threadId = null } = {}) {
428
+ fetchRecentDebugHistory({ limit = 100, dreamLimit = 5, sessionId = null, threadId = null, indexOnly = false, detailTurnId = null } = {}) {
425
429
  const lim = Math.max(1, Math.min(500, Number(limit) || 100));
426
430
  const dreamLim = Number.isFinite(Number(dreamLimit))
427
431
  ? Math.max(0, Math.min(50, Number(dreamLimit)))
428
432
  : 5;
429
- const where = [];
430
- const args = [];
431
- if (sessionId) { where.push('group_id = ?'); args.push(sessionId); }
432
- if (threadId) { where.push('thread_id = ?'); args.push(threadId); }
433
- const sql = `
434
- SELECT * FROM trace_turns
435
- ${where.length ? `WHERE ${where.join(' AND ')}` : ''}
436
- ORDER BY started_at DESC
437
- LIMIT ?
438
- `;
439
- // Fetch one extra row so the UI can tell whether older history exists
440
- // without issuing a second COUNT(*) against the hot debug trace table.
441
- args.push(lim + 1);
442
- const fetchedRows = this.#db.prepare(sql).all(...args);
443
- const hasMore = fetchedRows.length > lim;
444
- const rows = hasMore ? fetchedRows.slice(0, lim) : fetchedRows;
445
- const turnIds = rows.map(r => r.id);
446
- const tools = turnIds.length > 0
447
- ? this.#db.prepare(
448
- `SELECT * FROM trace_tools WHERE turn_id IN (${turnIds.map(() => '?').join(',')}) ORDER BY created_at`
449
- ).all(...turnIds)
450
- : [];
433
+ const requestedDetailTurnId = typeof detailTurnId === 'string' && detailTurnId ? detailTurnId : null;
451
434
  const parseJsonSafe = (s) => {
452
435
  if (s == null) return null;
453
436
  try { return JSON.parse(s); }
@@ -472,104 +455,12 @@ export class DebugTrace {
472
455
  : totalInputTokens + outputTokens,
473
456
  };
474
457
  };
475
- // Group rows by (turnId, threadId, sessionId, vpId) → frontend Turn
476
- // record. Each row is also surfaced as a Loop.
477
- const duplicateLoopTraceIds = new Set();
478
- const seenLoopKeys = new Set();
479
- for (const r of rows) {
480
- const traceId = r.trace_id || r.id;
481
- const key = `${traceId}#${r.turn_number || 0}`;
482
- if (seenLoopKeys.has(key)) duplicateLoopTraceIds.add(traceId);
483
- else seenLoopKeys.add(key);
484
- }
485
- // Legacy rows used one Engine-instance trace_id for many user requests.
486
- // That creates duplicate Loop 1/2/... rows under the same trace. Split
487
- // only those corrupted traces by SQLite row id; healthy rows keep trace_id
488
- // so multi-loop requests still hydrate as one turn. Keep this as one
489
- // function so loop hydration and tool attachment use the same identity.
490
- const turnKeyForRow = (r) => {
491
- const baseTurnId = r.trace_id || r.id;
492
- return duplicateLoopTraceIds.has(baseTurnId) ? (r.id || baseTurnId) : baseTurnId;
493
- };
494
- const loopInstanceIdForRow = (r) => {
495
- const baseTurnId = r.trace_id || r.id;
496
- return duplicateLoopTraceIds.has(baseTurnId) ? (r.id || `${baseTurnId}#${r.turn_number || 0}`) : null;
497
- };
498
- const turnsById = new Map();
499
- const loops = rows.map((r) => {
500
- const parsedMessages = parseJsonSafe(r.messages_json) || [];
501
- const parsedUsage = parseJsonSafe(r.usage_json);
502
- const hydratedTurnId = turnKeyForRow(r);
503
- const loopInstanceId = loopInstanceIdForRow(r);
504
- const loop = {
505
- turnId: hydratedTurnId,
506
- ...(loopInstanceId ? { loopInstanceId } : {}),
507
- loopNumber: r.turn_number || 0,
508
- model: r.model || null,
509
- systemPrompt: r.system_prompt || '',
510
- messages: parsedMessages,
511
- response: r.response_text || '',
512
- toolCalls: parseJsonSafe(r.tool_calls_json) || [],
513
- usage: normalizeUsage(parsedUsage, r),
514
- latencyMs: r.latency_ms || 0,
515
- ttfbMs: r.ttfb_ms || null,
516
- stopReason: r.stop_reason || null,
517
- rawRequest: r.raw_request || null,
518
- rawResponse: r.raw_response || null,
519
- sessionId: r.group_id || null,
520
- vpId: r.vp_id || null,
521
- threadId: r.thread_id || null,
522
- };
523
- if (!turnsById.has(hydratedTurnId)) {
524
- turnsById.set(hydratedTurnId, {
525
- turnId: hydratedTurnId,
526
- // C2 fix: read the explicit `user_prompt` column persisted at
527
- // startTurn time. Deriving from messages_json is unsafe — each
528
- // tool-loop iteration overwrites messages_json with the
529
- // cumulative conversation snapshot, so `messages[0].content`
530
- // would be turn-1's prompt for every subsequent turn header.
531
- userPrompt: r.user_prompt || '',
532
- sessionId: r.group_id || null,
533
- vpId: r.vp_id || null,
534
- threadId: r.thread_id || null,
535
- openedAt: r.started_at || 0,
536
- closedAt: r.ended_at || null,
537
- totalMs: 0,
538
- totalTokens: 0,
539
- loopCount: 0,
540
- memoryLoaded: null,
541
- memoryAdjust: null,
542
- tools: [],
543
- });
544
- }
545
- const t = turnsById.get(hydratedTurnId);
546
- t.loopCount += 1;
547
- // Aggregate per-loop latency / tokens so the Turn header shows the
548
- // same totals the live `turn_close` event would have stamped.
549
- t.totalMs += r.latency_ms || 0;
550
- t.totalTokens += loop.usage.totalTokens || 0;
551
- if (r.ended_at && (!t.closedAt || r.ended_at > t.closedAt)) t.closedAt = r.ended_at;
552
- return loop;
553
- });
554
- // Attach tools to their parent Turn so the panel can render per-tool
555
- // timing without scanning the loop bodies.
556
- for (const tool of tools) {
557
- // Find which loop row this tool belongs to; use the same hydrated
558
- // identity as the loop/turn records so split legacy rows keep tools.
559
- const owner = rows.find(r => r.id === tool.turn_id);
560
- if (!owner) continue;
561
- const t = turnsById.get(turnKeyForRow(owner));
562
- if (!t) continue;
563
- t.tools.push({
564
- loopNumber: owner.turn_number || 0,
565
- callId: tool.tool_call_id || tool.id,
566
- traceToolId: tool.id,
567
- name: tool.tool_name,
568
- toolOutput: tool.tool_output == null ? null : String(tool.tool_output),
569
- durationMs: tool.duration_ms || 0,
570
- isError: !!tool.is_error,
571
- });
572
- }
458
+ const scopedWhere = [];
459
+ const scopedArgs = [];
460
+ if (sessionId) { scopedWhere.push('group_id = ?'); scopedArgs.push(sessionId); }
461
+ if (threadId) { scopedWhere.push('thread_id = ?'); scopedArgs.push(threadId); }
462
+ const whereSql = scopedWhere.length ? `WHERE ${scopedWhere.join(' AND ')}` : '';
463
+
573
464
  const dreamEvents = [];
574
465
  if (dreamLim > 0) {
575
466
  const eventRows = this.#db.prepare(`
@@ -597,10 +488,167 @@ export class DebugTrace {
597
488
  dreamEvents.reverse();
598
489
  }
599
490
 
600
- // Reverse to oldest-first so the panel's existing append-driven UI
601
- // renders in chronological order on hydration.
602
- loops.reverse();
603
- return { loops, turns: Array.from(turnsById.values()), dreamEvents, hasMore, limit: lim };
491
+ const duplicateLoopTraceIdsForRows = (rows) => {
492
+ const duplicateLoopTraceIds = new Set();
493
+ const seenLoopKeys = new Set();
494
+ for (const r of rows) {
495
+ const traceId = r.trace_id || r.id;
496
+ const key = `${traceId}#${r.turn_number || 0}`;
497
+ if (seenLoopKeys.has(key)) duplicateLoopTraceIds.add(traceId);
498
+ else seenLoopKeys.add(key);
499
+ }
500
+ return duplicateLoopTraceIds;
501
+ };
502
+ const detailRequestedByRowId = (rows) => {
503
+ if (!requestedDetailTurnId || rows.length !== 1) return false;
504
+ const row = rows[0];
505
+ return row?.id === requestedDetailTurnId && row?.trace_id !== requestedDetailTurnId;
506
+ };
507
+ const summarizeRows = (rows, duplicateLoopTraceIds = duplicateLoopTraceIdsForRows(rows), forceRowId = false) => {
508
+ const turnKeyForRow = (r) => {
509
+ if (forceRowId) return r.id || r.trace_id;
510
+ const baseTurnId = r.trace_id || r.id;
511
+ return duplicateLoopTraceIds.has(baseTurnId) ? (r.id || baseTurnId) : baseTurnId;
512
+ };
513
+ const turnsById = new Map();
514
+ for (const r of rows) {
515
+ const hydratedTurnId = turnKeyForRow(r);
516
+ const parsedUsage = parseJsonSafe(r.usage_json);
517
+ const usage = normalizeUsage(parsedUsage, r);
518
+ if (!turnsById.has(hydratedTurnId)) {
519
+ turnsById.set(hydratedTurnId, {
520
+ turnId: hydratedTurnId,
521
+ userPrompt: r.user_prompt || '',
522
+ sessionId: r.group_id || null,
523
+ vpId: r.vp_id || null,
524
+ threadId: r.thread_id || null,
525
+ openedAt: r.started_at || 0,
526
+ closedAt: r.ended_at || null,
527
+ totalMs: 0,
528
+ totalTokens: 0,
529
+ summaryInputTokens: 0,
530
+ summaryOutputTokens: 0,
531
+ loopCount: 0,
532
+ memoryLoaded: null,
533
+ memoryAdjust: null,
534
+ tools: [],
535
+ detailsLoaded: false,
536
+ });
537
+ }
538
+ const t = turnsById.get(hydratedTurnId);
539
+ t.loopCount += 1;
540
+ t.totalMs += r.latency_ms || 0;
541
+ t.totalTokens += usage.totalTokens || 0;
542
+ t.summaryInputTokens += usage.totalInputTokens || 0;
543
+ t.summaryOutputTokens += usage.outputTokens || 0;
544
+ if (r.started_at && (!t.openedAt || r.started_at < t.openedAt)) t.openedAt = r.started_at;
545
+ if (r.ended_at && (!t.closedAt || r.ended_at > t.closedAt)) t.closedAt = r.ended_at;
546
+ if (!t.userPrompt && r.user_prompt) t.userPrompt = r.user_prompt;
547
+ }
548
+ return Array.from(turnsById.values()).sort((a, b) => (a.openedAt || 0) - (b.openedAt || 0));
549
+ };
550
+ const expandRows = (rows) => {
551
+ const duplicateLoopTraceIds = duplicateLoopTraceIdsForRows(rows);
552
+ const forceRowId = detailRequestedByRowId(rows);
553
+ const turnKeyForRow = (r) => {
554
+ if (forceRowId) return r.id || r.trace_id;
555
+ const baseTurnId = r.trace_id || r.id;
556
+ return duplicateLoopTraceIds.has(baseTurnId) ? (r.id || baseTurnId) : baseTurnId;
557
+ };
558
+ const loopInstanceIdForRow = (r) => {
559
+ const baseTurnId = r.trace_id || r.id;
560
+ return forceRowId || duplicateLoopTraceIds.has(baseTurnId) ? (r.id || `${baseTurnId}#${r.turn_number || 0}`) : null;
561
+ };
562
+ const turnsById = new Map(summarizeRows(rows, duplicateLoopTraceIds, forceRowId).map((t) => [t.turnId, { ...t, detailsLoaded: true }]));
563
+ const loops = rows.map((r) => {
564
+ const parsedMessages = parseJsonSafe(r.messages_json) || [];
565
+ const parsedUsage = parseJsonSafe(r.usage_json);
566
+ const hydratedTurnId = turnKeyForRow(r);
567
+ const loopInstanceId = loopInstanceIdForRow(r);
568
+ return {
569
+ turnId: hydratedTurnId,
570
+ ...(loopInstanceId ? { loopInstanceId } : {}),
571
+ loopNumber: r.turn_number || 0,
572
+ model: r.model || null,
573
+ systemPrompt: r.system_prompt || '',
574
+ messages: parsedMessages,
575
+ response: r.response_text || '',
576
+ toolCalls: parseJsonSafe(r.tool_calls_json) || [],
577
+ usage: normalizeUsage(parsedUsage, r),
578
+ latencyMs: r.latency_ms || 0,
579
+ ttfbMs: r.ttfb_ms || null,
580
+ stopReason: r.stop_reason || null,
581
+ rawRequest: r.raw_request || null,
582
+ rawResponse: r.raw_response || null,
583
+ sessionId: r.group_id || null,
584
+ vpId: r.vp_id || null,
585
+ threadId: r.thread_id || null,
586
+ };
587
+ });
588
+ const turnIds = rows.map(r => r.id);
589
+ const tools = turnIds.length > 0
590
+ ? this.#db.prepare(
591
+ `SELECT * FROM trace_tools WHERE turn_id IN (${turnIds.map(() => '?').join(',')}) ORDER BY created_at`
592
+ ).all(...turnIds)
593
+ : [];
594
+ for (const tool of tools) {
595
+ const owner = rows.find(r => r.id === tool.turn_id);
596
+ if (!owner) continue;
597
+ const t = turnsById.get(turnKeyForRow(owner));
598
+ if (!t) continue;
599
+ t.tools.push({
600
+ loopNumber: owner.turn_number || 0,
601
+ callId: tool.tool_call_id || tool.id,
602
+ traceToolId: tool.id,
603
+ name: tool.tool_name,
604
+ toolOutput: tool.tool_output == null ? null : String(tool.tool_output),
605
+ durationMs: tool.duration_ms || 0,
606
+ isError: !!tool.is_error,
607
+ });
608
+ }
609
+ return { loops, turns: Array.from(turnsById.values()).sort((a, b) => (a.openedAt || 0) - (b.openedAt || 0)) };
610
+ };
611
+
612
+ if (requestedDetailTurnId) {
613
+ const detailWhere = [`(trace_id = ? OR id = ?)`];
614
+ const detailArgs = [requestedDetailTurnId, requestedDetailTurnId];
615
+ if (sessionId) { detailWhere.push('group_id = ?'); detailArgs.push(sessionId); }
616
+ if (threadId) { detailWhere.push('thread_id = ?'); detailArgs.push(threadId); }
617
+ detailArgs.push(5000);
618
+ const rows = this.#db.prepare(`
619
+ SELECT * FROM trace_turns
620
+ WHERE ${detailWhere.join(' AND ')}
621
+ ORDER BY started_at ASC, turn_number ASC, rowid ASC
622
+ LIMIT ?
623
+ `).all(...detailArgs);
624
+ const expanded = expandRows(rows);
625
+ return { ...expanded, dreamEvents, hasMore: false, limit: rows.length, indexOnly: false, detailTurnId: requestedDetailTurnId };
626
+ }
627
+
628
+ if (indexOnly) {
629
+ const rows = this.#db.prepare(`
630
+ SELECT id, trace_id, message_id, mode, turn_number, model,
631
+ input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
632
+ stop_reason, latency_ms, started_at, ended_at, group_id, vp_id,
633
+ thread_id, usage_json, user_prompt
634
+ FROM trace_turns
635
+ ${whereSql}
636
+ ORDER BY started_at ASC, turn_number ASC, rowid ASC
637
+ `).all(...scopedArgs);
638
+ return { loops: [], turns: summarizeRows(rows), dreamEvents, hasMore: false, limit: lim, indexOnly: true };
639
+ }
640
+
641
+ const args = [...scopedArgs, lim + 1];
642
+ const fetchedRows = this.#db.prepare(`
643
+ SELECT * FROM trace_turns
644
+ ${whereSql}
645
+ ORDER BY started_at DESC
646
+ LIMIT ?
647
+ `).all(...args);
648
+ const hasMore = fetchedRows.length > lim;
649
+ const rows = (hasMore ? fetchedRows.slice(0, lim) : fetchedRows).reverse();
650
+ const expanded = expandRows(rows);
651
+ return { ...expanded, dreamEvents, hasMore, limit: lim, indexOnly: false };
604
652
  }
605
653
 
606
654
  /**
@@ -34,10 +34,10 @@ function thinkingV1Enabled() {
34
34
  return process.env.YEAFT_THINKING_V1 === '1';
35
35
  }
36
36
 
37
- function applyAnthropicThinking(body, model, effort) {
38
- const cap = getThinkingCapability(model);
37
+ function applyAnthropicThinking(body, model, effort, effortContext = {}) {
38
+ const cap = getThinkingCapability(model, effortContext);
39
39
  if (!cap.supportsThinking) return;
40
- if (!getModelEffortOptions(model).includes(effort)) return;
40
+ if (!getModelEffortOptions(model, effortContext).includes(effort)) return;
41
41
 
42
42
  if (cap.thinkingProtocol === 'anthropic-adaptive') {
43
43
  body.thinking = { type: 'adaptive' };
@@ -207,10 +207,10 @@ export class AnthropicAdapter extends LLMAdapter {
207
207
  }
208
208
 
209
209
  /**
210
- * @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'low'|'medium'|'high'|'xhigh'|'max', effortSource?: 'user'|'auto', signal?: AbortSignal }} params
210
+ * @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'low'|'medium'|'high'|'xhigh'|'max', effortSource?: 'user'|'auto', effortContext?: object, signal?: AbortSignal, onRawExchange?: ({rawRequest, rawResponse}) => void }} params
211
211
  * @returns {AsyncGenerator<import('./adapter.js').StreamEvent>}
212
212
  */
213
- async *stream({ model, system, messages, tools, maxTokens = 16384, effort, effortSource, signal, onRawExchange }) {
213
+ async *stream({ model, system, messages, tools, maxTokens = 16384, effort, effortSource, effortContext, signal, onRawExchange }) {
214
214
  if (signal?.aborted) throw new LLMAbortError();
215
215
 
216
216
  const body = {
@@ -226,7 +226,7 @@ export class AnthropicAdapter extends LLMAdapter {
226
226
  // models use budget_tokens. Unsupported combinations silently drop effort.
227
227
  const normEffort = normalizeEffort(effort);
228
228
  if ((thinkingV1Enabled() || effortSource === 'user') && normEffort) {
229
- applyAnthropicThinking(body, model, normEffort);
229
+ applyAnthropicThinking(body, model, normEffort, effortContext);
230
230
  }
231
231
 
232
232
  const translatedTools = this.#translateTools(tools);
@@ -473,7 +473,7 @@ export class AnthropicAdapter extends LLMAdapter {
473
473
  * models silently drop the param. max_tokens auto-widens to budget+1024
474
474
  * when needed.
475
475
  */
476
- async call({ model, system, messages, maxTokens = 4096, effort, effortSource, signal }) {
476
+ async call({ model, system, messages, maxTokens = 4096, effort, effortSource, effortContext, signal }) {
477
477
  if (signal?.aborted) throw new LLMAbortError();
478
478
 
479
479
  const body = {
@@ -486,7 +486,7 @@ export class AnthropicAdapter extends LLMAdapter {
486
486
  // task-327c: mirror stream()'s thinking injection for side queries.
487
487
  const normEffort = normalizeEffort(effort);
488
488
  if ((thinkingV1Enabled() || effortSource === 'user') && normEffort) {
489
- applyAnthropicThinking(body, model, normEffort);
489
+ applyAnthropicThinking(body, model, normEffort, effortContext);
490
490
  }
491
491
 
492
492
  let response;
@@ -552,9 +552,16 @@ export class AdapterRouter extends LLMAdapter {
552
552
  */
553
553
  async *stream(params) {
554
554
  const resolved = await this.#resolveAdapter(params.model);
555
+ const effortContext = {
556
+ protocol: resolved.protocol,
557
+ supportsEffort: resolved.entry?.supportsEffort,
558
+ effortOptions: resolved.entry?.effortOptions,
559
+ thinkingProtocol: resolved.entry?.thinkingProtocol,
560
+ maxBudgetTokens: resolved.entry?.maxBudgetTokens,
561
+ };
555
562
  const filtered = filterEffortForModel({ ...params, model: resolved.modelId }, resolved);
556
563
  const sanitized = sanitizeMessagesForWire(filtered);
557
- yield* resolved.adapter.stream({ ...sanitized, model: resolved.modelId });
564
+ yield* resolved.adapter.stream({ ...sanitized, model: resolved.modelId, effortContext });
558
565
  }
559
566
 
560
567
  /**
@@ -565,9 +572,16 @@ export class AdapterRouter extends LLMAdapter {
565
572
  */
566
573
  async call(params) {
567
574
  const resolved = await this.#resolveAdapter(params.model);
575
+ const effortContext = {
576
+ protocol: resolved.protocol,
577
+ supportsEffort: resolved.entry?.supportsEffort,
578
+ effortOptions: resolved.entry?.effortOptions,
579
+ thinkingProtocol: resolved.entry?.thinkingProtocol,
580
+ maxBudgetTokens: resolved.entry?.maxBudgetTokens,
581
+ };
568
582
  const filtered = filterEffortForModel({ ...params, model: resolved.modelId }, resolved);
569
583
  const sanitized = sanitizeMessagesForWire(filtered);
570
- return resolved.adapter.call({ ...sanitized, model: resolved.modelId });
584
+ return resolved.adapter.call({ ...sanitized, model: resolved.modelId, effortContext });
571
585
  }
572
586
 
573
587
  /**
package/yeaft/models.js CHANGED
@@ -537,8 +537,13 @@ export function thinkingBudgetForEffort(model, effort) {
537
537
  */
538
538
  export function getThinkingCapability(model, context = {}) {
539
539
  const info = MODEL_REGISTRY.get(model);
540
+ const modelId = parseModelRef(model).modelId;
540
541
  const overrideOptions = normalizeEffortOptions(context.effortOptions);
541
- const overrideProtocol = context.thinkingProtocol || (context.protocol === 'anthropic' ? 'anthropic' : context.protocol === 'openai-responses' ? 'openai-reasoning' : null);
542
+ const overrideProtocol = context.thinkingProtocol || (
543
+ context.protocol === 'anthropic'
544
+ ? (/^deepseek/i.test(modelId) ? 'anthropic-adaptive' : 'anthropic')
545
+ : context.protocol === 'openai-responses' ? 'openai-reasoning' : null
546
+ );
542
547
  if (context.supportsEffort === true || overrideOptions) {
543
548
  return {
544
549
  supportsThinking: true,
@@ -560,7 +565,15 @@ export function getThinkingCapability(model, context = {}) {
560
565
  };
561
566
  }
562
567
  if (context.protocol === 'anthropic' && inferred?.thinkingProtocol === 'openai-reasoning') {
563
- inferred = null;
568
+ if (/^deepseek/i.test(modelId)) {
569
+ inferred = {
570
+ ...inferred,
571
+ thinkingProtocol: 'anthropic-adaptive',
572
+ effortOptions: DEEPSEEK_REASONING_EFFORT_OPTIONS,
573
+ };
574
+ } else {
575
+ inferred = null;
576
+ }
564
577
  }
565
578
  if ((!info || !info.supportsThinking) && !inferred) {
566
579
  return {
@@ -4366,12 +4366,14 @@ export async function handleYeaftFetchToolStats(_msg = {}) {
4366
4366
  * splices into place.
4367
4367
  *
4368
4368
  * Inputs (all optional):
4369
- * - `limit` max number of loops to return (1..500, default 100)
4370
- * - `sessionId` narrow by group
4371
- * - `threadId` narrow by thread
4369
+ * - `limit` legacy recent-detail loop cap; ignored by index-only
4370
+ * - `indexOnly` list request summaries without loop/detail payloads
4371
+ * - `detailTurnId` fetch full loops/tools for one request
4372
+ * - `sessionId` — narrow by Session
4373
+ * - `threadId` — narrow by thread
4372
4374
  *
4373
4375
  * Sends:
4374
- * { type: 'yeaft_debug_history', loops: [...], turns: [...] }
4376
+ * { type: 'yeaft_debug_history', loops: [...], turns: [...], indexOnly, detailTurnId }
4375
4377
  *
4376
4378
  * Best-effort: if the session / trace isn't ready, sends an empty
4377
4379
  * snapshot so the panel renders a placeholder instead of spinning.
@@ -4381,13 +4383,15 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
4381
4383
  const dreamLimit = Number.isFinite(msg?.dreamLimit) ? Number(msg.dreamLimit) : 5;
4382
4384
  const sessionId = typeof msg?.sessionId === 'string' && msg.sessionId ? msg.sessionId : null;
4383
4385
  const threadId = typeof msg?.threadId === 'string' && msg.threadId ? msg.threadId : null;
4386
+ const indexOnly = !!msg?.indexOnly;
4387
+ const detailTurnId = typeof msg?.detailTurnId === 'string' && msg.detailTurnId ? msg.detailTurnId : null;
4384
4388
  let loops = [];
4385
4389
  let turns = [];
4386
4390
  let dreamEvents = [];
4387
4391
  let hasMore = false;
4388
4392
  try {
4389
4393
  if (session?.trace && typeof session.trace.fetchRecentDebugHistory === 'function') {
4390
- const out = session.trace.fetchRecentDebugHistory({ limit, dreamLimit, sessionId, threadId });
4394
+ const out = session.trace.fetchRecentDebugHistory({ limit, dreamLimit, sessionId, threadId, indexOnly, detailTurnId });
4391
4395
  loops = Array.isArray(out?.loops) ? out.loops : [];
4392
4396
  turns = Array.isArray(out?.turns) ? out.turns : [];
4393
4397
  dreamEvents = Array.isArray(out?.dreamEvents) ? out.dreamEvents : [];
@@ -4412,6 +4416,8 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
4412
4416
  threadId,
4413
4417
  hasMore,
4414
4418
  limit,
4419
+ indexOnly,
4420
+ detailTurnId,
4415
4421
  });
4416
4422
  }
4417
4423