@workerdeck/ui 1.1.0 → 1.2.0
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/build/{SessionPanel-I5QSLLb0.mjs → SessionPanel-DVhnUwXw.mjs} +37 -2
- package/build/SessionPanel-DVhnUwXw.mjs.map +1 -0
- package/build/format-C4h4jZL_.d.mts +25 -0
- package/build/format.d.mts +2 -2
- package/build/format.mjs +2 -2
- package/build/index.d.mts +2 -2
- package/build/index.mjs +2 -2
- package/build/scoped.css +4 -0
- package/build/{status-Dm9INVTt.mjs → status-BW2xj79g.mjs} +11 -2
- package/build/status-BW2xj79g.mjs.map +1 -0
- package/build/workspace.mjs +2 -2
- package/package.json +4 -4
- package/src/components/agent/TranscriptItemView.tsx +19 -1
- package/src/components/terminal/TerminalTranscript.tsx +4 -1
- package/src/components/terminal/height.ts +6 -1
- package/src/components/terminal/items.tsx +14 -1
- package/src/lib/format.ts +10 -0
- package/build/SessionPanel-I5QSLLb0.mjs.map +0 -1
- package/build/format-C_lpFq0l.d.mts +0 -16
- package/build/status-Dm9INVTt.mjs.map +0 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/lib/format.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The whole payload of a `compaction` row. The wire event carries no summary because codex's
|
|
4
|
+
* `contextCompaction` item carries none, so the sentence *is* the content.
|
|
5
|
+
*
|
|
6
|
+
* It lives here, in a pure module, because the renderer (`terminal/items.tsx`) and the height book
|
|
7
|
+
* (`terminal/height.ts`) must measure and draw the same string — height.ts cannot import the
|
|
8
|
+
* `.tsx` without pulling React into a module the tests require to stay pure.
|
|
9
|
+
*/
|
|
10
|
+
declare const COMPACTION_TEXT = "context compacted · earlier turns summarised to fit the window";
|
|
11
|
+
declare function formatCost(usd: number | undefined): string;
|
|
12
|
+
declare function formatDuration(ms: number): string;
|
|
13
|
+
declare function formatTokens(tokens: number): string;
|
|
14
|
+
declare function formatBytes(bytes: number): string;
|
|
15
|
+
declare function formatCountdown(untilEpochMs: number, now?: number): string;
|
|
16
|
+
declare function formatRelativeTime(epochMs: number | undefined, now?: number): string;
|
|
17
|
+
declare function formatRateLimitWindow(key: string): string;
|
|
18
|
+
declare function formatRateLimitWindowLong(key: string): string;
|
|
19
|
+
declare function rateLimitWindowSeconds(key: string): number | undefined;
|
|
20
|
+
declare function formatAgoPrecise(epochMs: number, now?: number): string;
|
|
21
|
+
declare function toolInputPreview(input: unknown, max?: number): string;
|
|
22
|
+
declare function friendlyModel(id: string | undefined): string | undefined;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { formatCountdown as a, formatRateLimitWindowLong as c, friendlyModel as d, rateLimitWindowSeconds as f, formatCost as i, formatRelativeTime as l, formatAgoPrecise as n, formatDuration as o, toolInputPreview as p, formatBytes as r, formatRateLimitWindow as s, COMPACTION_TEXT as t, formatTokens as u };
|
|
25
|
+
//# sourceMappingURL=format-C4h4jZL_.d.mts.map
|
package/build/format.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as formatCountdown, c as formatRateLimitWindowLong, d as friendlyModel, f as rateLimitWindowSeconds, i as formatCost, l as formatRelativeTime, n as formatAgoPrecise, o as formatDuration, p as toolInputPreview, r as formatBytes, s as formatRateLimitWindow, t as COMPACTION_TEXT, u as formatTokens } from "./format-C4h4jZL_.mjs";
|
|
2
2
|
import { ModelOption, RateLimitInfo, SessionStatus } from "@workerdeck/protocol";
|
|
3
3
|
//#region src/lib/status.d.ts
|
|
4
4
|
type StatusSeverity = 'none' | 'warning' | 'error';
|
|
@@ -40,5 +40,5 @@ declare function meterColorClass(pct: number | undefined): string;
|
|
|
40
40
|
*/
|
|
41
41
|
declare function meterTintClass(pct: number): string;
|
|
42
42
|
//#endregion
|
|
43
|
-
export { ModelReadings, StatusPresentation, StatusReadings, StatusSeverity, UsageLane, contextSeverity, currentModel, formatAgoPrecise, formatBytes, formatCost, formatCountdown, formatDuration, formatRateLimitWindow, formatRateLimitWindowLong, formatRelativeTime, formatTokens, friendlyModel, meterColorClass, meterSeverity, meterTintClass, modelLabel, rateLimitWindowSeconds, statusPresentation, tightestWindow, toolInputPreview, usageWindow, windowLabel };
|
|
43
|
+
export { COMPACTION_TEXT, ModelReadings, StatusPresentation, StatusReadings, StatusSeverity, UsageLane, contextSeverity, currentModel, formatAgoPrecise, formatBytes, formatCost, formatCountdown, formatDuration, formatRateLimitWindow, formatRateLimitWindowLong, formatRelativeTime, formatTokens, friendlyModel, meterColorClass, meterSeverity, meterTintClass, modelLabel, rateLimitWindowSeconds, statusPresentation, tightestWindow, toolInputPreview, usageWindow, windowLabel };
|
|
44
44
|
//# sourceMappingURL=format.d.mts.map
|
package/build/format.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { contextSeverity, currentModel, formatAgoPrecise, formatBytes, formatCost, formatCountdown, formatDuration, formatRateLimitWindow, formatRateLimitWindowLong, formatRelativeTime, formatTokens, friendlyModel, meterColorClass, meterSeverity, meterTintClass, modelLabel, rateLimitWindowSeconds, statusPresentation, tightestWindow, toolInputPreview, usageWindow, windowLabel };
|
|
1
|
+
import { C as toolInputPreview, S as rateLimitWindowSeconds, _ as formatRateLimitWindow, a as meterTintClass, b as formatTokens, c as tightestWindow, d as COMPACTION_TEXT, f as formatAgoPrecise, g as formatDuration, h as formatCountdown, i as meterSeverity, l as usageWindow, m as formatCost, n as currentModel, o as modelLabel, p as formatBytes, r as meterColorClass, s as statusPresentation, t as contextSeverity, u as windowLabel, v as formatRateLimitWindowLong, x as friendlyModel, y as formatRelativeTime } from "./status-BW2xj79g.mjs";
|
|
2
|
+
export { COMPACTION_TEXT, contextSeverity, currentModel, formatAgoPrecise, formatBytes, formatCost, formatCountdown, formatDuration, formatRateLimitWindow, formatRateLimitWindowLong, formatRelativeTime, formatTokens, friendlyModel, meterColorClass, meterSeverity, meterTintClass, modelLabel, rateLimitWindowSeconds, statusPresentation, tightestWindow, toolInputPreview, usageWindow, windowLabel };
|
package/build/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as formatCountdown, c as formatRateLimitWindowLong, d as friendlyModel, f as rateLimitWindowSeconds, i as formatCost, l as formatRelativeTime, n as formatAgoPrecise, o as formatDuration, p as toolInputPreview, r as formatBytes, s as formatRateLimitWindow, u as formatTokens } from "./format-C4h4jZL_.mjs";
|
|
2
2
|
import { C as permissionModeChoices, S as PermissionModeSelectProps, _ as useAffordances, a as SessionVitals, b as PermissionModeMeta, c as TranscriptDensityProvider, d as TranscriptVariantProvider, f as useTranscriptDensity, g as WithActions, h as TerminalAffordances, i as SessionSurfacePanel, l as TranscriptFont, m as CopyAction, n as SessionPanel, o as TerminalMetrics, p as useTranscriptVariant, r as SessionPanelProps, s as TranscriptDensity, t as SessionControls, u as TranscriptVariant, v as PERMISSION_MODES, w as permissionModeMeta, x as PermissionModeSelect, y as PermissionModeChoice } from "./SessionPanel-yNO60Tzw.mjs";
|
|
3
3
|
import { ButtonHTMLAttributes, FunctionComponent, HTMLAttributes, InputHTMLAttributes, ReactElement, ReactNode, Ref, RefObject, TextareaHTMLAttributes } from "react";
|
|
4
4
|
import { VariantProps } from "class-variance-authority";
|
|
@@ -15,7 +15,7 @@ import { ConnectionState, TranscriptItem, TranscriptState, UseAttachmentsResult
|
|
|
15
15
|
import { WorkerDeckClient } from "@workerdeck/client";
|
|
16
16
|
//#region src/components/ui/Button.d.ts
|
|
17
17
|
declare const buttonVariants: (props?: ({
|
|
18
|
-
variant?: "default" | "
|
|
18
|
+
variant?: "default" | "link" | "outline" | "secondary" | "ghost" | "destructive" | null | undefined;
|
|
19
19
|
size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-sm" | null | undefined;
|
|
20
20
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
21
21
|
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {}
|
package/build/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as isMutatingTool, A as PERMISSION_MODES, At as MenuItem, B as Composer, Bt as Input, C as TerminalPermissionPrompt, Ct as DialogClose, D as SubagentStrip, Dt as DialogTrigger, E as parseUserQuestions, Et as DialogRow, F as SkillsDialog, Ft as SelectItem, G as previewPatch, Gt as cn, H as TerminalSurface, Ht as badgeVariants, I as McpDialog, It as SelectItemText, J as Ink, K as Band, L as HostFilesDialog, Lt as SelectTrigger, M as permissionModeChoices, Mt as MenuTrigger, N as permissionModeMeta, Nt as Select, O as PermissionPrompt, Ot as Menu, P as ModelSelect, Pt as SelectContent, Q as useAffordances, Rt as SelectValue, S as TerminalQuestionPrompt, St as DialogBody, T as QuestionPrompt, Tt as DialogHeader, U as TerminalMarkdown, Ut as Button, V as skillPrompt, Vt as Badge, W as TerminalDiff, Wt as buttonVariants, X as CopyAction, Y as Row, Z as WithActions, _ as ConversationContent, _t as copyText, a as Transcript, at as commandTrigger, b as STATUS_META, bt as TooltipProvider, c as PromptTokenText, ct as usePromptAreaState, d as FileCard, dt as segmentsToPlainText, et as toolIcon, f as TerminalItemView, ft as ProgressRing, g as Conversation, gt as CopyButton, h as Loader, ht as Spinner, i as useMinuteClock, it as useTranscriptVariant, j as PermissionModeSelect, jt as MenuSeparator, k as Response, kt as MenuContent, l as Message, lt as PromptArea, m as SessionEmptyState, mt as CodeBlock, n as UsageDialog, nt as TranscriptVariantProvider, o as ToolCallCard, ot as hashtagTrigger, p as TerminalTranscript, pt as Splitter, q as Blank, r as UsageMeters, rt as useTranscriptDensity, s as Reasoning, st as mentionTrigger, t as SessionPanel, tt as TranscriptDensityProvider, u as MessageContent, ut as plainTextToSegments, v as ConversationScrollButton, vt as Tip, w as QUESTION_BEHAVIORS, wt as DialogContent, x as SessionInfoDialog, xt as Dialog, y as StatusBar, yt as TooltipContent, z as ContextDialog, zt as PortalScope } from "./SessionPanel-
|
|
2
|
-
import {
|
|
1
|
+
import { $ as isMutatingTool, A as PERMISSION_MODES, At as MenuItem, B as Composer, Bt as Input, C as TerminalPermissionPrompt, Ct as DialogClose, D as SubagentStrip, Dt as DialogTrigger, E as parseUserQuestions, Et as DialogRow, F as SkillsDialog, Ft as SelectItem, G as previewPatch, Gt as cn, H as TerminalSurface, Ht as badgeVariants, I as McpDialog, It as SelectItemText, J as Ink, K as Band, L as HostFilesDialog, Lt as SelectTrigger, M as permissionModeChoices, Mt as MenuTrigger, N as permissionModeMeta, Nt as Select, O as PermissionPrompt, Ot as Menu, P as ModelSelect, Pt as SelectContent, Q as useAffordances, Rt as SelectValue, S as TerminalQuestionPrompt, St as DialogBody, T as QuestionPrompt, Tt as DialogHeader, U as TerminalMarkdown, Ut as Button, V as skillPrompt, Vt as Badge, W as TerminalDiff, Wt as buttonVariants, X as CopyAction, Y as Row, Z as WithActions, _ as ConversationContent, _t as copyText, a as Transcript, at as commandTrigger, b as STATUS_META, bt as TooltipProvider, c as PromptTokenText, ct as usePromptAreaState, d as FileCard, dt as segmentsToPlainText, et as toolIcon, f as TerminalItemView, ft as ProgressRing, g as Conversation, gt as CopyButton, h as Loader, ht as Spinner, i as useMinuteClock, it as useTranscriptVariant, j as PermissionModeSelect, jt as MenuSeparator, k as Response, kt as MenuContent, l as Message, lt as PromptArea, m as SessionEmptyState, mt as CodeBlock, n as UsageDialog, nt as TranscriptVariantProvider, o as ToolCallCard, ot as hashtagTrigger, p as TerminalTranscript, pt as Splitter, q as Blank, r as UsageMeters, rt as useTranscriptDensity, s as Reasoning, st as mentionTrigger, t as SessionPanel, tt as TranscriptDensityProvider, u as MessageContent, ut as plainTextToSegments, v as ConversationScrollButton, vt as Tip, w as QUESTION_BEHAVIORS, wt as DialogContent, x as SessionInfoDialog, xt as Dialog, y as StatusBar, yt as TooltipContent, z as ContextDialog, zt as PortalScope } from "./SessionPanel-DVhnUwXw.mjs";
|
|
2
|
+
import { C as toolInputPreview, S as rateLimitWindowSeconds, _ as formatRateLimitWindow, b as formatTokens, c as tightestWindow, f as formatAgoPrecise, g as formatDuration, h as formatCountdown, i as meterSeverity, m as formatCost, o as modelLabel, p as formatBytes, r as meterColorClass, s as statusPresentation, t as contextSeverity, u as windowLabel, v as formatRateLimitWindowLong, x as friendlyModel, y as formatRelativeTime } from "./status-BW2xj79g.mjs";
|
|
3
3
|
import { Fragment, forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { Anchor, ArrowRight, Atom, Beaker, BellRing, Bike, Binary, Blocks, Bot, Box, Brain, Briefcase, Brush, Bug, Building2, Cable, Camera, Car, Cat, Check, ChevronDown, ChevronRight, CircleAlert, CircleSlash, CircuitBoard, Cloud, Code, Coffee, Cog, Compass, Container, Cpu, CreditCard, Database, Diamond, Dog, Dot, Eraser, Feather, Film, Flame, FlaskConical, Folder, Gamepad2, Gauge, Gem, Ghost, Gift, GitBranch, Globe, GraduationCap, Hammer, Hexagon, House, Image, Key, Landmark, Laptop, Layers, Leaf, Library, Lightbulb, Lock, Mail, Map as Map$1, MessageCircle, Mic, Monitor, Moon, Mountain, Music, Network, Newspaper, Notebook, Orbit, Package, Palette, PauseCircle, PenTool, Pencil, Phone, PiggyBank, Plane, Plug, Puzzle, Radar, Radio, Receipt, Rocket, Ruler, Satellite, Scale, Scissors, Search, SearchX, Server, Shield, Ship, ShoppingCart, Signal, Smartphone, Sparkles, Sprout, Square, Star, Sun, Table, Tag, Target, Telescope, Terminal, TestTube, Trash2, TreePine, Trophy, Truck, Umbrella, Users, Wallet, Wand, Waves, Webhook, Wifi, Wrench, X, Zap } from "lucide-react";
|
package/build/scoped.css
CHANGED
|
@@ -1684,6 +1684,10 @@
|
|
|
1684
1684
|
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
|
1685
1685
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
1686
1686
|
}
|
|
1687
|
+
.wd-root .ring {
|
|
1688
|
+
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
|
1689
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
1690
|
+
}
|
|
1687
1691
|
.wd-root .ring-2 {
|
|
1688
1692
|
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
|
1689
1693
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
//#region src/lib/format.ts
|
|
2
|
+
/**
|
|
3
|
+
* The whole payload of a `compaction` row. The wire event carries no summary because codex's
|
|
4
|
+
* `contextCompaction` item carries none, so the sentence *is* the content.
|
|
5
|
+
*
|
|
6
|
+
* It lives here, in a pure module, because the renderer (`terminal/items.tsx`) and the height book
|
|
7
|
+
* (`terminal/height.ts`) must measure and draw the same string — height.ts cannot import the
|
|
8
|
+
* `.tsx` without pulling React into a module the tests require to stay pure.
|
|
9
|
+
*/
|
|
10
|
+
const COMPACTION_TEXT = "context compacted · earlier turns summarised to fit the window";
|
|
2
11
|
function formatCost(usd) {
|
|
3
12
|
if (usd === void 0 || Number.isNaN(usd)) return "—";
|
|
4
13
|
if (usd === 0) return "$0.00";
|
|
@@ -238,6 +247,6 @@ function meterTintClass(pct) {
|
|
|
238
247
|
return pct >= 90 ? "bg-danger" : pct >= 70 ? "bg-warning" : "bg-accent";
|
|
239
248
|
}
|
|
240
249
|
//#endregion
|
|
241
|
-
export { toolInputPreview as S,
|
|
250
|
+
export { toolInputPreview as C, rateLimitWindowSeconds as S, formatRateLimitWindow as _, meterTintClass as a, formatTokens as b, tightestWindow as c, COMPACTION_TEXT as d, formatAgoPrecise as f, formatDuration as g, formatCountdown as h, meterSeverity as i, usageWindow as l, formatCost as m, currentModel as n, modelLabel as o, formatBytes as p, meterColorClass as r, statusPresentation as s, contextSeverity as t, windowLabel as u, formatRateLimitWindowLong as v, friendlyModel as x, formatRelativeTime as y };
|
|
242
251
|
|
|
243
|
-
//# sourceMappingURL=status-
|
|
252
|
+
//# sourceMappingURL=status-BW2xj79g.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-BW2xj79g.mjs","names":[],"sources":["../src/lib/format.ts","../src/lib/status.ts"],"sourcesContent":["/**\n * The whole payload of a `compaction` row. The wire event carries no summary because codex's\n * `contextCompaction` item carries none, so the sentence *is* the content.\n *\n * It lives here, in a pure module, because the renderer (`terminal/items.tsx`) and the height book\n * (`terminal/height.ts`) must measure and draw the same string — height.ts cannot import the\n * `.tsx` without pulling React into a module the tests require to stay pure.\n */\nexport const COMPACTION_TEXT = 'context compacted · earlier turns summarised to fit the window'\n\nexport function formatCost(usd: number | undefined): string {\n if (usd === undefined || Number.isNaN(usd)) {\n return '—'\n }\n if (usd === 0) {\n return '$0.00'\n }\n if (usd < 0.01) {\n return '<$0.01'\n }\n return `$${usd.toFixed(2)}`\n}\n\nexport function formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${Math.round(ms)}ms`\n }\n const s = ms / 1000\n if (s < 60) {\n return `${s.toFixed(1)}s`\n }\n const m = Math.floor(s / 60)\n return `${m}m ${Math.round(s % 60)}s`\n}\n\nexport function formatTokens(tokens: number): string {\n if (tokens >= 1_000_000) {\n return `${(tokens / 1_000_000).toFixed(1)}M`\n }\n if (tokens >= 1000) {\n return `${(tokens / 1000).toFixed(1)}k`\n }\n return String(Math.round(tokens))\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024) {\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n }\n if (bytes >= 1024) {\n return `${(bytes / 1024).toFixed(1)} KB`\n }\n return `${bytes} B`\n}\n\nexport function formatCountdown(untilEpochMs: number, now = Date.now()): string {\n const remaining = untilEpochMs - now\n if (remaining <= 0) {\n return 'now'\n }\n const minutes = Math.ceil(remaining / 60_000)\n if (minutes < 1) {\n return '<1m'\n }\n if (minutes < 60) {\n return `${minutes}m`\n }\n const days = Math.floor(minutes / (60 * 24))\n if (days >= 1) {\n return `${days}d ${Math.floor((minutes % (60 * 24)) / 60)}h`\n }\n return `${Math.floor(minutes / 60)}h ${minutes % 60}m`\n}\n\nexport function formatRelativeTime(epochMs: number | undefined, now = Date.now()): string {\n if (!epochMs) {\n return '—'\n }\n const diff = Math.max(0, now - epochMs)\n const s = Math.floor(diff / 1000)\n if (s < 60) {\n return 'just now'\n }\n const m = Math.floor(s / 60)\n if (m < 60) {\n return `${m}m ago`\n }\n const h = Math.floor(m / 60)\n if (h < 24) {\n return `${h}h ago`\n }\n const d = Math.floor(h / 24)\n return `${d}d ago`\n}\n\nexport function formatRateLimitWindow(key: string): string {\n if (key === 'five_hour') {\n return '5h'\n }\n if (key === 'seven_day') {\n return '7d'\n }\n const spaced = key.replaceAll('_', ' ')\n return key.startsWith('seven_day_') ? `7d ${spaced.slice('seven day '.length)}` : spaced\n}\n\nexport function formatRateLimitWindowLong(key: string): string {\n if (key === 'five_hour') {\n return '5-hour session'\n }\n if (key === 'seven_day') {\n return 'Weekly'\n }\n if (key === 'seven_day_oauth_apps') {\n return 'Weekly · apps'\n }\n const capitalize = (s: string) => s.replace(/\\b\\w/g, (c) => c.toUpperCase())\n if (!key.startsWith('seven_day_')) {\n return capitalize(key.replaceAll('_', ' '))\n }\n return `Weekly · ${capitalize(key.slice('seven_day_'.length).replaceAll('_', ' '))}`\n}\n\nexport function rateLimitWindowSeconds(key: string): number | undefined {\n if (key === 'five_hour') {\n return 5 * 3600\n }\n if (key.startsWith('seven_day')) {\n return 7 * 86_400\n }\n return undefined\n}\n\nexport function formatAgoPrecise(epochMs: number, now = Date.now()): string {\n const seconds = Math.max(0, Math.floor((now - epochMs) / 1000))\n if (seconds < 60) {\n return `${seconds} sec${seconds === 1 ? '' : 's'} ago`\n }\n if (seconds < 3600) {\n const minutes = Math.floor(seconds / 60)\n return `${minutes} min${minutes === 1 ? '' : 's'} ago`\n }\n const hours = Math.floor(seconds / 3600)\n return `${hours} hour${hours === 1 ? '' : 's'} ago`\n}\n\nexport function toolInputPreview(input: unknown, max = 80): string {\n if (input === null || input === undefined) {\n return ''\n }\n if (typeof input === 'object') {\n const o = input as Record<string, unknown>\n const primary = o.command ?? o.file_path ?? o.path ?? o.url ?? o.pattern ?? o.query ?? o.description\n if (typeof primary === 'string') {\n return primary.length > max ? primary.slice(0, max - 1) + '…' : primary\n }\n }\n const text = JSON.stringify(input) ?? ''\n return text.length > max ? text.slice(0, max - 1) + '…' : text\n}\n\nconst MODEL_FAMILIES: Record<string, { name: string; joiner?: string }> = {\n gpt: { name: 'GPT', joiner: '-' },\n deepseek: { name: 'DeepSeek' },\n glm: { name: 'GLM' },\n qwen: { name: 'Qwen' },\n kimi: { name: 'Kimi' },\n llama: { name: 'Llama' },\n mistral: { name: 'Mistral' },\n grok: { name: 'Grok' },\n}\n\nexport function friendlyModel(id: string | undefined): string | undefined {\n if (!id) {\n return undefined\n }\n const withoutVariant = id.split('[')[0] ?? id\n const parts = withoutVariant.toLowerCase().split('-').filter(Boolean)\n if (parts[0] === 'claude') {\n parts.shift()\n }\n const familyToken = parts.shift()\n if (!familyToken) {\n return id\n }\n const family = MODEL_FAMILIES[familyToken]\n const name =\n family?.name ??\n // OpenAI's reasoning series is lower-case by its own convention ('o3-mini'); \"O3\" reads as a different product.\n (/^o\\d+$/.test(familyToken) ? familyToken : `${familyToken.charAt(0).toUpperCase()}${familyToken.slice(1)}`)\n\n const version: string[] = []\n const words: string[] = []\n for (const part of parts) {\n if (/^\\d{8}$/.test(part)) {\n continue\n }\n if (/^\\d+(\\.\\d+)?$/.test(part)) {\n version.push(part)\n } else {\n words.push(`${part.charAt(0).toUpperCase()}${part.slice(1)}`)\n }\n }\n const versioned = version.length > 0 ? `${name}${family?.joiner ?? ' '}${version.join('.')}` : name\n return [versioned, ...words].join(' ')\n}\n","import type { ModelOption, RateLimitInfo, SessionStatus } from '@workerdeck/protocol'\n\nexport type StatusSeverity = 'none' | 'warning' | 'error'\n\nexport type StatusPresentation = {\n icon: string\n label: string\n severity: StatusSeverity\n}\n\nexport type StatusReadings = {\n status: SessionStatus\n connection?: 'live' | 'reconnecting' | 'offline'\n}\n\nconst STATUS_META: Record<SessionStatus, StatusPresentation> = {\n starting: { icon: 'loading~spin', label: 'Starting', severity: 'none' },\n running: { icon: 'loading~spin', label: 'Running', severity: 'none' },\n awaiting_approval: { icon: 'warning', label: 'Needs approval', severity: 'warning' },\n idle: { icon: 'check', label: 'Idle', severity: 'none' },\n parked: { icon: 'debug-pause', label: 'Parked', severity: 'none' },\n failed: { icon: 'error', label: 'Failed', severity: 'error' },\n closed: { icon: 'circle-slash', label: 'Closed', severity: 'none' },\n}\n\nexport function statusPresentation(vitals: StatusReadings | undefined): StatusPresentation {\n if (!vitals) {\n return { icon: 'hubot', label: 'Connecting…', severity: 'none' }\n }\n if (vitals.connection === 'offline') {\n return { icon: 'debug-disconnect', label: 'Offline', severity: 'error' }\n }\n if (vitals.connection === 'reconnecting') {\n return { icon: 'sync~spin', label: 'Reconnecting…', severity: 'warning' }\n }\n return STATUS_META[vitals.status] ?? { icon: 'hubot', label: vitals.status, severity: 'none' }\n}\n\nexport function meterSeverity(pct: number | undefined): StatusSeverity {\n if (pct === undefined) {\n return 'none'\n }\n if (pct >= 95) {\n return 'error'\n }\n if (pct >= 80) {\n return 'warning'\n }\n return 'none'\n}\n\nexport type UsageLane = 'session' | 'weekly' | 'model'\n\nexport function usageWindow(\n rateLimits: Record<string, RateLimitInfo> | undefined,\n lane: UsageLane,\n): { key: string; info: RateLimitInfo } | undefined {\n if (!rateLimits) {\n return undefined\n }\n if (lane === 'session') {\n const info = rateLimits.five_hour\n return info ? { key: 'five_hour', info } : undefined\n }\n if (lane === 'weekly') {\n const info = rateLimits.seven_day\n return info ? { key: 'seven_day', info } : undefined\n }\n const scoped = Object.fromEntries(\n Object.entries(rateLimits).filter(([key]) => key.startsWith('seven_day_') && key !== 'seven_day_oauth_apps'),\n )\n return tightestWindow(scoped)\n}\n\nexport function tightestWindow(rateLimits: Record<string, RateLimitInfo> | undefined): { key: string; info: RateLimitInfo } | undefined {\n const entries = Object.entries(rateLimits ?? {})\n if (entries.length === 0) {\n return undefined\n }\n let best: { key: string; info: RateLimitInfo } | undefined\n for (const [key, info] of entries) {\n const rank = info.status === 'rejected' ? Number.POSITIVE_INFINITY : (info.utilization ?? -1)\n const bestRank =\n best === undefined\n ? Number.NEGATIVE_INFINITY\n : best.info.status === 'rejected'\n ? Number.POSITIVE_INFINITY\n : (best.info.utilization ?? -1)\n if (rank > bestRank) {\n best = { key, info }\n }\n }\n return best\n}\n\nexport function windowLabel(key: string): string {\n if (key === 'five_hour') {\n return 'Session'\n }\n if (key === 'seven_day') {\n return 'Weekly'\n }\n const scoped = key.startsWith('seven_day_') ? key.slice('seven_day_'.length) : key\n const words = scoped.replaceAll('_', ' ')\n return words.charAt(0).toUpperCase() + words.slice(1)\n}\n\nexport type ModelReadings = { model?: string; models: readonly ModelOption[] }\n\nexport function currentModel(vitals: ModelReadings | undefined): ModelOption | undefined {\n const id = vitals?.model\n if (!id) {\n return undefined\n }\n const bare = (value: string) => value.replace(/\\[.*\\]$/, '')\n const wanted = bare(id)\n return vitals.models.find((m) => bare(m.value) === wanted || (m.resolvedModel && bare(m.resolvedModel) === wanted))\n}\n\nexport function modelLabel(vitals: ModelReadings | undefined): string {\n if (!vitals?.model) {\n return 'Default'\n }\n return currentModel(vitals)?.displayName ?? vitals.model\n}\n\nexport function contextSeverity(usage: { percentage: number } | undefined): StatusSeverity {\n return meterSeverity(usage?.percentage)\n}\n\nexport function meterColorClass(pct: number | undefined): string {\n const severity = meterSeverity(pct)\n return severity === 'error' ? 'text-danger' : severity === 'warning' ? 'text-warning' : 'text-fg-3'\n}\n\n/**\n * The fill colour for a meter bar. Deliberately a different ramp from\n * {@link meterColorClass}: a bar reads as alarming later than a number does, so the tint\n * turns at 70/90 where the text severity turns at 80/95.\n */\nexport function meterTintClass(pct: number): string {\n return pct >= 90 ? 'bg-danger' : pct >= 70 ? 'bg-warning' : 'bg-accent'\n}\n"],"mappings":"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"}
|
package/build/workspace.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Bt as Input, Gt as cn, R as EntryIcon, Ut as Button, bt as TooltipProvider, ht as Spinner, pt as Splitter, t as SessionPanel, vt as Tip } from "./SessionPanel-
|
|
2
|
-
import {
|
|
1
|
+
import { Bt as Input, Gt as cn, R as EntryIcon, Ut as Button, bt as TooltipProvider, ht as Spinner, pt as Splitter, t as SessionPanel, vt as Tip } from "./SessionPanel-DVhnUwXw.mjs";
|
|
2
|
+
import { p as formatBytes } from "./status-BW2xj79g.mjs";
|
|
3
3
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { ChevronRight, File, FileWarning, Folder, FolderOpen, PanelLeftClose, PanelLeftOpen, RefreshCw, Search, TriangleAlert, X } from "lucide-react";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workerdeck/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Styled agent-control component library for WorkerDeck: session panel, transcript with streaming, tool-call cards, permission prompts, session list, composer. Tailwind v4 + Base UI + cva. Pairs with the headless @workerdeck/react layer.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"streamdown": "^2.6.0",
|
|
40
40
|
"tailwind-merge": "^3.6.0",
|
|
41
41
|
"use-stick-to-bottom": "^1.1.6",
|
|
42
|
-
"@workerdeck/
|
|
43
|
-
"@workerdeck/
|
|
44
|
-
"@workerdeck/protocol": "1.
|
|
42
|
+
"@workerdeck/react": "1.2.0",
|
|
43
|
+
"@workerdeck/client": "1.2.0",
|
|
44
|
+
"@workerdeck/protocol": "1.2.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"monaco-editor": "^0.56.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MessageAttachment } from '@workerdeck/protocol'
|
|
2
2
|
import type { TranscriptItem } from '@workerdeck/react'
|
|
3
3
|
import { cn } from '../../lib/utils.ts'
|
|
4
|
-
import { formatCost, formatDuration, formatRelativeTime } from '../../lib/format.ts'
|
|
4
|
+
import { COMPACTION_TEXT, formatCost, formatDuration, formatRelativeTime } from '../../lib/format.ts'
|
|
5
5
|
import { FileCard } from './FileCard.tsx'
|
|
6
6
|
import { Message, MessageContent } from './Message.tsx'
|
|
7
7
|
import { PromptTokenText } from './PromptTokenText.tsx'
|
|
@@ -99,6 +99,9 @@ export function TranscriptItemView({
|
|
|
99
99
|
case 'notice': {
|
|
100
100
|
return <NoticeRow item={item} />
|
|
101
101
|
}
|
|
102
|
+
case 'compaction': {
|
|
103
|
+
return <CompactionRow />
|
|
104
|
+
}
|
|
102
105
|
case 'file_delivered': {
|
|
103
106
|
return <FileCard item={item} href={fileUrl?.(item.path)} />
|
|
104
107
|
}
|
|
@@ -108,6 +111,21 @@ export function TranscriptItemView({
|
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
|
|
114
|
+
/**
|
|
115
|
+
* The cards spelling of a compaction boundary — a rule with the label through it, the same shape
|
|
116
|
+
* `RecapRow` uses below, because both are boundaries rather than messages. The terminal spelling is
|
|
117
|
+
* `terminal/items.tsx`'s `CompactionRow`, one grid row, and the two share `COMPACTION_TEXT`.
|
|
118
|
+
*/
|
|
119
|
+
function CompactionRow() {
|
|
120
|
+
return (
|
|
121
|
+
<div data-slot="compaction" className="flex items-center gap-2 py-1">
|
|
122
|
+
<div className="h-px flex-1 bg-border" />
|
|
123
|
+
<span className="font-mono text-label text-fg-3">≡ {COMPACTION_TEXT}</span>
|
|
124
|
+
<div className="h-px flex-1 bg-border" />
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
111
129
|
export function RecapRow({ line, since, terminal }: { line: string; since?: number; terminal?: boolean }) {
|
|
112
130
|
const away = since === undefined ? undefined : formatRelativeTime(since)
|
|
113
131
|
const text = away ? `${line} · last here ${away}` : line
|
|
@@ -3,7 +3,7 @@ import type { TranscriptItem, TranscriptState } from '@workerdeck/react'
|
|
|
3
3
|
import { cn } from '../../lib/utils.ts'
|
|
4
4
|
import type { TerminalAffordances } from './affordances.tsx'
|
|
5
5
|
import { OpenSubagentAction, WithActions } from './affordances.tsx'
|
|
6
|
-
import { AssistantRow, FileRow, NoticeRow, ToolRunRow, ThinkingRow, ToolRow, TurnResultRow, UserRow, WorkingRow } from './items.tsx'
|
|
6
|
+
import { AssistantRow, CompactionRow, FileRow, NoticeRow, ToolRunRow, ThinkingRow, ToolRow, TurnResultRow, UserRow, WorkingRow } from './items.tsx'
|
|
7
7
|
import { blockNeedsBlank, taskChildItems, terminalBlocks, type TaskBlock } from './blocks.ts'
|
|
8
8
|
import { usePulse } from '../agent/pulse.tsx'
|
|
9
9
|
import { Pressable, useRevealOnOpen } from './press.tsx'
|
|
@@ -41,6 +41,9 @@ export function TerminalItemView({ item, fileUrl }: { item: TranscriptItem; file
|
|
|
41
41
|
case 'notice': {
|
|
42
42
|
return <NoticeRow item={item} />
|
|
43
43
|
}
|
|
44
|
+
case 'compaction': {
|
|
45
|
+
return <CompactionRow />
|
|
46
|
+
}
|
|
44
47
|
case 'file_delivered': {
|
|
45
48
|
return <FileRow item={item} href={fileUrl?.(item.path)} />
|
|
46
49
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FilePatch, PatchHunk } from '@workerdeck/protocol'
|
|
2
2
|
import type { TranscriptItem } from '@workerdeck/react'
|
|
3
|
-
import { formatBytes, formatCost, formatDuration, toolInputPreview } from '../../lib/format.ts'
|
|
3
|
+
import { COMPACTION_TEXT, formatBytes, formatCost, formatDuration, toolInputPreview } from '../../lib/format.ts'
|
|
4
4
|
import { taskChildItems, type TerminalBlock, type ToolCallItem } from './blocks.ts'
|
|
5
5
|
import { IMAGE_BOX_LINES } from './image-box.ts'
|
|
6
6
|
import { collapsedResult } from './result-preview.ts'
|
|
@@ -622,6 +622,11 @@ export function itemHeight(item: TranscriptItem, m: CellMetrics): ComputedHeight
|
|
|
622
622
|
case 'notice': {
|
|
623
623
|
return rowH(item.text, m, { extraPx })
|
|
624
624
|
}
|
|
625
|
+
// Measured against the exported constant, not a copy of the sentence: the two must not drift,
|
|
626
|
+
// and a mismatch here is a scrubber that points a few pixels off for the rest of the session.
|
|
627
|
+
case 'compaction': {
|
|
628
|
+
return rowH(COMPACTION_TEXT, m, { extraPx })
|
|
629
|
+
}
|
|
625
630
|
case 'file_delivered': {
|
|
626
631
|
const text = `${item.path} · ${formatBytes(item.bytes)}` + (item.description ? ` · ${item.description}` : '')
|
|
627
632
|
return rowH(text, m, { extraPx })
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
import type { TranscriptItem } from '@workerdeck/react'
|
|
3
|
-
import { formatBytes, formatCost, formatDuration, toolInputPreview } from '../../lib/format.ts'
|
|
3
|
+
import { COMPACTION_TEXT, formatBytes, formatCost, formatDuration, toolInputPreview } from '../../lib/format.ts'
|
|
4
4
|
import { isMutatingTool } from '../../lib/tool-icon.ts'
|
|
5
5
|
import { usePulse } from '../agent/pulse.tsx'
|
|
6
6
|
import { BookmarkAction, CopyAction, WithActions } from './affordances.tsx'
|
|
@@ -278,6 +278,19 @@ export function TurnResultRow({ item }: { item: Extract<TranscriptItem, { kind:
|
|
|
278
278
|
)
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
/**
|
|
282
|
+
* One row, deliberately. A rule with a centred label reads better and costs the height book a
|
|
283
|
+
* second measurement for a row that carries no content — and the point here is only that the
|
|
284
|
+
* boundary is *visible*, so the ring dropping 90% → 15% stops reading as a bug.
|
|
285
|
+
*/
|
|
286
|
+
export function CompactionRow() {
|
|
287
|
+
return (
|
|
288
|
+
<Row glyph="≡" glyphTone="yellow" tone="faint">
|
|
289
|
+
{COMPACTION_TEXT}
|
|
290
|
+
</Row>
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
281
294
|
export function NoticeRow({ item }: { item: Extract<TranscriptItem, { kind: 'notice' }> }) {
|
|
282
295
|
const error = item.level === 'error'
|
|
283
296
|
return (
|
package/src/lib/format.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The whole payload of a `compaction` row. The wire event carries no summary because codex's
|
|
3
|
+
* `contextCompaction` item carries none, so the sentence *is* the content.
|
|
4
|
+
*
|
|
5
|
+
* It lives here, in a pure module, because the renderer (`terminal/items.tsx`) and the height book
|
|
6
|
+
* (`terminal/height.ts`) must measure and draw the same string — height.ts cannot import the
|
|
7
|
+
* `.tsx` without pulling React into a module the tests require to stay pure.
|
|
8
|
+
*/
|
|
9
|
+
export const COMPACTION_TEXT = 'context compacted · earlier turns summarised to fit the window'
|
|
10
|
+
|
|
1
11
|
export function formatCost(usd: number | undefined): string {
|
|
2
12
|
if (usd === undefined || Number.isNaN(usd)) {
|
|
3
13
|
return '—'
|