authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-10 12:25:03-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-10 12:25:03-07:00
log343061a0538bde5aa79e6d2c4aca0d42aa7992cb
tree13b303e193457b27bed0c05fe1e978997ffa0349
parent0b40ea62e12ea061bbc8e954cf8dee288e9faf56
signature Signed by PGP key B5690EEEBB952194

Misc chrome polish: header badges, plan sidebar, diff panel, empty state (#3027)

Co-authored-by: codex <codex@users.noreply.github.com>

13 files changed, 746 insertions(+), 232 deletions(-)

apps/server/package.json+1-1
...@@ -22,7 +22,7 @@...@@ -22,7 +22,7 @@
22 "test": "vp test run"22 "test": "vp test run"
23 },23 },
24 "dependencies": {24 "dependencies": {
25 "@anthropic-ai/claude-agent-sdk": "^0.3.154",25 "@anthropic-ai/claude-agent-sdk": "^0.3.170",
26 "@effect/platform-bun": "catalog:",26 "@effect/platform-bun": "catalog:",
27 "@effect/platform-node": "catalog:",27 "@effect/platform-node": "catalog:",
28 "@effect/platform-node-shared": "catalog:",28 "@effect/platform-node-shared": "catalog:",
apps/server/src/provider/Layers/ClaudeAdapter.ts+579-135
...@@ -14,6 +14,7 @@ import {...@@ -14,6 +14,7 @@ import {
14 type PermissionResult,14 type PermissionResult,
15 type PermissionUpdate,15 type PermissionUpdate,
16 type SDKMessage,16 type SDKMessage,
17 type SDKControlGetContextUsageResponse,
17 type SDKResultMessage,18 type SDKResultMessage,
18 type SettingSource,19 type SettingSource,
19 type SDKUserMessage,20 type SDKUserMessage,
...@@ -29,6 +30,7 @@ import {...@@ -29,6 +30,7 @@ import {
29 type ProviderApprovalDecision,30 type ProviderApprovalDecision,
30 ProviderDriverKind,31 ProviderDriverKind,
31 ProviderInstanceId,32 ProviderInstanceId,
33 type ModelSelection,
32 ProviderItemId,34 ProviderItemId,
33 type ProviderRuntimeEvent,35 type ProviderRuntimeEvent,
34 type ProviderRuntimeTurnStatus,36 type ProviderRuntimeTurnStatus,
...@@ -159,6 +161,13 @@ interface ToolInFlight {...@@ -159,6 +161,13 @@ interface ToolInFlight {
159 readonly lastEmittedInputFingerprint?: string;161 readonly lastEmittedInputFingerprint?: string;
160}162}
161163
164interface ClaudeTaskState {
165 readonly id: string;
166 subject: string;
167 status: PlanStep["status"];
168 readonly blockedBy: Set<string>;
169}
170
162interface ClaudeSessionContext {171interface ClaudeSessionContext {
163 session: ProviderSession;172 session: ProviderSession;
164 readonly promptQueue: Queue.Queue<PromptQueueItem>;173 readonly promptQueue: Queue.Queue<PromptQueueItem>;
...@@ -175,9 +184,11 @@ interface ClaudeSessionContext {...@@ -175,9 +184,11 @@ interface ClaudeSessionContext {
175 items: Array<unknown>;184 items: Array<unknown>;
176 }>;185 }>;
177 readonly inFlightTools: Map<number, ToolInFlight>;186 readonly inFlightTools: Map<number, ToolInFlight>;
187 readonly claudeTasks: Map<string, ClaudeTaskState>;
178 turnState: ClaudeTurnState | undefined;188 turnState: ClaudeTurnState | undefined;
179 lastKnownContextWindow: number | undefined;189 lastKnownContextWindow: number | undefined;
180 lastKnownTokenUsage: ThreadTokenUsageSnapshot | undefined;190 lastKnownTokenUsage: ThreadTokenUsageSnapshot | undefined;
191 lastKnownTotalProcessedTokens: number | undefined;
181 lastAssistantUuid: string | undefined;192 lastAssistantUuid: string | undefined;
182 lastThreadStartedId: string | undefined;193 lastThreadStartedId: string | undefined;
183 stopped: boolean;194 stopped: boolean;
...@@ -188,6 +199,7 @@ interface ClaudeQueryRuntime extends AsyncIterable<SDKMessage> {...@@ -188,6 +199,7 @@ interface ClaudeQueryRuntime extends AsyncIterable<SDKMessage> {
188 readonly setModel: (model?: string) => Promise<void>;199 readonly setModel: (model?: string) => Promise<void>;
189 readonly setPermissionMode: (mode: PermissionMode) => Promise<void>;200 readonly setPermissionMode: (mode: PermissionMode) => Promise<void>;
190 readonly setMaxThinkingTokens: (maxThinkingTokens: number | null) => Promise<void>;201 readonly setMaxThinkingTokens: (maxThinkingTokens: number | null) => Promise<void>;
202 readonly getContextUsage?: () => Promise<SDKControlGetContextUsageResponse>;
191 readonly close: () => void;203 readonly close: () => void;
192}204}
193205
...@@ -337,60 +349,220 @@ function maxClaudeContextWindowFromModelUsage(...@@ -337,60 +349,220 @@ function maxClaudeContextWindowFromModelUsage(
337 return maxContextWindow;349 return maxContextWindow;
338}350}
339351
340function normalizeClaudeTokenUsage(352function selectedClaudeContextWindow(
341 value: unknown,353 modelSelection: ModelSelection | undefined,
342 contextWindow?: number,354): number | undefined {
343): ThreadTokenUsageSnapshot | undefined {355 switch (modelSelection?.model) {
344 if (!value || typeof value !== "object") {356 case "claude-opus-4-8":
357 case "claude-opus-4-7":
358 return 1_000_000;
359 }
360
361 const optionValue = getModelSelectionStringOptionValue(modelSelection, "contextWindow");
362 if (optionValue === "1m") {
363 return 1_000_000;
364 }
365 if (optionValue === "200k") {
366 return 200_000;
367 }
368 const caps = getClaudeModelCapabilities(modelSelection?.model);
369 const hasContextWindowOption = getProviderOptionDescriptors({ caps }).some(
370 (descriptor) => descriptor.type === "select" && descriptor.id === "contextWindow",
371 );
372 if (hasContextWindowOption) {
373 return 200_000;
374 }
375 return undefined;
376}
377
378function finiteNonNegativeInteger(value: unknown): number | undefined {
379 return typeof value === "number" && Number.isFinite(value) && value >= 0
380 ? Math.round(value)
381 : undefined;
382}
383
384function finitePositiveInteger(value: unknown): number | undefined {
385 return typeof value === "number" && Number.isFinite(value) && value > 0
386 ? Math.round(value)
387 : undefined;
388}
389
390function claudeUsageInputTokens(usage: Record<string, unknown>): number {
391 return (
392 (finiteNonNegativeInteger(usage.input_tokens) ?? 0) +
393 (finiteNonNegativeInteger(usage.cache_creation_input_tokens) ?? 0) +
394 (finiteNonNegativeInteger(usage.cache_read_input_tokens) ?? 0)
395 );
396}
397
398function claudeUsageOutputTokens(usage: Record<string, unknown>): number {
399 return finiteNonNegativeInteger(usage.output_tokens) ?? 0;
400}
401
402function lastClaudeUsageIteration(
403 value: Record<string, unknown>,
404): Record<string, unknown> | undefined {
405 const iterations = Array.isArray(value.iterations) ? value.iterations : [];
406 return iterations.findLast(
407 (iteration): iteration is Record<string, unknown> =>
408 iteration !== null && typeof iteration === "object" && !Array.isArray(iteration),
409 );
410}
411
412function claudeTotalProcessedTokens(value: unknown): number | undefined {
413 if (!value || typeof value !== "object" || Array.isArray(value)) {
345 return undefined;414 return undefined;
346 }415 }
347416
348 const usage = value as Record<string, unknown>;417 const usage = value as Record<string, unknown>;
349 const inputTokens =418 const explicitTotal = finiteNonNegativeInteger(usage.total_tokens);
350 (typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens)419 if (explicitTotal !== undefined && explicitTotal > 0) {
351 ? usage.input_tokens420 return explicitTotal;
352 : 0) +421 }
353 (typeof usage.cache_creation_input_tokens === "number" &&422
354 Number.isFinite(usage.cache_creation_input_tokens)423 const total = claudeUsageInputTokens(usage) + claudeUsageOutputTokens(usage);
355 ? usage.cache_creation_input_tokens424 return total > 0 ? total : undefined;
356 : 0) +425}
357 (typeof usage.cache_read_input_tokens === "number" &&426
358 Number.isFinite(usage.cache_read_input_tokens)427function makeClaudeTokenUsageSnapshot(input: {
359 ? usage.cache_read_input_tokens428 readonly activeTokens: number;
360 : 0);429 readonly inputTokens?: number;
361 const outputTokens =430 readonly outputTokens?: number;
362 typeof usage.output_tokens === "number" && Number.isFinite(usage.output_tokens)431 readonly contextWindow?: number;
363 ? usage.output_tokens432 readonly totalProcessedTokens?: number;
364 : 0;433 readonly lastUsedTokens?: number;
365 const derivedTotalProcessedTokens = inputTokens + outputTokens;434 readonly compactsAutomatically?: boolean;
366 const totalProcessedTokens =435}): ThreadTokenUsageSnapshot | undefined {
367 (typeof usage.total_tokens === "number" && Number.isFinite(usage.total_tokens)436 const activeTokens = finiteNonNegativeInteger(input.activeTokens);
368 ? usage.total_tokens437 if (activeTokens === undefined || activeTokens <= 0) {
369 : undefined) ?? (derivedTotalProcessedTokens > 0 ? derivedTotalProcessedTokens : undefined);
370 if (totalProcessedTokens === undefined || totalProcessedTokens <= 0) {
371 return undefined;438 return undefined;
372 }439 }
373440
374 const maxTokens =441 const maxTokens = finitePositiveInteger(input.contextWindow);
375 typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0442 const usedTokens = maxTokens !== undefined ? Math.min(activeTokens, maxTokens) : activeTokens;
376 ? contextWindow443 const lastUsedTokens =
377 : undefined;444 finiteNonNegativeInteger(input.lastUsedTokens) ??
378 const usedTokens =445 (maxTokens !== undefined ? Math.min(activeTokens, maxTokens) : activeTokens);
379 maxTokens !== undefined ? Math.min(totalProcessedTokens, maxTokens) : totalProcessedTokens;446 const totalProcessedTokens = finiteNonNegativeInteger(input.totalProcessedTokens);
447 const inputTokens = finiteNonNegativeInteger(input.inputTokens);
448 const outputTokens = finiteNonNegativeInteger(input.outputTokens);
380449
381 return {450 return {
382 usedTokens,451 usedTokens,
383 lastUsedTokens: usedTokens,452 lastUsedTokens,
384 ...(totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {}),453 ...(totalProcessedTokens !== undefined && totalProcessedTokens > usedTokens
385 ...(inputTokens > 0 ? { inputTokens } : {}),454 ? { totalProcessedTokens }
386 ...(outputTokens > 0 ? { outputTokens } : {}),455 : {}),
456 ...(inputTokens !== undefined && inputTokens > 0 ? { inputTokens } : {}),
457 ...(outputTokens !== undefined && outputTokens > 0 ? { outputTokens } : {}),
387 ...(maxTokens !== undefined ? { maxTokens } : {}),458 ...(maxTokens !== undefined ? { maxTokens } : {}),
388 ...(typeof usage.tool_uses === "number" && Number.isFinite(usage.tool_uses)459 ...(input.compactsAutomatically !== undefined
389 ? { toolUses: usage.tool_uses }460 ? { compactsAutomatically: input.compactsAutomatically }
390 : {}),461 : {}),
391 ...(typeof usage.duration_ms === "number" && Number.isFinite(usage.duration_ms)462 };
392 ? { durationMs: usage.duration_ms }463}
464
465function normalizeClaudeActiveTokenUsage(
466 value: unknown,
467 contextWindow?: number,
468 totalProcessedTokens?: number,
469): ThreadTokenUsageSnapshot | undefined {
470 if (!value || typeof value !== "object") {
471 return undefined;
472 }
473
474 const usage = value as Record<string, unknown>;
475 const activeUsage = lastClaudeUsageIteration(usage) ?? usage;
476 const inputTokens = claudeUsageInputTokens(activeUsage);
477 const outputTokens = claudeUsageOutputTokens(activeUsage);
478 const activeTokens = claudeTotalProcessedTokens(activeUsage) ?? inputTokens + outputTokens;
479 if (activeTokens <= 0) {
480 return undefined;
481 }
482
483 return makeClaudeTokenUsageSnapshot({
484 activeTokens,
485 inputTokens,
486 outputTokens,
487 ...(contextWindow !== undefined ? { contextWindow } : {}),
488 ...(totalProcessedTokens !== undefined ? { totalProcessedTokens } : {}),
489 });
490}
491
492function normalizeClaudeContextUsageApiSnapshot(
493 value: SDKControlGetContextUsageResponse,
494 totalProcessedTokens?: number,
495): ThreadTokenUsageSnapshot | undefined {
496 return makeClaudeTokenUsageSnapshot({
497 activeTokens: value.totalTokens,
498 contextWindow: value.maxTokens,
499 ...(totalProcessedTokens !== undefined ? { totalProcessedTokens } : {}),
500 compactsAutomatically: value.isAutoCompactEnabled,
501 });
502}
503
504function compactBoundaryTokenUsageSnapshot(
505 message: Record<string, unknown>,
506 contextWindow?: number,
507 totalProcessedTokens?: number,
508): ThreadTokenUsageSnapshot | undefined {
509 const metadata = message.compact_metadata;
510 if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
511 return undefined;
512 }
513
514 const compactMetadata = metadata as Record<string, unknown>;
515 const postTokens = finiteNonNegativeInteger(compactMetadata.post_tokens);
516 if (postTokens === undefined || postTokens <= 0) {
517 return undefined;
518 }
519
520 const preTokens = finiteNonNegativeInteger(compactMetadata.pre_tokens);
521 return makeClaudeTokenUsageSnapshot({
522 activeTokens: postTokens,
523 ...(preTokens !== undefined ? { lastUsedTokens: preTokens } : {}),
524 ...(contextWindow !== undefined ? { contextWindow } : {}),
525 ...(totalProcessedTokens !== undefined ? { totalProcessedTokens } : {}),
526 });
527}
528
529function normalizeClaudeTaskProgressTokenUsage(
530 value: unknown,
531 context: ClaudeSessionContext,
532): ThreadTokenUsageSnapshot | undefined {
533 const totalTokens = claudeTotalProcessedTokens(value);
534 if (totalTokens === undefined || totalTokens <= 0) {
535 return undefined;
536 }
537
538 const lastUsedTokens = context.lastKnownTokenUsage?.usedTokens;
539 const activeTokens =
540 lastUsedTokens !== undefined ? Math.max(totalTokens, lastUsedTokens) : totalTokens;
541 if (lastUsedTokens !== undefined && activeTokens === lastUsedTokens) {
542 return undefined;
543 }
544
545 const usage = value as Record<string, unknown>;
546 const snapshot = makeClaudeTokenUsageSnapshot({
547 activeTokens,
548 ...(context.lastKnownContextWindow !== undefined
549 ? { contextWindow: context.lastKnownContextWindow }
393 : {}),550 : {}),
551 totalProcessedTokens: Math.max(
552 totalTokens,
553 context.lastKnownTotalProcessedTokens ?? totalTokens,
554 ),
555 });
556 if (!snapshot) {
557 return undefined;
558 }
559
560 const toolUses = finiteNonNegativeInteger(usage.tool_uses);
561 const durationMs = finiteNonNegativeInteger(usage.duration_ms);
562 return {
563 ...snapshot,
564 ...(toolUses !== undefined ? { toolUses } : {}),
565 ...(durationMs !== undefined ? { durationMs } : {}),
394 };566 };
395}567}
396568
...@@ -539,6 +711,140 @@ function extractPlanStepsFromTodoInput(input: Record<string, unknown>): PlanStep...@@ -539,6 +711,140 @@ function extractPlanStepsFromTodoInput(input: Record<string, unknown>): PlanStep
539 }));711 }));
540}712}
541713
714function isClaudeTaskTool(toolName: string): boolean {
715 return toolName === "TaskCreate" || toolName === "TaskUpdate" || toolName === "TaskList";
716}
717
718function normalizeClaudeTaskStatus(value: unknown): PlanStep["status"] {
719 return value === "completed" ? "completed" : value === "in_progress" ? "inProgress" : "pending";
720}
721
722function readString(value: unknown): string | undefined {
723 return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
724}
725
726function readStringArray(value: unknown): Array<string> {
727 return Array.isArray(value)
728 ? value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0)
729 : [];
730}
731
732function readClaudeToolUseResult(message: SDKMessage): Record<string, unknown> | undefined {
733 if (message.type !== "user") {
734 return undefined;
735 }
736 const result = (message as { readonly tool_use_result?: unknown }).tool_use_result;
737 return result !== null && typeof result === "object" && !Array.isArray(result)
738 ? (result as Record<string, unknown>)
739 : undefined;
740}
741
742function readClaudeTaskFromResult(
743 result: Record<string, unknown> | undefined,
744): Record<string, unknown> | undefined {
745 const task = result?.task;
746 return task !== null && typeof task === "object" && !Array.isArray(task)
747 ? (task as Record<string, unknown>)
748 : undefined;
749}
750
751function applyClaudeTaskToolResult(
752 tasks: Map<string, ClaudeTaskState>,
753 tool: ToolInFlight,
754 result: Record<string, unknown> | undefined,
755): boolean {
756 if (!isClaudeTaskTool(tool.toolName)) {
757 return false;
758 }
759
760 let changed = false;
761 if (tool.toolName === "TaskList") {
762 const resultTasks = result?.tasks;
763 if (!Array.isArray(resultTasks)) {
764 return false;
765 }
766 tasks.clear();
767 for (const entry of resultTasks) {
768 if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
769 continue;
770 }
771 const task = entry as Record<string, unknown>;
772 const id = readString(task.id);
773 const subject = readString(task.subject);
774 if (!id || !subject) {
775 continue;
776 }
777 tasks.set(id, {
778 id,
779 subject,
780 status: normalizeClaudeTaskStatus(task.status),
781 blockedBy: new Set(readStringArray(task.blockedBy)),
782 });
783 }
784 return tasks.size > 0;
785 }
786
787 if (tool.toolName === "TaskCreate") {
788 const resultTask = readClaudeTaskFromResult(result);
789 const id = readString(resultTask?.id);
790 const subject = readString(resultTask?.subject) ?? readString(tool.input.subject);
791 if (!id || !subject) {
792 return false;
793 }
794 tasks.set(id, {
795 id,
796 subject,
797 status: normalizeClaudeTaskStatus(tool.input.status),
798 blockedBy: new Set(readStringArray(tool.input.blockedBy)),
799 });
800 return true;
801 }
802
803 const taskId = readString(tool.input.taskId) ?? readString(result?.taskId);
804 if (!taskId) {
805 return false;
806 }
807 const task = tasks.get(taskId);
808 if (!task) {
809 return false;
810 }
811 const subject = readString(tool.input.subject);
812 if (subject && task.subject !== subject) {
813 task.subject = subject;
814 changed = true;
815 }
816 if (typeof tool.input.status === "string") {
817 const status = normalizeClaudeTaskStatus(tool.input.status);
818 if (task.status !== status) {
819 task.status = status;
820 changed = true;
821 }
822 }
823 for (const dependency of readStringArray(tool.input.addBlockedBy)) {
824 if (!task.blockedBy.has(dependency)) {
825 task.blockedBy.add(dependency);
826 changed = true;
827 }
828 }
829 for (const dependency of readStringArray(tool.input.removeBlockedBy)) {
830 if (task.blockedBy.delete(dependency)) {
831 changed = true;
832 }
833 }
834 return changed;
835}
836
837function planStepsFromClaudeTasks(tasks: Map<string, ClaudeTaskState>): PlanStep[] {
838 return Array.from(tasks.values()).map((task) => {
839 const blockedBy = Array.from(task.blockedBy);
840 const blockedSuffix = blockedBy.length > 0 ? ` (blocked by #${blockedBy.join(", #")})` : "";
841 return {
842 step: `${task.subject}${blockedSuffix}`,
843 status: task.status,
844 };
845 });
846}
847
542function summarizeToolRequest(toolName: string, input: Record<string, unknown>): string {848function summarizeToolRequest(toolName: string, input: Record<string, unknown>): string {
543 const commandValue = input.command ?? input.cmd;849 const commandValue = input.command ?? input.cmd;
544 const command = typeof commandValue === "string" ? commandValue : undefined;850 const command = typeof commandValue === "string" ? commandValue : undefined;
...@@ -1438,6 +1744,70 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -1438,6 +1744,70 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
1438 });1744 });
1439 });1745 });
14401746
1747 const emitThreadTokenUsage = Effect.fn("emitThreadTokenUsage")(function* (
1748 context: ClaudeSessionContext,
1749 usage: ThreadTokenUsageSnapshot | undefined,
1750 options?: {
1751 readonly rawMethod?: string;
1752 readonly rawPayload?: unknown;
1753 },
1754 ) {
1755 if (!usage) {
1756 return;
1757 }
1758
1759 context.lastKnownTokenUsage = usage;
1760 context.lastKnownTotalProcessedTokens =
1761 usage.totalProcessedTokens ?? context.lastKnownTotalProcessedTokens;
1762
1763 const turnState = context.turnState;
1764 const stamp = yield* makeEventStamp();
1765 yield* offerRuntimeEvent({
1766 type: "thread.token-usage.updated",
1767 eventId: stamp.eventId,
1768 provider: PROVIDER,
1769 createdAt: stamp.createdAt,
1770 threadId: context.session.threadId,
1771 ...(turnState ? { turnId: turnState.turnId } : {}),
1772 payload: {
1773 usage,
1774 },
1775 providerRefs: nativeProviderRefs(context),
1776 ...(options?.rawMethod || options?.rawPayload
1777 ? {
1778 raw: {
1779 source: "claude.sdk.message" as const,
1780 ...(options.rawMethod ? { method: options.rawMethod } : {}),
1781 payload: options.rawPayload,
1782 },
1783 }
1784 : {}),
1785 });
1786 });
1787
1788 const queryCurrentContextUsage = Effect.fn("queryCurrentContextUsage")(function* (
1789 context: ClaudeSessionContext,
1790 totalProcessedTokens?: number,
1791 ) {
1792 if (!context.query.getContextUsage) {
1793 return undefined;
1794 }
1795
1796 const usage = yield* Effect.promise(async () => {
1797 try {
1798 return await context.query.getContextUsage?.();
1799 } catch {
1800 return undefined;
1801 }
1802 });
1803 if (!usage) {
1804 return undefined;
1805 }
1806
1807 context.lastKnownContextWindow = usage.maxTokens;
1808 return normalizeClaudeContextUsageApiSnapshot(usage, totalProcessedTokens);
1809 });
1810
1441 const emitProposedPlanCompleted = Effect.fn("emitProposedPlanCompleted")(function* (1811 const emitProposedPlanCompleted = Effect.fn("emitProposedPlanCompleted")(function* (
1442 context: ClaudeSessionContext,1812 context: ClaudeSessionContext,
1443 input: {1813 input: {
...@@ -1485,6 +1855,42 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -1485,6 +1855,42 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
1485 });1855 });
1486 });1856 });
14871857
1858 const emitClaudeTaskPlanUpdated = Effect.fn("emitClaudeTaskPlanUpdated")(function* (
1859 context: ClaudeSessionContext,
1860 input: {
1861 readonly toolUseId: string;
1862 readonly rawMethod: string;
1863 readonly rawPayload: unknown;
1864 },
1865 ) {
1866 const plan = planStepsFromClaudeTasks(context.claudeTasks);
1867 if (plan.length === 0) {
1868 return;
1869 }
1870
1871 const stamp = yield* makeEventStamp();
1872 yield* offerRuntimeEvent({
1873 type: "turn.plan.updated",
1874 eventId: stamp.eventId,
1875 provider: PROVIDER,
1876 createdAt: stamp.createdAt,
1877 threadId: context.session.threadId,
1878 ...(context.turnState ? { turnId: asCanonicalTurnId(context.turnState.turnId) } : {}),
1879 payload: {
1880 explanation: "Claude Tasks",
1881 plan,
1882 },
1883 providerRefs: nativeProviderRefs(context, {
1884 providerItemId: input.toolUseId,
1885 }),
1886 raw: {
1887 source: "claude.sdk.message",
1888 method: input.rawMethod,
1889 payload: input.rawPayload,
1890 },
1891 });
1892 });
1893
1488 const completeTurn = Effect.fn("completeTurn")(function* (1894 const completeTurn = Effect.fn("completeTurn")(function* (
1489 context: ClaudeSessionContext,1895 context: ClaudeSessionContext,
1490 status: ProviderRuntimeTurnStatus,1896 status: ProviderRuntimeTurnStatus,
...@@ -1496,51 +1902,77 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -1496,51 +1902,77 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
1496 context.lastKnownContextWindow = resultContextWindow;1902 context.lastKnownContextWindow = resultContextWindow;
1497 }1903 }
14981904
1499 // The SDK result.usage contains *accumulated* totals across all API calls1905 const maxTokens = resultContextWindow ?? context.lastKnownContextWindow;
1500 // (input_tokens, cache_read_input_tokens, etc. summed over every request).1906 const accumulatedTotalProcessedTokens = claudeTotalProcessedTokens(result?.usage);
1501 // This does NOT represent the current context window size.1907 if (accumulatedTotalProcessedTokens !== undefined) {
1502 // Instead, use the last known context-window-accurate usage from task_progress1908 context.lastKnownTotalProcessedTokens = accumulatedTotalProcessedTokens;
1503 // events and treat the accumulated total as totalProcessedTokens.1909 }
1504 const accumulatedSnapshot = normalizeClaudeTokenUsage(1910
1505 result?.usage,1911 const contextUsageSnapshot = yield* queryCurrentContextUsage(
1506 resultContextWindow ?? context.lastKnownContextWindow,1912 context,
1913 accumulatedTotalProcessedTokens ?? context.lastKnownTotalProcessedTokens,
1507 );1914 );
1508 const accumulatedTotalProcessedTokens =1915 const resultUsageRecord =
1509 accumulatedSnapshot?.totalProcessedTokens ?? accumulatedSnapshot?.usedTokens;1916 result?.usage && typeof result.usage === "object" && !Array.isArray(result.usage)
1917 ? (result.usage as Record<string, unknown>)
1918 : undefined;
1919 const hasResultUsageIteration =
1920 resultUsageRecord !== undefined && lastClaudeUsageIteration(resultUsageRecord) !== undefined;
1921 const resultHasActiveUsage =
1922 resultUsageRecord !== undefined &&
1923 (hasResultUsageIteration ||
1924 claudeUsageInputTokens(resultUsageRecord) + claudeUsageOutputTokens(resultUsageRecord) > 0);
1925 const resultTotalOnly =
1926 resultUsageRecord !== undefined &&
1927 !resultHasActiveUsage &&
1928 claudeTotalProcessedTokens(resultUsageRecord) !== undefined;
1929 const resultIterationSnapshot = resultUsageRecord
1930 ? normalizeClaudeActiveTokenUsage(
1931 resultUsageRecord,
1932 maxTokens,
1933 accumulatedTotalProcessedTokens ?? context.lastKnownTotalProcessedTokens,
1934 )
1935 : undefined;
1510 const lastGoodUsage = context.lastKnownTokenUsage;1936 const lastGoodUsage = context.lastKnownTokenUsage;
1511 const maxTokens = resultContextWindow ?? context.lastKnownContextWindow;1937 const usageSnapshot: ThreadTokenUsageSnapshot | undefined =
1512 const usageSnapshot: ThreadTokenUsageSnapshot | undefined = lastGoodUsage1938 contextUsageSnapshot ??
1513 ? {1939 (resultTotalOnly && lastGoodUsage
1514 ...lastGoodUsage,1940 ? {
1515 ...(typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 01941 ...lastGoodUsage,
1516 ? { maxTokens }1942 ...(typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0
1517 : {}),1943 ? { maxTokens }
1518 ...(typeof accumulatedTotalProcessedTokens === "number" &&1944 : {}),
1519 Number.isFinite(accumulatedTotalProcessedTokens) &&1945 ...(typeof accumulatedTotalProcessedTokens === "number" &&
1520 accumulatedTotalProcessedTokens > lastGoodUsage.usedTokens1946 Number.isFinite(accumulatedTotalProcessedTokens) &&
1521 ? {1947 accumulatedTotalProcessedTokens > lastGoodUsage.usedTokens
1522 totalProcessedTokens: accumulatedTotalProcessedTokens,1948 ? {
1523 }1949 totalProcessedTokens: accumulatedTotalProcessedTokens,
1524 : {}),1950 }
1525 }1951 : {}),
1526 : accumulatedSnapshot;1952 }
1953 : resultIterationSnapshot) ??
1954 (lastGoodUsage
1955 ? {
1956 ...lastGoodUsage,
1957 ...(typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0
1958 ? { maxTokens }
1959 : {}),
1960 ...(typeof accumulatedTotalProcessedTokens === "number" &&
1961 Number.isFinite(accumulatedTotalProcessedTokens) &&
1962 accumulatedTotalProcessedTokens > lastGoodUsage.usedTokens
1963 ? {
1964 totalProcessedTokens: accumulatedTotalProcessedTokens,
1965 }
1966 : {}),
1967 }
1968 : undefined);
15271969
1528 const turnState = context.turnState;1970 const turnState = context.turnState;
1529 if (!turnState) {1971 if (!turnState) {
1530 if (usageSnapshot) {1972 yield* emitThreadTokenUsage(context, usageSnapshot, {
1531 const usageStamp = yield* makeEventStamp();1973 rawMethod: "claude/result",
1532 yield* offerRuntimeEvent({1974 rawPayload: result ?? { status },
1533 type: "thread.token-usage.updated",1975 });
1534 eventId: usageStamp.eventId,
1535 provider: PROVIDER,
1536 createdAt: usageStamp.createdAt,
1537 threadId: context.session.threadId,
1538 payload: {
1539 usage: usageSnapshot,
1540 },
1541 providerRefs: {},
1542 });
1543 }
15441976
1545 const stamp = yield* makeEventStamp();1977 const stamp = yield* makeEventStamp();
1546 yield* offerRuntimeEvent({1978 yield* offerRuntimeEvent({
...@@ -1611,21 +2043,10 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -1611,21 +2043,10 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
1611 items: [...turnState.items],2043 items: [...turnState.items],
1612 });2044 });
16132045
1614 if (usageSnapshot) {2046 yield* emitThreadTokenUsage(context, usageSnapshot, {
1615 const usageStamp = yield* makeEventStamp();2047 rawMethod: "claude/result",
1616 yield* offerRuntimeEvent({2048 rawPayload: result ?? { status },
1617 type: "thread.token-usage.updated",2049 });
1618 eventId: usageStamp.eventId,
1619 provider: PROVIDER,
1620 createdAt: usageStamp.createdAt,
1621 threadId: context.session.threadId,
1622 turnId: turnState.turnId,
1623 payload: {
1624 usage: usageSnapshot,
1625 },
1626 providerRefs: nativeProviderRefs(context),
1627 });
1628 }
16292050
1630 const stamp = yield* makeEventStamp();2051 const stamp = yield* makeEventStamp();
1631 yield* offerRuntimeEvent({2052 yield* offerRuntimeEvent({
...@@ -1670,6 +2091,23 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -1670,6 +2091,23 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
16702091
1671 const { event } = message;2092 const { event } = message;
16722093
2094 if (event.type === "message_delta") {
2095 if (message.parent_tool_use_id !== null && message.parent_tool_use_id !== undefined) {
2096 return;
2097 }
2098
2099 const snapshot = normalizeClaudeActiveTokenUsage(
2100 event.usage,
2101 context.lastKnownContextWindow,
2102 context.lastKnownTotalProcessedTokens,
2103 );
2104 yield* emitThreadTokenUsage(context, snapshot, {
2105 rawMethod: "claude/stream_event/message_delta",
2106 rawPayload: message,
2107 });
2108 return;
2109 }
2110
1673 if (event.type === "content_block_delta") {2111 if (event.type === "content_block_delta") {
1674 if (2112 if (
1675 (event.delta.type === "text_delta" || event.delta.type === "thinking_delta") &&2113 (event.delta.type === "text_delta" || event.delta.type === "thinking_delta") &&
...@@ -1932,6 +2370,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -1932,6 +2370,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
19322370
1933 const [index, tool] = toolEntry;2371 const [index, tool] = toolEntry;
1934 const itemStatus = toolResult.isError ? "failed" : "completed";2372 const itemStatus = toolResult.isError ? "failed" : "completed";
2373 const toolUseResult = readClaudeToolUseResult(message);
1935 const toolData = {2374 const toolData = {
1936 toolName: tool.toolName,2375 toolName: tool.toolName,
1937 input: tool.input,2376 input: tool.input,
...@@ -2016,6 +2455,17 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -2016,6 +2455,17 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
2016 },2455 },
2017 });2456 });
20182457
2458 if (
2459 !toolResult.isError &&
2460 applyClaudeTaskToolResult(context.claudeTasks, tool, toolUseResult)
2461 ) {
2462 yield* emitClaudeTaskPlanUpdated(context, {
2463 toolUseId: tool.itemId,
2464 rawMethod: "claude/user",
2465 rawPayload: message,
2466 });
2467 }
2468
2019 context.inFlightTools.delete(index);2469 context.inFlightTools.delete(index);
2020 }2470 }
2021 });2471 });
...@@ -2171,6 +2621,18 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -2171,6 +2621,18 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
2171 });2621 });
2172 return;2622 return;
2173 case "compact_boundary":2623 case "compact_boundary":
2624 yield* emitThreadTokenUsage(
2625 context,
2626 compactBoundaryTokenUsageSnapshot(
2627 message as unknown as Record<string, unknown>,
2628 context.lastKnownContextWindow,
2629 context.lastKnownTotalProcessedTokens,
2630 ),
2631 {
2632 rawMethod: "claude/system/compact_boundary",
2633 rawPayload: message,
2634 },
2635 );
2174 yield* offerRuntimeEvent({2636 yield* offerRuntimeEvent({
2175 ...base,2637 ...base,
2176 type: "thread.state.changed",2638 type: "thread.state.changed",
...@@ -2229,25 +2691,14 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -2229,25 +2691,14 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
2229 });2691 });
2230 return;2692 return;
2231 case "task_progress":2693 case "task_progress":
2232 if (message.usage) {2694 yield* emitThreadTokenUsage(
2233 const normalizedUsage = normalizeClaudeTokenUsage(2695 context,
2234 message.usage,2696 normalizeClaudeTaskProgressTokenUsage(message.usage, context),
2235 context.lastKnownContextWindow,2697 {
2236 );2698 rawMethod: "claude/system/task_progress",
2237 if (normalizedUsage) {2699 rawPayload: message,
2238 context.lastKnownTokenUsage = normalizedUsage;2700 },
2239 const usageStamp = yield* makeEventStamp();2701 );
2240 yield* offerRuntimeEvent({
2241 ...base,
2242 eventId: usageStamp.eventId,
2243 createdAt: usageStamp.createdAt,
2244 type: "thread.token-usage.updated",
2245 payload: {
2246 usage: normalizedUsage,
2247 },
2248 });
2249 }
2250 }
2251 yield* offerRuntimeEvent({2702 yield* offerRuntimeEvent({
2252 ...base,2703 ...base,
2253 type: "task.progress",2704 type: "task.progress",
...@@ -2261,25 +2712,14 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -2261,25 +2712,14 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
2261 });2712 });
2262 return;2713 return;
2263 case "task_notification":2714 case "task_notification":
2264 if (message.usage) {2715 yield* emitThreadTokenUsage(
2265 const normalizedUsage = normalizeClaudeTokenUsage(2716 context,
2266 message.usage,2717 normalizeClaudeTaskProgressTokenUsage(message.usage, context),
2267 context.lastKnownContextWindow,2718 {
2268 );2719 rawMethod: "claude/system/task_notification",
2269 if (normalizedUsage) {2720 rawPayload: message,
2270 context.lastKnownTokenUsage = normalizedUsage;2721 },
2271 const usageStamp = yield* makeEventStamp();2722 );
2272 yield* offerRuntimeEvent({
2273 ...base,
2274 eventId: usageStamp.eventId,
2275 createdAt: usageStamp.createdAt,
2276 type: "thread.token-usage.updated",
2277 payload: {
2278 usage: normalizedUsage,
2279 },
2280 });
2281 }
2282 }
2283 yield* offerRuntimeEvent({2723 yield* offerRuntimeEvent({
2284 ...base,2724 ...base,
2285 type: "task.completed",2725 type: "task.completed",
...@@ -2669,6 +3109,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -2669,6 +3109,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
2669 const pendingApprovals = new Map<ApprovalRequestId, PendingApproval>();3109 const pendingApprovals = new Map<ApprovalRequestId, PendingApproval>();
2670 const pendingUserInputs = new Map<ApprovalRequestId, PendingUserInput>();3110 const pendingUserInputs = new Map<ApprovalRequestId, PendingUserInput>();
2671 const inFlightTools = new Map<number, ToolInFlight>();3111 const inFlightTools = new Map<number, ToolInFlight>();
3112 const claudeTasks = new Map<string, ClaudeTaskState>();
26723113
2673 const contextRef = yield* Ref.make<ClaudeSessionContext | undefined>(undefined);3114 const contextRef = yield* Ref.make<ClaudeSessionContext | undefined>(undefined);
26743115
...@@ -2969,6 +3410,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -2969,6 +3410,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
2969 const caps = getClaudeModelCapabilities(modelSelection?.model);3410 const caps = getClaudeModelCapabilities(modelSelection?.model);
2970 const descriptors = getProviderOptionDescriptors({ caps });3411 const descriptors = getProviderOptionDescriptors({ caps });
2971 const apiModelId = modelSelection ? resolveClaudeApiModelId(modelSelection) : undefined;3412 const apiModelId = modelSelection ? resolveClaudeApiModelId(modelSelection) : undefined;
3413 const initialContextWindow = selectedClaudeContextWindow(modelSelection);
2972 const rawEffort = getModelSelectionStringOptionValue(modelSelection, "effort");3414 const rawEffort = getModelSelectionStringOptionValue(modelSelection, "effort");
2973 const effort = resolveClaudeEffort(caps, rawEffort) ?? null;3415 const effort = resolveClaudeEffort(caps, rawEffort) ?? null;
2974 const fastModeSupported = descriptors.some(3416 const fastModeSupported = descriptors.some(
...@@ -3094,9 +3536,11 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (...@@ -3094,9 +3536,11 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
3094 pendingUserInputs,3536 pendingUserInputs,
3095 turns: [],3537 turns: [],
3096 inFlightTools,3538 inFlightTools,
3539 claudeTasks,
3097 turnState: undefined,3540 turnState: undefined,
3098 lastKnownContextWindow: undefined,3541 lastKnownContextWindow: initialContextWindow,
3099 lastKnownTokenUsage: undefined,3542 lastKnownTokenUsage: undefined,
3543 lastKnownTotalProcessedTokens: undefined,
3100 lastAssistantUuid: resumeState?.resumeSessionAt,3544 lastAssistantUuid: resumeState?.resumeSessionAt,
3101 lastThreadStartedId: undefined,3545 lastThreadStartedId: undefined,
3102 stopped: false,3546 stopped: false,
apps/server/src/provider/Layers/ClaudeProvider.ts-16
...@@ -108,14 +108,6 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [...@@ -108,14 +108,6 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
108 id: "fastMode",108 id: "fastMode",
109 label: "Fast Mode",109 label: "Fast Mode",
110 }),110 }),
111 buildSelectOptionDescriptor({
112 id: "contextWindow",
113 label: "Context Window",
114 options: [
115 { value: "200k", label: "200k", isDefault: true },
116 { value: "1m", label: "1M" },
117 ],
118 }),
119 ],111 ],
120 }),112 }),
121 },113 },
...@@ -142,14 +134,6 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [...@@ -142,14 +134,6 @@ const BUILT_IN_MODELS: ReadonlyArray<ServerProviderModel> = [
142 id: "fastMode",134 id: "fastMode",
143 label: "Fast Mode",135 label: "Fast Mode",
144 }),136 }),
145 buildSelectOptionDescriptor({
146 id: "contextWindow",
147 label: "Context Window",
148 options: [
149 { value: "200k", label: "200k", isDefault: true },
150 { value: "1m", label: "1M" },
151 ],
152 }),
153 ],137 ],
154 }),138 }),
155 },139 },
apps/web/src/components/ChatMarkdown.tsx+5-1
...@@ -665,6 +665,7 @@ function UncachedShikiCodeBlock({...@@ -665,6 +665,7 @@ function UncachedShikiCodeBlock({
665interface MarkdownFileLinkProps {665interface MarkdownFileLinkProps {
666 href: string;666 href: string;
667 targetPath: string;667 targetPath: string;
668 iconPath: string;
668 displayPath: string;669 displayPath: string;
669 label: string;670 label: string;
670 copyMarkdown: string;671 copyMarkdown: string;
...@@ -938,6 +939,7 @@ function MarkdownExternalLinkContent({...@@ -938,6 +939,7 @@ function MarkdownExternalLinkContent({
938const MarkdownFileLink = memo(function MarkdownFileLink({939const MarkdownFileLink = memo(function MarkdownFileLink({
939 href,940 href,
940 targetPath,941 targetPath,
942 iconPath,
941 displayPath,943 displayPath,
942 label,944 label,
943 copyMarkdown,945 copyMarkdown,
...@@ -1044,7 +1046,7 @@ const MarkdownFileLink = memo(function MarkdownFileLink({...@@ -1044,7 +1046,7 @@ const MarkdownFileLink = memo(function MarkdownFileLink({
1044 }}1046 }}
1045 onContextMenu={handleContextMenu}1047 onContextMenu={handleContextMenu}
1046 >1048 >
1047 <FileTagChipContent path={targetPath} label={label} theme={theme} selectable />1049 <FileTagChipContent path={iconPath} label={label} theme={theme} selectable />
1048 </a>1050 </a>
1049 }1051 }
1050 />1052 />
...@@ -1067,6 +1069,7 @@ function areMarkdownFileLinkPropsEqual(...@@ -1067,6 +1069,7 @@ function areMarkdownFileLinkPropsEqual(
1067 return (1069 return (
1068 previous.href === next.href &&1070 previous.href === next.href &&
1069 previous.targetPath === next.targetPath &&1071 previous.targetPath === next.targetPath &&
1072 previous.iconPath === next.iconPath &&
1070 previous.displayPath === next.displayPath &&1073 previous.displayPath === next.displayPath &&
1071 previous.label === next.label &&1074 previous.label === next.label &&
1072 previous.copyMarkdown === next.copyMarkdown &&1075 previous.copyMarkdown === next.copyMarkdown &&
...@@ -1186,6 +1189,7 @@ function ChatMarkdown({...@@ -1186,6 +1189,7 @@ function ChatMarkdown({
1186 <MarkdownFileLink1189 <MarkdownFileLink
1187 href={fileLinkMeta.targetPath}1190 href={fileLinkMeta.targetPath}
1188 targetPath={fileLinkMeta.targetPath}1191 targetPath={fileLinkMeta.targetPath}
1192 iconPath={fileLinkMeta.filePath}
1189 displayPath={fileLinkMeta.displayPath}1193 displayPath={fileLinkMeta.displayPath}
1190 label={labelParts.join(" · ")}1194 label={labelParts.join(" · ")}
1191 copyMarkdown={`[${fileLinkMeta.basename}](${normalizedHref})`}1195 copyMarkdown={`[${fileLinkMeta.basename}](${normalizedHref})`}
apps/web/src/components/DiffPanel.tsx+35-1
...@@ -53,6 +53,8 @@ const DIFF_PANEL_UNSAFE_CSS = `...@@ -53,6 +53,8 @@ const DIFF_PANEL_UNSAFE_CSS = `
53[data-file],53[data-file],
54[data-error-wrapper],54[data-error-wrapper],
55[data-virtualizer-buffer] {55[data-virtualizer-buffer] {
56 --diffs-header-font-family: var(--font-sans) !important;
57 --diffs-font-family: var(--font-mono) !important;
56 --diffs-bg: color-mix(in srgb, var(--card) 90%, var(--background)) !important;58 --diffs-bg: color-mix(in srgb, var(--card) 90%, var(--background)) !important;
57 --diffs-light-bg: color-mix(in srgb, var(--card) 90%, var(--background)) !important;59 --diffs-light-bg: color-mix(in srgb, var(--card) 90%, var(--background)) !important;
58 --diffs-dark-bg: color-mix(in srgb, var(--card) 90%, var(--background)) !important;60 --diffs-dark-bg: color-mix(in srgb, var(--card) 90%, var(--background)) !important;
...@@ -93,6 +95,37 @@ const DIFF_PANEL_UNSAFE_CSS = `...@@ -93,6 +95,37 @@ const DIFF_PANEL_UNSAFE_CSS = `
93 z-index: 4;95 z-index: 4;
94 background-color: color-mix(in srgb, var(--card) 94%, var(--foreground)) !important;96 background-color: color-mix(in srgb, var(--card) 94%, var(--foreground)) !important;
95 border-bottom: 1px solid var(--border) !important;97 border-bottom: 1px solid var(--border) !important;
98 align-items: center !important;
99 font-family: var(--font-sans) !important;
100 font-size: 12px !important;
101 line-height: 1 !important;
102 min-height: 32px !important;
103 padding-block: 6px !important;
104}
105
106[data-diffs-header] [data-header-content] {
107 align-items: center !important;
108 line-height: 1 !important;
109}
110
111[data-diffs-header] [data-metadata] {
112 align-items: center !important;
113 line-height: 1 !important;
114 font-variant-numeric: tabular-nums;
115}
116
117[data-diffs-header] [data-additions-count],
118[data-diffs-header] [data-deletions-count] {
119 font-family: var(--font-mono) !important;
120 font-size: 11px !important;
121 font-variant-numeric: tabular-nums;
122 line-height: 1 !important;
123}
124
125[data-diffs-header] [data-change-icon],
126[data-diffs-header] [data-rename-icon] {
127 display: block;
128 flex-shrink: 0;
96}129}
97130
98[data-title] {131[data-title] {
...@@ -103,6 +136,7 @@ const DIFF_PANEL_UNSAFE_CSS = `...@@ -103,6 +136,7 @@ const DIFF_PANEL_UNSAFE_CSS = `
103 text-decoration: underline;136 text-decoration: underline;
104 text-decoration-color: transparent;137 text-decoration-color: transparent;
105 text-underline-offset: 2px;138 text-underline-offset: 2px;
139 font-family: var(--font-sans) !important;
106}140}
107141
108[data-title]:hover {142[data-title]:hover {
...@@ -488,7 +522,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {...@@ -488,7 +522,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
488 inferredCheckpointTurnCountByTurnId[summary.turnId] ??522 inferredCheckpointTurnCountByTurnId[summary.turnId] ??
489 "?"}523 "?"}
490 </span>524 </span>
491 <span className="text-[9px] leading-tight opacity-70">525 <span className="text-[9px] leading-tight opacity-70 tabular-nums">
492 {formatShortTimestamp(summary.completedAt, settings.timestampFormat)}526 {formatShortTimestamp(summary.completedAt, settings.timestampFormat)}
493 </span>527 </span>
494 </div>528 </div>
apps/web/src/components/GitActionsControl.tsx+5-1
...@@ -19,6 +19,7 @@ import {...@@ -19,6 +19,7 @@ import {
19 ChevronDownIcon,19 ChevronDownIcon,
20 CloudUploadIcon,20 CloudUploadIcon,
21 ExternalLinkIcon,21 ExternalLinkIcon,
22 GitBranchPlusIcon,
22 GitCommitIcon,23 GitCommitIcon,
23 InfoIcon,24 InfoIcon,
24 LockIcon,25 LockIcon,
...@@ -1611,7 +1612,10 @@ export default function GitActionsControl({...@@ -1611,7 +1612,10 @@ export default function GitActionsControl({
1611 void initAction.run();1612 void initAction.run();
1612 }}1613 }}
1613 >1614 >
1614 {initAction.isPending ? "Initializing..." : "Initialize Git"}1615 <GitBranchPlusIcon className="size-3.5" aria-hidden />
1616 <span className="ml-0.5">
1617 {initAction.isPending ? "Initializing..." : "Initialize Git"}
1618 </span>
1615 </Button>1619 </Button>
1616 ) : (1620 ) : (
1617 <Group aria-label="Git actions" className="shrink-0">1621 <Group aria-label="Git actions" className="shrink-0">
apps/web/src/components/NoActiveThreadState.tsx+1-1
...@@ -30,7 +30,7 @@ export function NoActiveThreadState() {...@@ -30,7 +30,7 @@ export function NoActiveThreadState() {
30 </header>30 </header>
3131
32 <Empty className="flex-1">32 <Empty className="flex-1">
33 <div className="w-full max-w-lg rounded-3xl border border-border/55 bg-card/20 px-8 py-12 shadow-sm/5">33 <div className="w-full max-w-lg px-8 py-12">
34 <EmptyHeader className="max-w-none">34 <EmptyHeader className="max-w-none">
35 <EmptyTitle className="text-foreground text-xl">Pick a thread to continue</EmptyTitle>35 <EmptyTitle className="text-foreground text-xl">Pick a thread to continue</EmptyTitle>
36 <EmptyDescription className="mt-2 text-sm text-muted-foreground/78">36 <EmptyDescription className="mt-2 text-sm text-muted-foreground/78">
apps/web/src/components/PlanSidebar.tsx+8-7
...@@ -32,14 +32,14 @@ import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";...@@ -32,14 +32,14 @@ import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
32function stepStatusIcon(status: string): React.ReactNode {32function stepStatusIcon(status: string): React.ReactNode {
33 if (status === "completed") {33 if (status === "completed") {
34 return (34 return (
35 <span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-emerald-500/15 text-emerald-500">35 <span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-success/10 text-success-foreground">
36 <CheckIcon className="size-3" />36 <CheckIcon className="size-3" />
37 </span>37 </span>
38 );38 );
39 }39 }
40 if (status === "inProgress") {40 if (status === "inProgress") {
41 return (41 return (
42 <span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-blue-500/15 text-blue-400">42 <span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
43 <LoaderIcon className="size-3 animate-spin" />43 <LoaderIcon className="size-3 animate-spin" />
44 </span>44 </span>
45 );45 );
...@@ -139,13 +139,14 @@ const PlanSidebar = memo(function PlanSidebar({...@@ -139,13 +139,14 @@ const PlanSidebar = memo(function PlanSidebar({
139 <div className="flex h-12 shrink-0 items-center justify-between border-b border-border/60 px-3">139 <div className="flex h-12 shrink-0 items-center justify-between border-b border-border/60 px-3">
140 <div className="flex items-center gap-2">140 <div className="flex items-center gap-2">
141 <Badge141 <Badge
142 variant="secondary"142 variant="info"
143 className="rounded-md bg-blue-500/10 px-1.5 py-0 text-[10px] font-semibold tracking-wide text-blue-400 uppercase"143 size="sm"
144 className="rounded-md px-1.5 py-0 font-semibold tracking-wide uppercase"
144 >145 >
145 {label}146 {label}
146 </Badge>147 </Badge>
147 {activePlan ? (148 {activePlan ? (
148 <span className="text-[11px] text-muted-foreground/60">149 <span className="text-[11px] text-muted-foreground/60 tabular-nums">
149 {formatTimestamp(activePlan.createdAt, timestampFormat)}150 {formatTimestamp(activePlan.createdAt, timestampFormat)}
150 </span>151 </span>
151 ) : null}152 ) : null}
...@@ -211,12 +212,12 @@ const PlanSidebar = memo(function PlanSidebar({...@@ -211,12 +212,12 @@ const PlanSidebar = memo(function PlanSidebar({
211 <div212 <div
212 key={`${step.status}:${step.step}`}213 key={`${step.status}:${step.step}`}
213 className={cn(214 className={cn(
214 "flex items-start gap-2.5 rounded-lg px-2.5 py-2 transition-colors duration-200",215 "flex items-center gap-2.5 rounded-lg px-2.5 py-2 transition-colors duration-200",
215 step.status === "inProgress" && "bg-blue-500/5",216 step.status === "inProgress" && "bg-blue-500/5",
216 step.status === "completed" && "bg-emerald-500/5",217 step.status === "completed" && "bg-emerald-500/5",
217 )}218 )}
218 >219 >
219 <div className="mt-0.5">{stepStatusIcon(step.status)}</div>220 {stepStatusIcon(step.status)}
220 <p221 <p
221 className={cn(222 className={cn(
222 "text-[13px] leading-snug",223 "text-[13px] leading-snug",
apps/web/src/components/chat/ChatHeader.tsx+2-16
...@@ -10,7 +10,6 @@ import { memo } from "react";...@@ -10,7 +10,6 @@ import { memo } from "react";
10import GitActionsControl from "../GitActionsControl";10import GitActionsControl from "../GitActionsControl";
11import { type DraftId } from "~/composerDraftStore";11import { type DraftId } from "~/composerDraftStore";
12import { DiffIcon, TerminalSquareIcon } from "lucide-react";12import { DiffIcon, TerminalSquareIcon } from "lucide-react";
13import { Badge } from "../ui/badge";
14import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";13import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
15import ProjectScriptsControl, { type NewProjectScriptInput } from "../ProjectScriptsControl";14import ProjectScriptsControl, { type NewProjectScriptInput } from "../ProjectScriptsControl";
16import { Toggle } from "../ui/toggle";15import { Toggle } from "../ui/toggle";
...@@ -105,19 +104,6 @@ export const ChatHeader = memo(function ChatHeader({...@@ -105,19 +104,6 @@ export const ChatHeader = memo(function ChatHeader({
105 />104 />
106 <TooltipPopup side="top">{activeThreadTitle}</TooltipPopup>105 <TooltipPopup side="top">{activeThreadTitle}</TooltipPopup>
107 </Tooltip>106 </Tooltip>
108 {activeProjectName && (
109 <Badge
110 variant="outline"
111 className="min-w-0 max-w-full shrink overflow-hidden sm:max-w-56"
112 >
113 <span className="min-w-0 truncate">{activeProjectName}</span>
114 </Badge>
115 )}
116 {activeProjectName && !isGitRepo && (
117 <Badge variant="outline" className="shrink-0 text-[10px] text-amber-700">
118 No Git
119 </Badge>
120 )}
121 </div>107 </div>
122 <div className="flex min-w-0 flex-wrap items-center justify-start gap-2 sm:shrink-0 sm:justify-end @3xl/header-actions:gap-3">108 <div className="flex min-w-0 flex-wrap items-center justify-start gap-2 sm:shrink-0 sm:justify-end @3xl/header-actions:gap-3">
123 {activeProjectScripts && (109 {activeProjectScripts && (
...@@ -153,7 +139,7 @@ export const ChatHeader = memo(function ChatHeader({...@@ -153,7 +139,7 @@ export const ChatHeader = memo(function ChatHeader({
153 pressed={terminalOpen}139 pressed={terminalOpen}
154 onPressedChange={onToggleTerminal}140 onPressedChange={onToggleTerminal}
155 aria-label="Toggle terminal drawer"141 aria-label="Toggle terminal drawer"
156 variant="outline"142 variant="ghost"
157 size="xs"143 size="xs"
158 disabled={!terminalAvailable}144 disabled={!terminalAvailable}
159 >145 >
...@@ -177,7 +163,7 @@ export const ChatHeader = memo(function ChatHeader({...@@ -177,7 +163,7 @@ export const ChatHeader = memo(function ChatHeader({
177 pressed={diffOpen}163 pressed={diffOpen}
178 onPressedChange={onToggleDiff}164 onPressedChange={onToggleDiff}
179 aria-label="Toggle diff panel"165 aria-label="Toggle diff panel"
180 variant="outline"166 variant="ghost"
181 size="xs"167 size="xs"
182 disabled={!isGitRepo && !diffOpen}168 disabled={!isGitRepo && !diffOpen}
183 >169 >
apps/web/src/components/chat/ComposerPlanFollowUpBanner.tsx+8-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1import { memo } from "react";1import { memo } from "react";
2import { Badge } from "../ui/badge";
23
3export const ComposerPlanFollowUpBanner = memo(function ComposerPlanFollowUpBanner({4export const ComposerPlanFollowUpBanner = memo(function ComposerPlanFollowUpBanner({
4 planTitle,5 planTitle,
...@@ -8,7 +9,13 @@ export const ComposerPlanFollowUpBanner = memo(function ComposerPlanFollowUpBann...@@ -8,7 +9,13 @@ export const ComposerPlanFollowUpBanner = memo(function ComposerPlanFollowUpBann
8 return (9 return (
9 <div className="px-4 py-3.5 sm:px-5 sm:py-4">10 <div className="px-4 py-3.5 sm:px-5 sm:py-4">
10 <div className="flex flex-wrap items-center gap-2">11 <div className="flex flex-wrap items-center gap-2">
11 <span className="uppercase text-sm tracking-[0.2em]">Plan ready</span>12 <Badge
13 variant="info"
14 size="sm"
15 className="rounded-md px-1.5 py-0 font-semibold tracking-wide uppercase"
16 >
17 Plan Ready
18 </Badge>
12 {planTitle ? (19 {planTitle ? (
13 <span className="min-w-0 flex-1 truncate text-sm font-medium">{planTitle}</span>20 <span className="min-w-0 flex-1 truncate text-sm font-medium">{planTitle}</span>
14 ) : null}21 ) : null}
apps/web/src/components/chat/MessagesTimeline.browser.tsx+29
...@@ -7,6 +7,8 @@ import { page } from "vite-plus/test/browser";...@@ -7,6 +7,8 @@ import { page } from "vite-plus/test/browser";
7import { afterEach, describe, expect, it, vi } from "vite-plus/test";7import { afterEach, describe, expect, it, vi } from "vite-plus/test";
8import { render } from "vitest-browser-react";8import { render } from "vitest-browser-react";
99
10import { getVscodeIconUrlForEntry } from "../../vscode-icons";
11
10const scrollToEndSpy = vi.fn();12const scrollToEndSpy = vi.fn();
11const getStateSpy = vi.fn(() => ({ isAtEnd: true }));13const getStateSpy = vi.fn(() => ({ isAtEnd: true }));
1214
...@@ -386,4 +388,31 @@ describe("MessagesTimeline", () => {...@@ -386,4 +388,31 @@ describe("MessagesTimeline", () => {
386 await screen.unmount();388 await screen.unmount();
387 }389 }
388 });390 });
391
392 it("uses the file path without line suffix for markdown file tag icons", async () => {
393 const fileLink = "[package.json](path/to/package.json:25)";
394 const screen = await render(
395 <MessagesTimeline
396 {...buildProps()}
397 markdownCwd="/repo/project"
398 timelineEntries={[buildAssistantTimelineEntry(`Updated ${fileLink}`)]}
399 />,
400 );
401
402 try {
403 const assistantFileLink = document.querySelector(
404 '[data-message-role="assistant"] .chat-markdown-file-link',
405 );
406 const icon = assistantFileLink?.querySelector("img");
407
408 expect(assistantFileLink?.textContent).toContain("package.json");
409 expect(assistantFileLink?.textContent).toContain("L25");
410 expect(assistantFileLink?.getAttribute("href")).toBe("/repo/project/path/to/package.json:25");
411 expect(icon?.getAttribute("src")).toBe(
412 getVscodeIconUrlForEntry("/repo/project/path/to/package.json", "file", "dark"),
413 );
414 } finally {
415 await screen.unmount();
416 }
417 });
389});418});
apps/web/src/components/ui/toggle.tsx+2
...@@ -21,6 +21,8 @@ const toggleVariants = cva(...@@ -21,6 +21,8 @@ const toggleVariants = cva(
21 },21 },
22 variant: {22 variant: {
23 default: "border-transparent",23 default: "border-transparent",
24 ghost:
25 "border-transparent text-foreground shadow-none [:disabled,:active,[data-pressed]]:shadow-none before:shadow-none data-pressed:bg-accent data-pressed:text-accent-foreground disabled:opacity-100 disabled:text-muted-foreground disabled:[&_svg]:opacity-100",
24 outline:26 outline:
25 "border-input bg-background not-dark:bg-clip-padding shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:data-pressed:bg-input dark:hover:bg-input/64 dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:not-disabled:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/2%)] [:disabled,:active,[data-pressed]]:shadow-none",27 "border-input bg-background not-dark:bg-clip-padding shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:data-pressed:bg-input dark:hover:bg-input/64 dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:not-disabled:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/2%)] [:disabled,:active,[data-pressed]]:shadow-none",
26 },28 },
pnpm-lock.yaml+71-52
...@@ -371,8 +371,8 @@ importers:...@@ -371,8 +371,8 @@ importers:
371 apps/server:371 apps/server:
372 dependencies:372 dependencies:
373 '@anthropic-ai/claude-agent-sdk':373 '@anthropic-ai/claude-agent-sdk':
374 specifier: ^0.3.154374 specifier: ^0.3.170
375 version: 0.3.159(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)375 version: 0.3.170(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)
376 '@effect/platform-bun':376 '@effect/platform-bun':
377 specifier: 4.0.0-beta.78377 specifier: 4.0.0-beta.78
378 version: 4.0.0-beta.78(bufferutil@4.1.0)(effect@4.0.0-beta.78(patch_hash=883249d8efbb462e928e21fefef96027b66aec50751178cafdce45f08eee3754))(utf-8-validate@6.0.6)378 version: 4.0.0-beta.78(bufferutil@4.1.0)(effect@4.0.0-beta.78(patch_hash=883249d8efbb462e928e21fefef96027b66aec50751178cafdce45f08eee3754))(utf-8-validate@6.0.6)
...@@ -854,48 +854,48 @@ packages:...@@ -854,48 +854,48 @@ packages:
854 '@alchemy.run/node-utils@0.0.4':854 '@alchemy.run/node-utils@0.0.4':
855 resolution: {integrity: sha512-TiIhPXCTCi3tk0zmdYJJ14CNSesSfsJxXdIOP0HTSItQ1mZWLocrF7qCuEWKyW/IEFzp6kaiOf19aIA/mbCp1g==}855 resolution: {integrity: sha512-TiIhPXCTCi3tk0zmdYJJ14CNSesSfsJxXdIOP0HTSItQ1mZWLocrF7qCuEWKyW/IEFzp6kaiOf19aIA/mbCp1g==}
856856
857 '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.159':857 '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.170':
858 resolution: {integrity: sha512-3nnH4yUNJVSyaU5DBlGw2yxc4zlnVvAnc9UOe+La47QVG7/dN+rWAgn4zCbqKk9bWFLDQ1Ek0r56EZE1Qo4UKQ==}858 resolution: {integrity: sha512-rwfgArIa5WI0QPNqFsRBgvtSI0mrtpynUm0oK6+l6/KX4hcgnYGEzciZR1bOeD9/7sSZlTdIgt+T9alKeZmXcg==}
859 cpu: [arm64]859 cpu: [arm64]
860 os: [darwin]860 os: [darwin]
861861
862 '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.159':862 '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.170':
863 resolution: {integrity: sha512-iv+NRjz+t4Q1R2+kLdDbccSo3b0wedVJ9jMT3noznOVojZHzgkxVpTvt36/XkXV0rqIDQ5H18bBYIZZzOXu7mg==}863 resolution: {integrity: sha512-0e58h8UQMtsQxLGIv9r4foxfBFWKZ7NeDtoplLhuD7EwQonehomw1sBXCch77t/IfUS+q5vQ5zv+fOGmap5nLQ==}
864 cpu: [x64]864 cpu: [x64]
865 os: [darwin]865 os: [darwin]
866866
867 '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.159':867 '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.170':
868 resolution: {integrity: sha512-WvwiQBWt3tdu5EwqjpDZszI6p2uetYsw4Cxc6ptO/SmLIYXcDienP8nmirZdsZrS+Gzk6imgY0IY5mmNaRhelQ==}868 resolution: {integrity: sha512-SRYfQcsXlOq+CD/FqkQBTSHbaD++w73GnnO+NUV9adLYrca3kfetRwWT1iguY1cNS0l34dCR3rlzCPq78vg1Jg==}
869 cpu: [arm64]869 cpu: [arm64]
870 os: [linux]870 os: [linux]
871871
872 '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.159':872 '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.170':
873 resolution: {integrity: sha512-FlsS5M4GCpzsQVaNDFF8dRgFGR3QwyAHZFl/xM/2Y2BqVBH+NH17RpKQSJxr1qr41QnsNkinMnu2iSKoc33hKg==}873 resolution: {integrity: sha512-gLbaFqcGppFJQd4DLNV4IXoeahejT/p2/M8bSSvRDbla9GOsBr1AxV5XLRyBn1e7xFGozZIAIQr3+1chp7NJgQ==}
874 cpu: [arm64]874 cpu: [arm64]
875 os: [linux]875 os: [linux]
876876
877 '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.159':877 '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.170':
878 resolution: {integrity: sha512-kFH6RC2YJbPc8XWRNy/wL4YU7LzdJjSwAdH488sVzIif3q+TrvVrV5y/IW0+MLmta+CKIqtFYpGaucsJYvj7Eg==}878 resolution: {integrity: sha512-m4+I0qBEk7cxRKS+pL+eoWXbXTFOAo83fQ0tQvap4z/mDMm06IWJtEPoYTaMBwsp32GJWLkHWKbZSBCHZnp2DQ==}
879 cpu: [x64]879 cpu: [x64]
880 os: [linux]880 os: [linux]
881881
882 '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.159':882 '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.170':
883 resolution: {integrity: sha512-uNPEC/iRzVb4bEdzs0KAz1zV7i1PVGEZZnJTQyi1OtgVa81sAoH/H0CbbzDiTsquKdaESf+1DSSEkUlfZmMUEw==}883 resolution: {integrity: sha512-Xl/m7TaSC3T5IDBdHrZQ9fCQYyDmPELN34CL+MoyPIf7uSmuZnjE9fUOqDh2Rv26JxWssi1M6X+BBvVuKd6Cpg==}
884 cpu: [x64]884 cpu: [x64]
885 os: [linux]885 os: [linux]
886886
887 '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.159':887 '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.170':
888 resolution: {integrity: sha512-WN1QEZGgWXz9GMl61QU6j9E+LEF5plki87bL2xsGwuCPzK+OeVPQU55pabuP8P+vFBFHUo3Y9OlTVyZHnUzmAQ==}888 resolution: {integrity: sha512-IG+8isJNNJKbnnhO7m+PGhfVCg+XoQ/MDxGde5eigFI0WsEfitjuWSWwx82bT9ghxI1aa6qNvI+UPgPcZuo5Fg==}
889 cpu: [arm64]889 cpu: [arm64]
890 os: [win32]890 os: [win32]
891891
892 '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.159':892 '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.170':
893 resolution: {integrity: sha512-Ty4seccD+dTDX5hhj89IUELZd/LkxO5O43Uiz5Mo8ZJktoX38SK4XMZlBS935QdqTFLRvPL0hvK4Lt4dTOqzPw==}893 resolution: {integrity: sha512-7cuqSKbHVItPGVwRbd3A0BEJwcNtc7Fhoh6qHN4C6yrmjSrvdYYx3MLvq/VI768/RoG7mAMDxb+j7WfEfoP9BA==}
894 cpu: [x64]894 cpu: [x64]
895 os: [win32]895 os: [win32]
896896
897 '@anthropic-ai/claude-agent-sdk@0.3.159':897 '@anthropic-ai/claude-agent-sdk@0.3.170':
898 resolution: {integrity: sha512-Xh1oVMIK6N3KsiNIhqNH8ZK90zjRmAEL9d1Md8ZlGdHJE+HhdMYdBadujc3KEkV0uufsEUvYp+A3fDenfypGSA==}898 resolution: {integrity: sha512-pAvhfk+iTodXZ6RF18Kz7BEUWFjL7EcR3tKuhUNdPpE1NAYCR3mSHGbafi72JsrNwKEDIs7FU31z3fqhwy8QzA==}
899 engines: {node: '>=18.0.0'}899 engines: {node: '>=18.0.0'}
900 peerDependencies:900 peerDependencies:
901 '@anthropic-ai/sdk': '>=0.93.0'901 '@anthropic-ai/sdk': '>=0.93.0'
...@@ -2809,6 +2809,12 @@ packages:...@@ -2809,6 +2809,12 @@ packages:
2809 '@emnapi/core': ^1.7.12809 '@emnapi/core': ^1.7.1
2810 '@emnapi/runtime': ^1.7.12810 '@emnapi/runtime': ^1.7.1
28112811
2812 '@napi-rs/wasm-runtime@1.1.5':
2813 resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==}
2814 peerDependencies:
2815 '@emnapi/core': ^1.7.1
2816 '@emnapi/runtime': ^1.7.1
2817
2812 '@neon-rs/load@0.0.4':2818 '@neon-rs/load@0.0.4':
2813 resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==}2819 resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==}
28142820
...@@ -6534,8 +6540,8 @@ packages:...@@ -6534,8 +6540,8 @@ packages:
6534 hoist-non-react-statics@3.3.2:6540 hoist-non-react-statics@3.3.2:
6535 resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}6541 resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
65366542
6537 hono@4.12.23:6543 hono@4.12.25:
6538 resolution: {integrity: sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==}6544 resolution: {integrity: sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==}
6539 engines: {node: '>=16.9.0'}6545 engines: {node: '>=16.9.0'}
65406546
6541 hosted-git-info@4.1.0:6547 hosted-git-info@4.1.0:
...@@ -7689,6 +7695,10 @@ packages:...@@ -7689,6 +7695,10 @@ packages:
7689 obug@2.1.1:7695 obug@2.1.1:
7690 resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}7696 resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
76917697
7698 obug@2.1.2:
7699 resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==}
7700 engines: {node: '>=12.20.0'}
7701
7692 ofetch@1.5.1:7702 ofetch@1.5.1:
7693 resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}7703 resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}
76947704
...@@ -8627,8 +8637,8 @@ packages:...@@ -8627,8 +8637,8 @@ packages:
8627 resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}8637 resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
8628 engines: {node: '>= 0.4'}8638 engines: {node: '>= 0.4'}
86298639
8630 side-channel@1.1.0:8640 side-channel@1.1.1:
8631 resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}8641 resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
8632 engines: {node: '>= 0.4'}8642 engines: {node: '>= 0.4'}
86338643
8634 siginfo@2.0.0:8644 siginfo@2.0.0:
...@@ -9673,44 +9683,44 @@ snapshots:...@@ -9673,44 +9683,44 @@ snapshots:
96739683
9674 '@alchemy.run/node-utils@0.0.4': {}9684 '@alchemy.run/node-utils@0.0.4': {}
96759685
9676 '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.159':9686 '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.170':
9677 optional: true9687 optional: true
96789688
9679 '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.159':9689 '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.170':
9680 optional: true9690 optional: true
96819691
9682 '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.159':9692 '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.170':
9683 optional: true9693 optional: true
96849694
9685 '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.159':9695 '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.170':
9686 optional: true9696 optional: true
96879697
9688 '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.159':9698 '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.170':
9689 optional: true9699 optional: true
96909700
9691 '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.159':9701 '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.170':
9692 optional: true9702 optional: true
96939703
9694 '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.159':9704 '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.170':
9695 optional: true9705 optional: true
96969706
9697 '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.159':9707 '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.170':
9698 optional: true9708 optional: true
96999709
9700 '@anthropic-ai/claude-agent-sdk@0.3.159(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)':9710 '@anthropic-ai/claude-agent-sdk@0.3.170(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)':
9701 dependencies:9711 dependencies:
9702 '@anthropic-ai/sdk': 0.93.0(zod@4.4.3)9712 '@anthropic-ai/sdk': 0.93.0(zod@4.4.3)
9703 '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3)9713 '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3)
9704 zod: 4.4.39714 zod: 4.4.3
9705 optionalDependencies:9715 optionalDependencies:
9706 '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.1599716 '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.170
9707 '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.1599717 '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.170
9708 '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.1599718 '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.170
9709 '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.1599719 '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.170
9710 '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.1599720 '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.170
9711 '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.1599721 '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.170
9712 '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.1599722 '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.170
9713 '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.1599723 '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.170
97149724
9715 '@anthropic-ai/sdk@0.93.0(zod@4.4.3)':9725 '@anthropic-ai/sdk@0.93.0(zod@4.4.3)':
9716 dependencies:9726 dependencies:
...@@ -11606,9 +11616,9 @@ snapshots:...@@ -11606,9 +11616,9 @@ snapshots:
1160611616
11607 '@formkit/auto-animate@0.9.0': {}11617 '@formkit/auto-animate@0.9.0': {}
1160811618
11609 '@hono/node-server@1.19.14(hono@4.12.23)':11619 '@hono/node-server@1.19.14(hono@4.12.25)':
11610 dependencies:11620 dependencies:
11611 hono: 4.12.2311621 hono: 4.12.25
1161211622
11613 '@img/colour@1.1.0':11623 '@img/colour@1.1.0':
11614 optional: true11624 optional: true
...@@ -12031,7 +12041,7 @@ snapshots:...@@ -12031,7 +12041,7 @@ snapshots:
1203112041
12032 '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)':12042 '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)':
12033 dependencies:12043 dependencies:
12034 '@hono/node-server': 1.19.14(hono@4.12.23)12044 '@hono/node-server': 1.19.14(hono@4.12.25)
12035 ajv: 8.20.012045 ajv: 8.20.0
12036 ajv-formats: 3.0.1(ajv@8.20.0)12046 ajv-formats: 3.0.1(ajv@8.20.0)
12037 content-type: 1.0.512047 content-type: 1.0.5
...@@ -12041,7 +12051,7 @@ snapshots:...@@ -12041,7 +12051,7 @@ snapshots:
12041 eventsource-parser: 3.1.012051 eventsource-parser: 3.1.0
12042 express: 5.2.112052 express: 5.2.1
12043 express-rate-limit: 8.5.2(express@5.2.1)12053 express-rate-limit: 8.5.2(express@5.2.1)
12044 hono: 4.12.2312054 hono: 4.12.25
12045 jose: 6.2.312055 jose: 6.2.3
12046 json-schema-typed: 8.0.212056 json-schema-typed: 8.0.2
12047 pkce-challenge: 5.0.112057 pkce-challenge: 5.0.1
...@@ -12085,6 +12095,13 @@ snapshots:...@@ -12085,6 +12095,13 @@ snapshots:
12085 '@tybys/wasm-util': 0.10.212095 '@tybys/wasm-util': 0.10.2
12086 optional: true12096 optional: true
1208712097
12098 '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
12099 dependencies:
12100 '@emnapi/core': 1.10.0
12101 '@emnapi/runtime': 1.10.0
12102 '@tybys/wasm-util': 0.10.2
12103 optional: true
12104
12088 '@neon-rs/load@0.0.4': {}12105 '@neon-rs/load@0.0.4': {}
1208912106
12090 '@noble/curves@1.9.1':12107 '@noble/curves@1.9.1':
...@@ -12603,7 +12620,7 @@ snapshots:...@@ -12603,7 +12620,7 @@ snapshots:
12603 '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7)12620 '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7)
12604 '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7)12621 '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7)
12605 '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7)12622 '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7)
12606 '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.7)12623 '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7)
12607 '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7)12624 '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7)
12608 '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7)12625 '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7)
12609 '@babel/plugin-transform-regenerator': 7.29.7(@babel/core@7.29.7)12626 '@babel/plugin-transform-regenerator': 7.29.7(@babel/core@7.29.7)
...@@ -12818,7 +12835,7 @@ snapshots:...@@ -12818,7 +12835,7 @@ snapshots:
12818 dependencies:12835 dependencies:
12819 '@emnapi/core': 1.10.012836 '@emnapi/core': 1.10.0
12820 '@emnapi/runtime': 1.10.012837 '@emnapi/runtime': 1.10.0
12821 '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)12838 '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
12822 optional: true12839 optional: true
1282312840
12824 '@rolldown/binding-wasm32-wasi@1.0.1':12841 '@rolldown/binding-wasm32-wasi@1.0.1':
...@@ -12832,7 +12849,7 @@ snapshots:...@@ -12832,7 +12849,7 @@ snapshots:
12832 dependencies:12849 dependencies:
12833 '@emnapi/core': 1.10.012850 '@emnapi/core': 1.10.0
12834 '@emnapi/runtime': 1.10.012851 '@emnapi/runtime': 1.10.0
12835 '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)12852 '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
12836 optional: true12853 optional: true
1283712854
12838 '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17':12855 '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17':
...@@ -15896,7 +15913,7 @@ snapshots:...@@ -15896,7 +15913,7 @@ snapshots:
15896 dependencies:15913 dependencies:
15897 react-is: 16.13.115914 react-is: 16.13.1
1589815915
15899 hono@4.12.23: {}15916 hono@4.12.25: {}
1590015917
15901 hosted-git-info@4.1.0:15918 hosted-git-info@4.1.0:
15902 dependencies:15919 dependencies:
...@@ -17258,6 +17275,8 @@ snapshots:...@@ -17258,6 +17275,8 @@ snapshots:
1725817275
17259 obug@2.1.1: {}17276 obug@2.1.1: {}
1726017277
17278 obug@2.1.2: {}
17279
17261 ofetch@1.5.1:17280 ofetch@1.5.1:
17262 dependencies:17281 dependencies:
17263 destr: 2.0.517282 destr: 2.0.5
...@@ -17645,7 +17664,7 @@ snapshots:...@@ -17645,7 +17664,7 @@ snapshots:
1764517664
17646 qs@6.15.2:17665 qs@6.15.2:
17647 dependencies:17666 dependencies:
17648 side-channel: 1.1.017667 side-channel: 1.1.1
1764917668
17650 query-string@7.1.3:17669 query-string@7.1.3:
17651 dependencies:17670 dependencies:
...@@ -18439,7 +18458,7 @@ snapshots:...@@ -18439,7 +18458,7 @@ snapshots:
18439 object-inspect: 1.13.418458 object-inspect: 1.13.4
18440 side-channel-map: 1.0.118459 side-channel-map: 1.0.1
1844118460
18442 side-channel@1.1.0:18461 side-channel@1.1.1:
18443 dependencies:18462 dependencies:
18444 es-errors: 1.3.018463 es-errors: 1.3.0
18445 object-inspect: 1.13.418464 object-inspect: 1.13.4
...@@ -19071,7 +19090,7 @@ snapshots:...@@ -19071,7 +19090,7 @@ snapshots:
19071 es-module-lexer: 2.1.019090 es-module-lexer: 2.1.0
19072 expect-type: 1.3.019091 expect-type: 1.3.0
19073 magic-string: 0.30.2119092 magic-string: 0.30.21
19074 obug: 2.1.119093 obug: 2.1.2
19075 pathe: 2.0.319094 pathe: 2.0.3
19076 picomatch: 4.0.419095 picomatch: 4.0.4
19077 std-env: 4.1.019096 std-env: 4.1.0