@2lab.ai/ui 0.0.0-stage → 0.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.
Files changed (60) hide show
  1. package/README.md +55 -2
  2. package/dist/agent-inbox.js +1061 -0
  3. package/dist/agent-inbox.js.map +1 -0
  4. package/dist/agent-queue.js +1610 -0
  5. package/dist/agent-queue.js.map +1 -0
  6. package/dist/agent-run.js +859 -0
  7. package/dist/agent-run.js.map +1 -0
  8. package/dist/ai-answer.js +687 -0
  9. package/dist/ai-answer.js.map +1 -0
  10. package/dist/components/agent-inbox/agent-inbox.d.ts +158 -0
  11. package/dist/components/agent-inbox/index.d.ts +2 -0
  12. package/dist/components/agent-inbox/styles.d.ts +6 -0
  13. package/dist/components/agent-queue/agent-queue.d.ts +41 -0
  14. package/dist/components/agent-queue/index.d.ts +6 -0
  15. package/dist/components/agent-queue/logic.d.ts +261 -0
  16. package/dist/components/agent-queue/strings.d.ts +473 -0
  17. package/dist/components/agent-queue/styles.d.ts +8 -0
  18. package/dist/components/agent-run/agent-run.d.ts +273 -0
  19. package/dist/components/agent-run/index.d.ts +2 -0
  20. package/dist/components/agent-run/styles.d.ts +6 -0
  21. package/dist/components/ai-answer/ai-answer.d.ts +92 -0
  22. package/dist/components/ai-answer/index.d.ts +2 -0
  23. package/dist/components/ai-answer/styles.d.ts +2 -0
  24. package/dist/components/context-window/context-window.d.ts +156 -0
  25. package/dist/components/context-window/index.d.ts +2 -0
  26. package/dist/components/context-window/styles.d.ts +2 -0
  27. package/dist/components/memory-panel/index.d.ts +2 -0
  28. package/dist/components/memory-panel/memory-panel.d.ts +101 -0
  29. package/dist/components/memory-panel/styles.d.ts +2 -0
  30. package/dist/components/model-picker/index.d.ts +2 -0
  31. package/dist/components/model-picker/model-picker.d.ts +136 -0
  32. package/dist/components/model-picker/morph.d.ts +9 -0
  33. package/dist/components/model-picker/styles.d.ts +6 -0
  34. package/dist/components/tool-call-card/index.d.ts +7 -0
  35. package/dist/components/tool-call-card/logic.d.ts +87 -0
  36. package/dist/components/tool-call-card/strings.d.ts +94 -0
  37. package/dist/components/tool-call-card/styles.d.ts +5 -0
  38. package/dist/components/tool-call-card/tool-call-card.d.ts +49 -0
  39. package/dist/components/tool-call-card/tool-call-group.d.ts +32 -0
  40. package/dist/context-window.js +384 -0
  41. package/dist/context-window.js.map +1 -0
  42. package/dist/core/element.d.ts +41 -0
  43. package/dist/core/format.d.ts +14 -0
  44. package/dist/core/html.d.ts +10 -0
  45. package/dist/core/tokens.d.ts +14 -0
  46. package/dist/format-BBGWXPzk.js +51 -0
  47. package/dist/format-BBGWXPzk.js.map +1 -0
  48. package/dist/html-elD6Bb_u.js +175 -0
  49. package/dist/html-elD6Bb_u.js.map +1 -0
  50. package/dist/index.d.ts +16 -0
  51. package/dist/index.js +129 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/memory-panel.js +1255 -0
  54. package/dist/memory-panel.js.map +1 -0
  55. package/dist/model-picker.js +858 -0
  56. package/dist/model-picker.js.map +1 -0
  57. package/dist/tokens.css +4 -0
  58. package/dist/tool-call-card.js +1046 -0
  59. package/dist/tool-call-card.js.map +1 -0
  60. package/package.json +79 -4
@@ -0,0 +1,101 @@
1
+ import { UiElement } from '../../core/element.js';
2
+ /** One memory store (a file the agent reads at the start of every session). */
3
+ export interface MemoryStore {
4
+ /** Stable id, sent back as `store` in every event. */
5
+ id: string;
6
+ /** Group title and chip label (e.g. `Agent notes`). */
7
+ label: string;
8
+ /** File or source shown next to the label (e.g. `MEMORY.md`). */
9
+ file?: string;
10
+ /** Character budget of the store — drives the usage bar. 0 / absent: no bar, just the count. */
11
+ limit: number;
12
+ /** The entries, in store order. Entry `#n` is `entries[n - 1]`. */
13
+ entries: string[];
14
+ /** Longest single entry. Longer entries are flagged; the editors count against it. */
15
+ perEntryLimit?: number;
16
+ /** What the store puts between entries when it counts its usage (e.g. `'\n§\n'`). Default `''`. */
17
+ separator?: string;
18
+ }
19
+ export interface MemoryPanelData {
20
+ stores: MemoryStore[];
21
+ /** `true` hides every write action (edit, delete, add, clear); a string also says why, above the list. */
22
+ readOnly?: boolean | string;
23
+ /** Heading with a rolling entry count. Default: none (the host page titles the panel). */
24
+ title?: string;
25
+ }
26
+ export interface MemoryAddDetail {
27
+ store: string;
28
+ /** As typed (the host decides whether to trim). */
29
+ text: string;
30
+ }
31
+ export interface MemoryEditDetail {
32
+ store: string;
33
+ index: number;
34
+ /** As typed. A change that only adds or removes surrounding whitespace is never sent. */
35
+ text: string;
36
+ /** The entry text the edit replaces — check it against your copy before writing. */
37
+ previous: string;
38
+ }
39
+ export interface MemoryDeleteDetail {
40
+ store: string;
41
+ index: number;
42
+ text: string;
43
+ }
44
+ /** The entry Undo brings back: `index` is where it was, `text` exactly what was deleted. */
45
+ export type MemoryRestoreDetail = MemoryDeleteDetail;
46
+ export interface MemoryClearDetail {
47
+ store: string;
48
+ }
49
+ /** ok < 80% ≤ warn (amber) < 95% ≤ high (red bar) < 100% ≤ full (red text). */
50
+ export type UsageLevel = 'ok' | 'warn' | 'high' | 'full';
51
+ export declare const UNDO_MS = 5000;
52
+ export declare const FOLD_MS = 320;
53
+ /** Characters a store uses: its entries joined with its separator (what the store itself counts). */
54
+ export declare function storeUsed(entries: readonly string[], separator?: string): number;
55
+ /** The usage band against the store's limit. */
56
+ export declare function usageLevel(used: number, limit: number): UsageLevel;
57
+ /** The store's usage once `text` is added — trimmed, as the store saves it, plus one separator. */
58
+ export declare function usageAfterAdd(entries: readonly string[], separator: string, text: string): number;
59
+ /** An edit is "the same" when both sides match once trimmed — the store trims what it saves, so no write is needed. */
60
+ export declare function isSameEdit(next: string, previous: string): boolean;
61
+ /** `text` cut to `max` characters with an ellipsis (spoken names, the undo sentence). */
62
+ export declare function shortText(text: string, max?: number): string;
63
+ /** Search: the trimmed query, case-insensitive, inside the entry — or inside the store's label / file name. */
64
+ export declare function entryMatches(text: string, q: string, store?: {
65
+ label?: string;
66
+ file?: string;
67
+ }): boolean;
68
+ /** Escaped `text` with each case-insensitive occurrence of the trimmed query wrapped in `<mark>`. */
69
+ export declare function highlight(text: string, q: string): string;
70
+ /**
71
+ * Stable row keys: a content hash plus the occurrence. A row keeps its key when rows above it are deleted or
72
+ * restored, so focus stays on the same entry when the host's next `data` shifts the indices.
73
+ */
74
+ export declare function entryKeys(entries: readonly string[]): string[];
75
+ /**
76
+ * `<ui-memory-panel>` — the agent's personal memory stores in one panel: store chips (a radiogroup), search with
77
+ * highlight, one group per store with its usage bar, in-place edit, a delete that folds away at once with a 5 s
78
+ * Undo on the tray, add per store, a two-press "Clear all". Every write leaves as a cancelable `ui-*` event and
79
+ * shows optimistically until the host's next `data`, which is always the truth.
80
+ */
81
+ export declare class UiMemoryPanel extends UiElement<MemoryPanelData> {
82
+ #private;
83
+ static styles: string[];
84
+ static get observedAttributes(): string[];
85
+ constructor();
86
+ get data(): MemoryPanelData | undefined;
87
+ /** Setting data ends every optimistic change: the panel shows exactly the stores you pass. */
88
+ set data(value: MemoryPanelData | undefined);
89
+ attributeChangedCallback(): void;
90
+ connectedCallback(): void;
91
+ disconnectedCallback(): void;
92
+ /** The static chrome: chips, search, tray. The groups are painted into `.mm-list`. */
93
+ protected render(): string;
94
+ /** Renders are deferred while a deleted row folds away, so the host's answer never cuts the fold short. */
95
+ protected update(): void;
96
+ }
97
+ declare global {
98
+ interface HTMLElementTagNameMap {
99
+ 'ui-memory-panel': UiMemoryPanel;
100
+ }
101
+ }
@@ -0,0 +1,2 @@
1
+ /** Ported from the soma-work dashboard settings › 기억 › 개인 메모리 (.prd/affectui/re-memory-panel.md D1–D17, `.mm-*`). */
2
+ export declare const styles = "\n:host { --mm-fold: 320ms; --mm-warn: var(--yellow); --mm-danger-bg: color-mix(in srgb, var(--red) 11%, transparent);\n --mm-mark: color-mix(in srgb, var(--st-wait) 32%, transparent); --mm-well: var(--surface-raised); }\n.mm { container: mm / inline-size; position: relative; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--text-secondary); font-size: 13px; }\n.mm h3, .mm p { margin: 0; }\n.mm ol { margin: 0; padding: 0; }\n.mm :is(button, input, textarea):focus-visible { outline: 2px solid var(--border-focus); outline-offset: 1px; }\n.mm-head { display: flex; align-items: center; gap: 8px; padding: 14px 16px 2px; }\n.mm-title { font-size: 13.5px; font-weight: 600; color: var(--text); }\n.mm-count { min-width: 20px; height: 20px; padding: 0 6px; border-radius: 5px; background: var(--mm-well); font-size: 12px; font-weight: 500; line-height: 20px; text-align: center; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }\n.mm-count:empty { display: none; }\n\n/* filters: store chips (radiogroup) \u00B7 search covering the chips */\n.mm-filters { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 4px; padding: 10px 10px 10px 14px; border-bottom: 1px solid var(--border); }\n.mm-chips, .mm-search { grid-area: 1 / 1; transition: opacity 220ms ease, filter 220ms ease; }\n.mm-chips { display: flex; gap: 4px; min-width: 0; margin: -3px -4px; padding: 3px 4px; overflow-x: auto; scrollbar-width: none; -webkit-mask-image: linear-gradient(90deg, black calc(100% - 14px), transparent); mask-image: linear-gradient(90deg, black calc(100% - 14px), transparent); }\n.mm-chips::-webkit-scrollbar { display: none; }\n.mm-chip { display: inline-flex; flex: none; align-items: center; gap: 5px; height: 28px; padding: 0 9px; font-size: 12.5px; color: var(--text-secondary); background: none; border: 0; border-radius: 8px; white-space: nowrap; transition: background-color var(--dur-press) ease, color var(--dur-press) ease, box-shadow var(--dur-press) ease; }\n.mm-chip[aria-checked=\"true\"] { background: var(--mm-well); color: var(--text); font-weight: 600; box-shadow: inset 0 0 0 1px var(--border); }\n.mm-chip-n { font-size: 11.5px; font-weight: 400; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }\n.mm-search { display: flex; align-items: center; gap: 6px; min-width: 0; height: 30px; padding: 0 2px 0 9px; border-radius: 8px; background: var(--mm-well); color: var(--text-tertiary); opacity: 0; visibility: hidden; }\n.mm-search svg { flex: none; }\n.mm-search input { flex: 1; min-width: 0; height: 28px; padding: 0; font-size: 13px; color: var(--text); background: none; border: 0; outline: none; -webkit-appearance: none; appearance: none; }\n.mm-search input::placeholder { color: var(--text-tertiary); }\n.mm-search input::-webkit-search-cancel-button { display: none; }\n.mm .mm-search input:focus-visible { outline: none; }\n.mm-search:focus-within { box-shadow: inset 0 0 0 1px var(--border-focus); }\n.mm-filters[data-searching] .mm-chips { opacity: 0; filter: blur(2px); }\n.mm-filters[data-searching] .mm-search { grid-area: 1 / 1 / 2 / 3; opacity: 1; visibility: visible; }\n.mm-filters[data-searching] .mm-search-open { opacity: 0; visibility: hidden; }\n.mm-icon { display: grid; flex: none; place-items: center; width: 28px; height: 28px; padding: 0; font-size: 16px; line-height: 1; color: var(--text-tertiary); background: none; border: 0; border-radius: 8px; transition: background-color var(--dur-press) ease, color var(--dur-press) ease, opacity 200ms ease; }\n.mm-search-open { grid-area: 1 / 2; }\n.mm-note { padding: 9px 16px; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--text) 3%, transparent); font-size: 12.5px; color: var(--text-secondary); }\n\n/* groups \u2014 one per store; folds are grid rows 0fr \u2194 1fr */\n.mm-list { padding: 2px 8px 6px; }\n.mm-fold { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows var(--mm-fold) var(--ease-out), opacity var(--dur-fade) ease; }\n.mm-fold[data-open] { grid-template-rows: 1fr; opacity: 1; transition: grid-template-rows var(--mm-fold) var(--ease-out), opacity 240ms ease 60ms; }\n.mm-fold[data-leaving] { transition: grid-template-rows var(--mm-fold) var(--ease-out), opacity 200ms ease, filter 200ms ease; filter: blur(2px); }\n.mm-clip { min-height: 0; overflow: hidden; }\n.mm-ghead { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 4px 16px; padding: 12px 8px 4px; }\n.mm-ghead h3 { min-width: 0; font-size: 13px; font-weight: 600; color: var(--text); overflow-wrap: anywhere; }\n.mm-file { margin-left: 4px; font-size: 12px; font-weight: 400; color: var(--text-tertiary); }\n.mm-usage { display: flex; flex: 0 1 280px; align-items: center; gap: 8px; min-width: 0; font-size: 12px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }\n.mm-bar { position: relative; flex: 1; min-width: 48px; height: 6px; border-radius: 3px; background: var(--ib-pill); overflow: hidden; }\n.mm-bar > i { position: absolute; inset: 0 auto 0 0; border-radius: inherit; background: var(--st-done); }\n.mm-usage[data-level=\"warn\"] .mm-bar > i { background: var(--st-wait); }\n.mm-usage[data-level=\"high\"] .mm-bar > i, .mm-usage[data-level=\"full\"] .mm-bar > i { background: var(--st-fail); }\n.mm-usage[data-level=\"warn\"], .mm-usage[data-level=\"high\"], .mm-over { color: var(--mm-warn); }\n.mm-usage[data-level=\"full\"] { color: var(--red); }\n.mm-uval { flex: none; white-space: nowrap; }\n.mm-items { list-style: none; }\n.mm-row { padding: 6px 8px 5px; border-radius: 8px; transition: background-color var(--dur-press) ease; }\n.mm-row:hover, .mm-row:focus-within, .mm-row[data-editing] { background: var(--mm-well); }\n.mm-text { font-size: 13px; line-height: 20px; color: var(--text); white-space: pre-wrap; overflow-wrap: anywhere; }\n.mm-text mark { border-radius: 3px; background: var(--mm-mark); box-shadow: 0 0 0 1px var(--mm-mark); color: inherit; }\n.mm-meta { display: flex; align-items: center; gap: 6px; min-height: 26px; font-size: 12px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }\n.mm-src { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n.mm-acts { display: inline-flex; flex: none; align-items: center; gap: 2px; margin: -1px -4px -1px auto; opacity: 0; transition: opacity 180ms ease; }\n.mm-row:hover .mm-acts, .mm-row:focus-within .mm-acts, .mm-acts.is-on { opacity: 1; }\n.mm-act { height: 26px; padding: 0 9px; font-size: 12.5px; font-weight: 500; color: var(--text-secondary); background: none; border: 0; border-radius: 8px; white-space: nowrap; transition: background-color var(--dur-press) ease, color var(--dur-press) ease, opacity var(--dur-press) ease; }\n.mm-act-main { background: var(--text); color: var(--surface); }\n.mm-act:disabled { opacity: 0.4; cursor: default; }\n.mm textarea.mm-edit { display: block; width: 100%; min-height: 20px; margin: 0; padding: 0; font-size: 13px; line-height: 20px; color: var(--text); background: none; border: 0; border-radius: 0; resize: none; overflow: hidden; }\n.mm textarea.mm-edit:focus-visible { outline: none; }\n.mm-row[data-editing]:focus-within, .mm-new:focus-within { box-shadow: inset 0 0 0 1px var(--border-focus); }\n.mm-counter { font-size: 11.5px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }\n.mm-counter.is-over { color: var(--red); }\n.mm-empty { padding: 4px 8px 8px; font-size: 12.5px; color: var(--text-tertiary); }\n.mm-nomatch { padding: 18px 8px 12px; text-align: center; overflow-wrap: anywhere; }\n/* 3px of room so a focus ring is not clipped by the folding parent */\n.mm-addline { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 3px 3px 8px; }\n.mm-add { display: flex; align-items: center; gap: 6px; min-width: 0; height: 32px; padding: 0 8px; font-size: 13px; color: var(--text-tertiary); background: none; border: 0; border-radius: 8px; text-align: left; transition: background-color var(--dur-press) ease, color var(--dur-press) ease; }\n.mm-clear { flex: none; height: 28px; padding: 0 8px; font-size: 12px; color: var(--text-tertiary); background: none; border: 0; border-radius: 8px; white-space: nowrap; transition: background-color var(--dur-press) ease, color var(--dur-press) ease, box-shadow var(--dur-press) ease; }\n.mm-clear[data-armed] { background: var(--mm-danger-bg); color: var(--red); font-weight: 600; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--red) 40%, transparent); }\n.mm-new { margin: 3px 3px 8px; padding: 8px 8px 6px; border-radius: 8px; background: var(--mm-well); }\n.mm-label { display: block; margin-bottom: 4px; font-size: 12px; color: var(--text-tertiary); }\n.mm-line { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; margin-top: 6px; }\n\n/* tray: caption (cross-fades) \u00B7 polite status \u00B7 Undo */\n.mm-foot { display: flex; align-items: center; gap: 8px; min-height: 40px; padding: 4px 8px 4px 16px; border-top: 1px solid var(--border); border-radius: 0 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px); background: var(--bg); }\n.mm-caption { display: grid; flex: 1; min-width: 0; font-size: 12.5px; color: var(--text-tertiary); white-space: nowrap; font-variant-numeric: tabular-nums; }\n.mm-caption b { font-weight: 600; color: var(--text); }\n.mm-swap { grid-area: 1 / 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; animation: mm-swap-in 320ms ease both; }\n.mm-swap[data-out] { animation: mm-swap-out 220ms ease both; }\n.mm-swap.is-warn { color: var(--mm-warn); }\n.mm-swap.is-fail { color: var(--red); }\n.mm-forgot { display: flex; min-width: 0; }\n.mm-flat { flex: none; white-space: pre; }\n.mm-quote { min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n.mm-undo { display: inline-flex; flex: none; align-items: center; height: 28px; padding: 0 10px; font-size: 12.5px; font-weight: 600; color: var(--text); background: var(--surface); border: 1px solid var(--border); border-radius: 8px; animation: mm-swap-in 300ms ease both; transition: border-color var(--dur-press) ease; }\n.mm-undo[hidden] { display: none; }\n@keyframes mm-swap-in { from { opacity: 0; filter: blur(2px); } }\n@keyframes mm-swap-out { to { opacity: 0; filter: blur(2px); } }\n@media (hover: hover) and (pointer: fine) {\n .mm-chip:hover, .mm-icon:hover, .mm-add:hover { background: var(--mm-well); color: var(--text); }\n .mm-act:hover { background: var(--surface); color: var(--text); }\n .mm-act-main:hover { background: var(--text); color: var(--surface); opacity: 0.86; }\n .mm-act-del:hover, .mm-clear:hover { background: var(--mm-danger-bg); color: var(--red); }\n .mm-undo:hover { border-color: var(--accent); }\n}\n@container mm (max-width: 480px) { .mm-cap { display: none; } .mm-usage { flex-basis: 100%; } }\n@container mm (max-width: 340px) { .mm-chip-n { display: none; } .mm-chip { padding: 0 7px; } }\n@media (pointer: coarse) {\n .mm-acts { opacity: 1; }\n .mm-act, .mm-chip, .mm-clear, .mm-add, .mm-undo { min-height: 44px; }\n .mm-icon { width: 44px; height: 44px; }\n .mm-search { height: 44px; }\n .mm-search input { height: 42px; font-size: 16px; }\n .mm textarea.mm-edit { font-size: 16px; line-height: 22px; }\n .mm-meta { min-height: 44px; }\n}\n";
@@ -0,0 +1,2 @@
1
+ export type { AnchorSpot, ModelMenuSide, ModelMeters, ModelPickerChange, ModelPickerData, ModelPickerValue, ModelPrice, PickerModel, } from './model-picker.js';
2
+ export { anchorMoved, anchorSpot, fmtModelPrice, fmtModelTokens, MODEL_EFFORT_LEVELS, meterSegments, modelEffortLabel, modelEffortLevels, modelGlyph, modelMeters, placeModelMenu, UiModelPicker, validModelPrice, } from './model-picker.js';
@@ -0,0 +1,136 @@
1
+ import { UiElement } from '../../core/element.js';
2
+ /** USD per million tokens. */
3
+ export interface ModelPrice {
4
+ input: number;
5
+ output: number;
6
+ }
7
+ /** One model the session can switch to. */
8
+ export interface PickerModel {
9
+ /** What `ui-change` carries back (the host's switch key, e.g. `opus[1m]` or a full model id). */
10
+ id: string;
11
+ /** Display name, e.g. `Opus 5.5 (1M)`. */
12
+ label: string;
13
+ /** Compact name for the description line and the avatar letter (defaults: id / label). */
14
+ short?: string;
15
+ /** Context window in tokens. */
16
+ window?: number;
17
+ /** The model's own auto-compact threshold in tokens. */
18
+ autoCompactAt?: number;
19
+ /** Price per million tokens; `null` / missing = unknown (never guessed). */
20
+ price?: ModelPrice | null;
21
+ /** Effort levels this model runs, in order. Missing = the five standard levels; `[]` = no effort control. */
22
+ effortLevels?: string[];
23
+ /** Override the 5-segment meters (0–5, 0 draws "—"). By default they are measured against the largest in the list. */
24
+ bars?: {
25
+ window?: number;
26
+ compaction?: number;
27
+ cost?: number;
28
+ };
29
+ }
30
+ export interface ModelPickerValue {
31
+ /** The session's model id. When it is not in `models` it is shown as a first, unselectable row. */
32
+ model: string;
33
+ /** The effort level that was set. */
34
+ effort?: string;
35
+ /** The level that actually runs when the model caps the set one (e.g. set Max → runs High). */
36
+ effectiveEffort?: string;
37
+ }
38
+ export interface ModelPickerData {
39
+ models: PickerModel[];
40
+ value: ModelPickerValue;
41
+ /** Tokens in the session's context now: a row whose auto-compact point is below it says it switches after compaction. */
42
+ contextTokens?: number;
43
+ /** `true` = read-only; a string = read-only with that reason (tooltip + description). */
44
+ disabled?: boolean | string;
45
+ /** A write in flight. `model`: rows locked (a string replaces the "Switching model…" line); `effort`: levels locked. */
46
+ busy?: {
47
+ model?: boolean | string;
48
+ effort?: boolean;
49
+ };
50
+ /** Last write failed — announced with role="alert" (inside the open menu, or under the trigger). */
51
+ error?: string;
52
+ }
53
+ /** `ui-change` detail. */
54
+ export interface ModelPickerChange {
55
+ model: string;
56
+ effort?: string;
57
+ /** Which of the two the person changed. */
58
+ changed: 'model' | 'effort';
59
+ }
60
+ export type ModelMenuSide = 'top' | 'bottom';
61
+ export type ModelMeters = [window: number, compaction: number, cost: number];
62
+ /** The standard effort levels, in order (the dashboard's MODEL_EFFORT_LEVELS). */
63
+ export declare const MODEL_EFFORT_LEVELS: string[];
64
+ /** Filled segments of a 5-segment meter: `v` against the largest of the list, at least 1; 0 when unknown. */
65
+ export declare function meterSegments(v: unknown, max: unknown): number;
66
+ /** A price both of whose numbers are real, else null (unknown stays unknown). */
67
+ export declare function validModelPrice(p: ModelPrice | null | undefined): ModelPrice | null;
68
+ /** Window · compaction · cost meters per model: the host's `bars`, else against the list's largest value. */
69
+ export declare function modelMeters(models: PickerModel[]): ModelMeters[];
70
+ /** Token counts the way the dashboard's model menu shows them: 950 · 450.0K · 1.0M. */
71
+ export declare function fmtModelTokens(n: unknown): string;
72
+ /** Dollars, whole when the target is whole: $5, $2.50 (a rolling value keeps its target's form). */
73
+ export declare function fmtModelPrice(v: unknown, ref?: unknown): string;
74
+ /** The effort levels a model runs: its own list (deduped), the standard five when it has none, [] = no control. */
75
+ export declare function modelEffortLevels(model?: PickerModel | null): string[];
76
+ export declare function modelEffortLabel(level?: string): string;
77
+ /** The avatar letter: first character of the short name (or label), upper-cased. */
78
+ export declare function modelGlyph(m: {
79
+ short?: string;
80
+ label?: string;
81
+ id?: string;
82
+ }): string;
83
+ /**
84
+ * Where the menu goes (viewport px): under (`bottom`) or over (`top`) the trigger, flipped when that side
85
+ * has no room and the other has more, left edge on the trigger's, kept 12px inside the viewport. When the
86
+ * chosen side is too short for the whole menu it gets that side's height and scrolls (`maxHeight`), so the
87
+ * trigger stays visible; only when even that is under 180px does it slide over the trigger.
88
+ */
89
+ export declare function placeModelMenu(r: {
90
+ top: number;
91
+ bottom: number;
92
+ left: number;
93
+ }, w: number, h: number, vw: number, vh: number, side?: ModelMenuSide): {
94
+ left: number;
95
+ top: number;
96
+ side: ModelMenuSide;
97
+ maxHeight: number;
98
+ };
99
+ export interface AnchorSpot {
100
+ left: number;
101
+ top: number;
102
+ }
103
+ /**
104
+ * The trigger's place in its layout: its viewport position plus the scroll offsets of every ancestor up to
105
+ * (and including) the first `position: fixed` one, crossing shadow roots. Scrolling leaves it unchanged —
106
+ * also a page scroll under a fixed panel — so only a layout move (a card that changed column) changes it.
107
+ */
108
+ export declare function anchorSpot(el: Element): AnchorSpot;
109
+ /** The anchor moved more than 2px in its layout. */
110
+ export declare function anchorMoved(at: AnchorSpot | null, now: AnchorSpot | null): boolean;
111
+ /**
112
+ * `<ui-model-picker>` — one trigger (avatar · model · effort · chevron) that opens the session's model menu:
113
+ * every model with its window, auto-compact point and cost as 5-segment meters, the effort levels that model
114
+ * runs, and a tray with the price. Picking a model or a level sends a cancelable `ui-change`.
115
+ */
116
+ export declare class UiModelPicker extends UiElement<ModelPickerData> {
117
+ #private;
118
+ static styles: string[];
119
+ static get observedAttributes(): string[];
120
+ constructor();
121
+ /** Whether the menu is open. Setting it opens/closes without moving focus and without `ui-open-change`. */
122
+ get open(): boolean;
123
+ set open(v: boolean);
124
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
125
+ connectedCallback(): void;
126
+ disconnectedCallback(): void;
127
+ /** Patch the live tree instead of replacing it: the open/close transition, the thumb and focus survive. */
128
+ protected update(): void;
129
+ protected render(): string;
130
+ protected updated(): void;
131
+ }
132
+ declare global {
133
+ interface HTMLElementTagNameMap {
134
+ 'ui-model-picker': UiModelPicker;
135
+ }
136
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Bring a live subtree in line with freshly rendered markup, keeping every node whose place did not change —
3
+ * so focus, a running transition (the menu's open/close, the effort thumb) and live-region text nodes survive
4
+ * a re-render. Ported from the soma-work dashboard's `morphInnerHtml` (spec 12).
5
+ *
6
+ * Children pair up in order. A node with `data-mk` pairs only with the node of the same key (a row that stands
7
+ * for one model never turns into another model's row); unkeyed nodes pair by tag + id + first class.
8
+ */
9
+ export declare function morphInto(el: Element, html: string): void;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Ported from the soma-work dashboard ("MODEL · EFFORT MENU", .prd/affectui/re-model-picker.md D1, D2, D13–D15).
3
+ * The dashboard's own --mp-well / --mp-bar / --mp-bar-off pairs (dark #2e2e33 · #5a5a62 · #35353b, light #f1f1f3 ·
4
+ * #c9c9d0 · #e6e6ea) are derived from the card and text tokens, so both themes follow any page palette.
5
+ */
6
+ export declare const styles = "\n:host { --mp-well: color-mix(in srgb, var(--ib-text) 6%, var(--ib-card)); --mp-bar: color-mix(in srgb, var(--ib-text) 24%, var(--ib-card));\n --mp-bar-off: color-mix(in srgb, var(--ib-text) 9%, var(--ib-card));\n display: inline-block; max-width: 100%; vertical-align: middle; }\n.mp { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; max-width: 100%; }\n.mp-trigger { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; min-width: 0; min-height: 30px; padding: 0 9px 0 5px; font: inherit; font-size: 13px; font-weight: 500; color: var(--ib-text); background: var(--ib-seg); border: 1px solid var(--ib-card-border); border-radius: 8px; cursor: pointer; white-space: nowrap; transition: background-color var(--dur-press) var(--ease-out); }\n.mp-trigger:not([aria-disabled=\"true\"]):hover, .mp-trigger[aria-expanded=\"true\"] { background: var(--mp-well); }\n.mp-trigger:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }\n.mp-trigger[aria-busy=\"true\"] { cursor: progress; }\n.mp-trigger[aria-disabled=\"true\"] { cursor: default; background: transparent; border-color: transparent; }\n.mp-trigger[aria-disabled=\"true\"] .mp-chev { display: none; }\n.mp-trigger .mp-glyph { width: 18px; height: 18px; border-radius: 5px; font-size: 10px; }\n.mp-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n.mp-level { flex: none; font-weight: 400; color: var(--ib-text-2); }\n.mp-chev { flex: none; width: 6px; height: 6px; margin: 0 1px 0 2px; border-right: 1.5px solid var(--ib-muted); border-bottom: 1.5px solid var(--ib-muted); transform: translateY(-2px) rotate(45deg); transition: transform 300ms var(--ease-out); }\n.mp-trigger[aria-expanded=\"true\"] .mp-chev { transform: translateY(1px) rotate(225deg); }\n.mp-glyph { display: inline-grid; flex: none; place-items: center; width: 22px; height: 22px; border-radius: 6px; background: var(--ib-card); box-shadow: inset 0 0 0 1px var(--ib-card-border); font-size: 10.5px; font-weight: 600; line-height: 1; color: var(--ib-text); }\n.mp-note { max-width: 100%; font-size: 12px; line-height: 17px; color: var(--ib-text-2); overflow-wrap: anywhere; }\n.mp-note.is-error { color: var(--st-fail-text); }\n.mp-note:empty { display: none; }\n/* A fixed 100px box at 0,0: tells the menu its containing block's origin and scale (transformed ancestors). */\n.mp-probe { position: fixed; left: 0; top: 0; width: 100px; height: 0; visibility: hidden; pointer-events: none; }\n/* The menu: position fixed (an ancestor's overflow clip never cuts it), placed from the trigger in script. */\n.mp-pop { position: fixed; z-index: 150; left: 0; top: 0; box-sizing: border-box; width: min(336px, calc(100vw - 32px)); max-height: calc(100vh - 24px); overflow-y: auto; overscroll-behavior: contain; container: mp / inline-size; padding: 4px; border-radius: 16px; background: var(--ib-col); box-shadow: 0 0 0 1px var(--ib-card-border), var(--ib-shadow-raised); color: var(--ib-text-2); font-size: 13px; line-height: 20px; text-align: left; white-space: normal; transform-origin: top left; opacity: 0; visibility: hidden; pointer-events: none; transform: translateY(-4px) scale(0.96); transition: opacity 160ms ease, transform 220ms var(--ease-out), visibility 0s linear 220ms; }\n.mp-pop[data-side=\"top\"] { transform-origin: bottom left; transform: translateY(4px) scale(0.96); }\n.mp-pop.is-open { opacity: 1; visibility: visible; transform: none; pointer-events: auto; transition: opacity 200ms ease, transform var(--dur-move) var(--ease-out), visibility 0s; }\n.mp-card { padding: 6px; border-radius: 12px; background: var(--ib-card); box-shadow: 0 0 0 1px var(--ib-card-border), var(--ib-shadow); }\n.mp-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; height: 24px; padding: 0 10px; font-size: 11px; line-height: 16px; color: var(--ib-muted); }\n.mp-head-t { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n.mp-cols, .mp-bars { display: grid; flex: none; grid-template-columns: repeat(3, 36px); gap: 8px; }\n.mp-cols { font-size: 10px; letter-spacing: -0.01em; }\n.mp-cols > span { display: flex; justify-content: center; white-space: nowrap; }\n.mp-wait { margin: 0 10px 4px; font-size: 12px; line-height: 17px; color: var(--st-wait); overflow-wrap: anywhere; }\n.mp-list { display: grid; gap: 1px; border-radius: 10px; outline: none; }\n.mp-opt { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; grid-template-areas: \"g t b\" \"g d d\"; align-items: center; column-gap: 10px; min-height: 48px; padding: 6px 10px; border-radius: 10px; cursor: pointer; transition: background-color var(--dur-press) ease, opacity 220ms ease; }\n.mp-opt > .mp-glyph { grid-area: g; }\n.mp-opt > .mp-bars { grid-area: b; }\n.mp-opt[aria-disabled=\"true\"] { cursor: default; }\n.mp-list[aria-disabled=\"true\"] .mp-opt:not([aria-selected=\"true\"]) .mp-title { color: var(--ib-text-2); }\n.mp-list[data-pointing] .mp-opt[data-active] { background: var(--mp-well); }\n.mp-list[data-pointing] .mp-opt:not([data-active]) { opacity: 0.5; }\n.mp-list:focus-visible .mp-opt[data-active] { background: var(--mp-well); box-shadow: inset 0 0 0 1.5px var(--border-focus); }\n.mp-title { grid-area: t; display: flex; align-items: center; gap: 4px; min-width: 0; font-weight: 500; color: var(--ib-text); }\n.mp-title-name { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n.mp-check { flex: none; font-size: 12px; line-height: 1; color: var(--accent); animation: mp-pop 320ms var(--ease-out) both; }\n.mp-desc { grid-area: d; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; font-size: 12px; line-height: 17px; color: var(--ib-muted); overflow-wrap: anywhere; }\n.mp-meter { display: grid; grid-template-columns: repeat(5, 1fr); gap: 2px; height: 5px; }\n.mp-meter.is-none { display: block; height: auto; font-size: 11px; line-height: 1; text-align: center; color: var(--ib-muted); }\n.mp-seg { border-radius: 1.5px; background: var(--mp-bar-off); transition: background-color 180ms ease; }\n.mp-seg[data-on] { background: var(--mp-bar); }\n.mp-opt[data-lit] .mp-seg[data-on] { background: var(--accent); transition-delay: calc(var(--c) * 70ms + var(--s) * 45ms); animation: mp-fill 360ms var(--ease-out) both; animation-delay: calc(var(--c) * 70ms + var(--s) * 45ms); }\n.mp-settings { margin-top: 6px; padding: 6px 10px 4px; border-top: 1px solid var(--ib-card-border); }\n.mp-effort-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; min-height: 22px; }\n.mp-effort-label { flex: none; font-size: 12.5px; color: var(--ib-text-2); }\n.mp-effort-cap { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--st-wait); }\n.mp-seg-ctl { position: relative; display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr)); margin-top: 4px; padding: 2px; border-radius: 9px; background: var(--ib-seg); box-shadow: inset 0 0 0 1px var(--ib-card-border); }\n.mp-thumb { position: absolute; top: 2px; bottom: 2px; left: 2px; width: calc((100% - 4px) / var(--n)); border-radius: 7px; background: var(--ib-seg-on); box-shadow: 0 0 0 1px var(--ib-card-border), 0 1px 2px color-mix(in srgb, #000 12%, transparent); transform: translateX(calc(var(--i) * 100%)); transition: transform var(--dur-move) var(--ease-out); }\n.mp-seg-btn { position: relative; min-width: 0; min-height: 26px; padding: 0 2px; font: inherit; font-size: 12px; font-weight: 500; color: var(--ib-muted); background: none; border: 0; border-radius: 7px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: color 200ms ease; }\n.mp-seg-btn[aria-checked=\"true\"], .mp-seg-btn:hover { color: var(--ib-text); }\n.mp-seg-btn:focus-visible { outline: 2px solid var(--border-focus); outline-offset: -2px; }\n.mp-seg-ctl[aria-disabled=\"true\"] .mp-seg-btn { cursor: progress; }\n.mp-err { margin: 6px 10px 2px; font-size: 12px; line-height: 17px; color: var(--st-fail-text); overflow-wrap: anywhere; }\n.mp-err:empty { display: none; }\n.mp-foot { display: flex; align-items: center; gap: 6px; min-height: 38px; padding: 4px 6px 0 10px; }\n.mp-caption { flex: 1; min-width: 0; overflow: hidden; font-size: 12px; color: var(--ib-muted); white-space: nowrap; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }\n.mp-caption b { font-weight: 500; color: var(--ib-text); }\n.mp-keys { display: flex; flex: none; gap: 2px; }\n.mp-keys kbd { display: grid; place-items: center; min-width: 18px; height: 18px; padding: 0 3px; border-radius: 5px; background: var(--ib-card); box-shadow: 0 0 0 1px var(--ib-card-border); font-family: inherit; font-size: 10.5px; line-height: 1; color: var(--ib-muted); }\n@keyframes mp-pop { from { opacity: 0; transform: scale(0.6); } }\n@keyframes mp-fill { 0% { transform: scaleY(1); } 45% { transform: scaleY(1.6); } 100% { transform: scaleY(1); } }\n@container mp (max-width: 300px) {\n .mp-opt .mp-glyph, .mp-keys { display: none; }\n .mp-opt { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: \"t b\" \"d d\"; }\n}\n/* Touch / narrow: the dashboard's own block \u2014 trigger and levels \u226544px, rows \u226554px, no key hints. */\n@media (pointer: coarse), (max-width: 639px) {\n .mp-trigger, .mp-seg-btn { min-height: 44px; }\n .mp-opt { min-height: 54px; }\n .mp-keys { display: none; }\n}\n/* The global rule shortens durations; scale, the staggered fill and the check pop stop here too. */\n@media (prefers-reduced-motion: reduce) {\n .mp-pop, .mp-pop[data-side=\"top\"] { transform: none; }\n .mp-opt[data-lit] .mp-seg[data-on] { animation: none; transition-delay: 0s; }\n .mp-check { animation: none; }\n}\n";
@@ -0,0 +1,7 @@
1
+ export type { ToolCallData, ToolCallGroupData, ToolCallStatus, ToolIcon } from './logic.js';
2
+ export { formatToolClock, formatToolMs, groupStats, highlightJson, TOOL_ICONS, toCodeText, toolIcon, } from './logic.js';
3
+ export type { ToolCallLang, ToolCallStrings } from './strings.js';
4
+ export { toolCallStrings } from './strings.js';
5
+ export type { ToolCallActionDetail, ToolCallCopyDetail, ToolCallPart, ToolCallToggleDetail } from './tool-call-card.js';
6
+ export { normStatus, UiToolCallCard } from './tool-call-card.js';
7
+ export { callKeys, UiToolCallGroup } from './tool-call-group.js';
@@ -0,0 +1,87 @@
1
+ /**
2
+ * `running` — the call is in flight. `success` / `error` — it returned. `interrupted` — the run ended (stopped turn,
3
+ * crashed process) before this call returned a result: neither running nor failed.
4
+ */
5
+ export type ToolCallStatus = 'running' | 'success' | 'error' | 'interrupted';
6
+ export type ToolIcon = 'wrench' | 'search' | 'file' | 'edit' | 'terminal' | 'globe' | 'agent' | 'list' | 'plug';
7
+ export interface ToolCallData {
8
+ /** Tool name as the agent calls it, shown in mono — `Read`, `mcp__github__get_issue`. */
9
+ name: string;
10
+ /** One human line — `src/app.ts (lines 1-80)`. */
11
+ summary?: string;
12
+ /** Icon in the tile. Default `toolIcon(name)`. */
13
+ icon?: ToolIcon;
14
+ status: ToolCallStatus;
15
+ /** Object → pretty JSON; a JSON string is parsed; other text is shown as is. */
16
+ args?: unknown;
17
+ result?: unknown;
18
+ /** Shown when status is `error`. */
19
+ error?: string;
20
+ /** Epoch ms the call started — the live timer counts from here while running. */
21
+ startedAt?: number;
22
+ /** Final duration in ms. */
23
+ duration?: number;
24
+ /** Epoch ms clock time the call started, shown muted in the body as HH:MM:SS. */
25
+ at?: number;
26
+ /** Set → controlled; unset → the element keeps its own open state. */
27
+ open?: boolean;
28
+ /** Initial open state while uncontrolled (read on the first render only). */
29
+ defaultOpen?: boolean;
30
+ /** Default true: opens when the call turns to error, closes again on the next run unless toggled by hand. */
31
+ openOnError?: boolean;
32
+ /** Shows Retry next to the error → `ui-retry`. */
33
+ canRetry?: boolean;
34
+ /** Default 220 — taller blocks get a fade and Show all / Show less. */
35
+ maxCodeHeight?: number;
36
+ /** The host is fetching args/result: the body shows a quiet Loading… line. */
37
+ loading?: boolean;
38
+ /** The host's fetch failed: this message plus Try again → `ui-reload`. */
39
+ detailError?: string;
40
+ /** One muted line in the body (e.g. “Showing the first 256 KiB of 1.2 MB”). */
41
+ note?: string;
42
+ /** Default true; false → the header button is out of the tab order (the host runs a roving focus). */
43
+ focusable?: boolean;
44
+ /** Host key for the call, echoed as `detail.id` in every event (and used by the group to keep cards). */
45
+ id?: string;
46
+ }
47
+ export interface ToolCallGroupData {
48
+ calls: ToolCallData[];
49
+ /** Label once every call is done. Default “Used N tools”. */
50
+ label?: string;
51
+ /** Label while any call runs. Default “Using tools…”. */
52
+ runningLabel?: string;
53
+ /** Total ms. Default: the sum of the calls' durations, or the time measured while running. */
54
+ duration?: number;
55
+ /** Set → controlled; unset → the element keeps its own open state. */
56
+ open?: boolean;
57
+ /** Initial open state while uncontrolled (read on the first render only). */
58
+ defaultOpen?: boolean;
59
+ /** Default true; false → the group's and the cards' header buttons are out of the tab order. */
60
+ focusable?: boolean;
61
+ }
62
+ /** 0.4s · 12.3s · 2m 05s · 1h 02m. Live timers always show tenths below a minute; a final value under 1s reads 350ms. */
63
+ export declare function formatToolMs(ms: number, live?: boolean): string;
64
+ /** Clock time HH:MM:SS (24h) in the card's language; empty for a missing or invalid time. */
65
+ export declare function formatToolClock(at: number | undefined, lang?: 'en' | 'ko'): string;
66
+ /**
67
+ * What a code block shows: objects pretty-printed, a string that holds JSON parsed and pretty-printed, any other
68
+ * text as is. `json` says whether the text may be highlighted.
69
+ */
70
+ export declare function toCodeText(value: unknown): {
71
+ text: string;
72
+ json: boolean;
73
+ };
74
+ /** JSON text → escaped HTML with `tc-k` (key), `tc-s` (string), `tc-n` (number), `tc-l` (literal) spans. */
75
+ export declare function highlightJson(text: string): string;
76
+ /** The tile icon for a tool name: Read/Write → file, Edit → edit, Bash → terminal, Grep/Glob/WebSearch → search,
77
+ * WebFetch → globe, Agent/Task → agent, TodoWrite → list, mcp__* → plug, anything else → wrench. */
78
+ export declare function toolIcon(name: string): ToolIcon;
79
+ /** Line count of a block (for the cap estimate when the layout cannot be measured). */
80
+ export declare function lineCount(text: string): number;
81
+ /** What the group line says about its calls. `interrupted` counts as neither running nor failed. */
82
+ export declare function groupStats(calls: ToolCallData[]): {
83
+ running: boolean;
84
+ failed: number;
85
+ sum: number | undefined;
86
+ };
87
+ export declare const TOOL_ICONS: Record<ToolIcon, string>;
@@ -0,0 +1,94 @@
1
+ /** UI copy. English by default, Korean with `lang="ko"` (the soma-work 작업 로그 wording). */
2
+ declare const en: {
3
+ running: string;
4
+ done: string;
5
+ failed: string;
6
+ noResult: string;
7
+ args: string;
8
+ result: string;
9
+ waiting: string;
10
+ nothing: string;
11
+ failedDefault: string;
12
+ interrupted: string;
13
+ showAll: string;
14
+ showLess: string;
15
+ retry: string;
16
+ reload: string;
17
+ loading: string;
18
+ copy: (part: string) => string;
19
+ copied: string;
20
+ selected: string;
21
+ startedAt: (clock: string) => string;
22
+ sayRunning: (name: string) => string;
23
+ sayDone: (name: string) => string;
24
+ sayFailed: (name: string, error: string) => string;
25
+ sayInterrupted: (name: string) => string;
26
+ used: (n: number) => string;
27
+ using: string;
28
+ nFailed: (n: number) => string;
29
+ groupSay: (label: string, failed: number) => string;
30
+ };
31
+ export type ToolCallStrings = typeof en;
32
+ export declare const STRINGS: {
33
+ en: {
34
+ running: string;
35
+ done: string;
36
+ failed: string;
37
+ noResult: string;
38
+ args: string;
39
+ result: string;
40
+ waiting: string;
41
+ nothing: string;
42
+ failedDefault: string;
43
+ interrupted: string;
44
+ showAll: string;
45
+ showLess: string;
46
+ retry: string;
47
+ reload: string;
48
+ loading: string;
49
+ copy: (part: string) => string;
50
+ copied: string;
51
+ selected: string;
52
+ startedAt: (clock: string) => string;
53
+ sayRunning: (name: string) => string;
54
+ sayDone: (name: string) => string;
55
+ sayFailed: (name: string, error: string) => string;
56
+ sayInterrupted: (name: string) => string;
57
+ used: (n: number) => string;
58
+ using: string;
59
+ nFailed: (n: number) => string;
60
+ groupSay: (label: string, failed: number) => string;
61
+ };
62
+ ko: {
63
+ running: string;
64
+ done: string;
65
+ failed: string;
66
+ noResult: string;
67
+ args: string;
68
+ result: string;
69
+ waiting: string;
70
+ nothing: string;
71
+ failedDefault: string;
72
+ interrupted: string;
73
+ showAll: string;
74
+ showLess: string;
75
+ retry: string;
76
+ reload: string;
77
+ loading: string;
78
+ copy: (part: string) => string;
79
+ copied: string;
80
+ selected: string;
81
+ startedAt: (clock: string) => string;
82
+ sayRunning: (name: string) => string;
83
+ sayDone: (name: string) => string;
84
+ sayFailed: (name: string, error: string) => string;
85
+ sayInterrupted: (name: string) => string;
86
+ used: (n: number) => string;
87
+ using: string;
88
+ nFailed: (n: number) => string;
89
+ groupSay: (label: string, failed: number) => string;
90
+ };
91
+ };
92
+ export type ToolCallLang = keyof typeof STRINGS;
93
+ export declare function toolCallStrings(lang: string | null | undefined): ToolCallStrings;
94
+ export {};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tool Call Card + Group. Sizes and timings follow the affectUI Tool Call Card pattern; every colour is a dashboard
3
+ * token or a color-mix() of tokens, so light and dark both read.
4
+ */
5
+ export declare const styles = "\n:host { --tc-ink: var(--text); --tc-text: var(--text-secondary); --tc-muted: var(--text-tertiary);\n --tc-bg: var(--surface); --tc-line: var(--border); --tc-tile: var(--surface-raised);\n --tc-hover: color-mix(in srgb, var(--text) 3.5%, var(--surface));\n --tc-code: color-mix(in srgb, var(--surface-raised) 60%, var(--surface));\n --tc-ring: var(--surface);\n --tc-red: var(--st-fail-text); --tc-red-bg: color-mix(in srgb, var(--st-fail) 7%, var(--surface));\n --tc-red-line: color-mix(in srgb, var(--st-fail) 24%, transparent);\n --tc-key: var(--text); --tc-str: color-mix(in srgb, var(--green) 62%, var(--accent)); --tc-num: var(--orange);\n --tc-lit: var(--purple); --tc-shine: var(--text);\n --tc-lift: 0 1px 2px color-mix(in srgb, var(--text) 6%, transparent);\n --tc-ease: var(--ease-out);\n min-width: 0; font-size: 13.5px; line-height: 1.4; letter-spacing: -0.006em; }\n[hidden] { display: none !important; }\nbutton { appearance: none; margin: 0; border: 0; background: none; text-align: left; letter-spacing: inherit; }\n\n/* Card */\n.tc { position: relative; border: 1px solid var(--tc-line); border-radius: 12px; background: var(--tc-bg); box-shadow: var(--tc-lift); }\n.tc-head { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 8px 10px 8px 9px;\n border-radius: 11px; color: var(--tc-text); transition: background-color 160ms ease; }\n.tc[data-open] > .tc-head { border-radius: 11px 11px 0 0; }\n.tc-icon { display: grid; place-items: center; flex: none; width: 26px; height: 26px; border-radius: 7px;\n background: var(--tc-tile); box-shadow: inset 0 0 0 1px var(--tc-line); color: var(--tc-ink); }\n.tc-icon > svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }\n.tc-main { display: flex; align-items: baseline; gap: 8px; flex: 1; min-width: 0; }\n.tc-name { flex: 0 1 auto; max-width: 55%; overflow: hidden; color: var(--tc-ink); font-family: var(--mono); font-size: 12.5px;\n font-weight: 500; letter-spacing: 0; text-overflow: ellipsis; white-space: nowrap; }\n.tc-summary { flex: 1 1 0; min-width: 0; overflow: hidden; color: var(--tc-text); text-overflow: ellipsis; white-space: nowrap; }\n.tc-status { display: inline-flex; align-items: center; gap: 6px; flex: none; color: var(--tc-muted); font-size: 12.5px;\n font-variant-numeric: tabular-nums; white-space: nowrap; }\n.tc-status.is-in { animation: tc-in 360ms var(--tc-ease) both; }\n.tc[data-status=\"error\"] .tc-status { color: var(--tc-red); }\n.tc-status > svg { width: 14px; height: 14px; flex: none; fill: none; stroke-linecap: round; stroke-linejoin: round; }\n.tc-spin { animation: tc-spin 800ms linear infinite; }\n.tc-spin circle { stroke: currentColor; stroke-width: 1.6; opacity: 0.25; }\n.tc-spin path { stroke: var(--st-run); stroke-width: 1.6; }\n.tc-ok { stroke: var(--st-done); stroke-width: 1.8; }\n.is-in .tc-ok path { stroke-dasharray: 1; stroke-dashoffset: 1; animation: tc-draw 360ms var(--tc-ease) 80ms forwards; }\n.tc-err { stroke: var(--st-fail); stroke-width: 1.5; }\n.tc-stop { stroke: currentColor; stroke-width: 1.5; }\n.tc-chev { flex: none; width: 14px; height: 14px; fill: none; stroke: var(--tc-muted); stroke-width: 1.5; stroke-linecap: round;\n stroke-linejoin: round; transition: transform 360ms var(--tc-ease); }\n.tc[data-open] > .tc-head .tc-chev, .tcg[data-open] > .tcg-head .tc-chev { transform: rotate(180deg); }\n@media (hover: hover) {\n .tc-head:hover { background: var(--tc-hover); }\n .tc-head:hover .tc-chev { stroke: var(--tc-ink); }\n .tcg-head:hover { background: var(--tc-hover); color: var(--tc-ink); }\n .tc-copy:hover { background: var(--tc-hover); color: var(--tc-ink); }\n .tc-more:hover { color: var(--tc-ink); }\n .tc-btn:hover { background: var(--tc-hover); }\n}\n\n/* Body: 0fr \u2192 1fr, so the height animates without measuring */\n.tc-body { display: grid; grid-template-rows: 0fr; opacity: 0;\n transition: grid-template-rows 380ms var(--tc-ease), opacity 240ms ease; }\n.tc-body[data-open] { grid-template-rows: 1fr; opacity: 1; }\n.tc-clip { min-height: 0; overflow: hidden; }\n.tc-sections { display: grid; gap: 12px; padding: 12px; border-top: 1px solid var(--tc-line); }\n.tc-top { display: flex; justify-content: flex-end; margin: -2px 0 -6px; }\n.tc-line { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }\n.tc-line > p { min-width: 0; }\n.tc-head-end { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }\n.tc-at { flex: none; color: var(--tc-muted); font-size: 11.5px; line-height: 16px; font-variant-numeric: tabular-nums; }\n.tc-section { display: grid; gap: 6px; min-width: 0; }\n.tc-section-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px; }\n.tc-label { color: var(--tc-muted); font-size: 12px; font-weight: 500; }\n.tc-pending, .tc-note { margin: 0; color: var(--tc-muted); font-size: 12.5px; }\n.tc-note { font-size: 12px; }\n\n.tc-copy { position: relative; display: grid; place-items: center; flex: none; width: 24px; height: 24px; padding: 0;\n border-radius: 6px; color: var(--tc-muted); transition: background-color 160ms ease, color 160ms ease; }\n.tc-copy svg { position: absolute; width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4;\n stroke-linecap: round; stroke-linejoin: round;\n transition: opacity 200ms ease, transform 320ms var(--tc-ease), filter 200ms ease; }\n.tc-copy-done { opacity: 0; transform: scale(0.6); filter: blur(2px); stroke-width: 1.7; }\n.tc-copy[data-copied] { color: var(--st-done); }\n.tc-copy[data-copied] .tc-copy-icon { opacity: 0; transform: scale(0.6); filter: blur(2px); }\n.tc-copy[data-copied] .tc-copy-done { opacity: 1; transform: none; filter: none; }\n\n/* Code wraps instead of scrolling sideways; a capped block fades out at the bottom */\n.tc-code { position: relative; margin: 0; padding: 10px 12px; overflow: hidden; border-radius: 8px; background: var(--tc-code);\n box-shadow: inset 0 0 0 1px var(--tc-line); color: var(--tc-text); font-family: var(--mono); font-size: 12px;\n line-height: 1.6; letter-spacing: 0; white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 2;\n transition: max-height 420ms var(--tc-ease); }\n.tc-code[data-json] { color: var(--tc-muted); }\n.tc-code code { display: block; font: inherit; }\n.tc-code::after { content: \"\"; position: absolute; left: 1px; right: 1px; bottom: 1px; height: 56px; border-radius: 0 0 7px 7px;\n background: linear-gradient(to bottom, color-mix(in srgb, var(--tc-code) 0%, transparent), var(--tc-code));\n opacity: 0; transition: opacity 240ms ease; pointer-events: none; }\n.tc-code[data-capped]::after { opacity: 1; }\n.tc-k { color: var(--tc-key); }\n.tc-s { color: var(--tc-str); }\n.tc-n { color: var(--tc-num); }\n.tc-l { color: var(--tc-lit); }\n.tc-more { position: relative; justify-self: start; padding: 2px 4px; margin-left: -4px; border-radius: 5px; color: var(--tc-text);\n font-size: 12.5px; font-weight: 500; transition: color 160ms ease; }\n\n/* Error: the message and an optional Retry; a failed host fetch: the message and Try again */\n.tc-error { display: flex; align-items: flex-start; gap: 8px; padding: 9px 10px 9px 11px; border-radius: 8px;\n background: var(--tc-red-bg); box-shadow: inset 0 0 0 1px var(--tc-red-line); }\n.tc-error > svg { flex: none; width: 14px; height: 14px; margin-top: 2px; fill: none; stroke-linecap: round; }\n.tc-error-text { flex: 1; min-width: 0; margin: 0; color: var(--tc-red); font-size: 13px; line-height: 1.45;\n white-space: pre-wrap; overflow-wrap: anywhere; }\n.tc-reload-row { display: flex; align-items: center; gap: 8px; }\n.tc-reload-row > .tc-pending { flex: 1; min-width: 0; overflow-wrap: anywhere; }\n.tc-btn { position: relative; display: inline-flex; align-items: center; gap: 5px; flex: none; height: 26px; margin: -3px 0;\n padding: 0 10px 0 8px; border-radius: 7px; background: var(--tc-bg); box-shadow: inset 0 0 0 1px var(--tc-line), var(--tc-lift);\n color: var(--tc-ink); font-size: 12.5px; font-weight: 500; white-space: nowrap; transition: background-color 160ms ease; }\n.tc-btn svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round;\n stroke-linejoin: round; transition: transform 200ms var(--tc-ease); }\n.tc-btn:active svg { transform: rotate(-60deg); }\n\n/* Group: one quiet line with the tools' icons stacked, the cards below */\n.tcg { display: grid; }\n.tcg-head { display: flex; align-items: center; gap: 9px; justify-self: start; max-width: 100%; min-height: 36px;\n padding: 4px 10px 4px 5px; border-radius: 10px; color: var(--tc-text);\n transition: background-color 160ms ease, color 160ms ease; }\n.tcg-stack { display: flex; flex: none; }\n.tcg-stack .tc-icon { width: 24px; height: 24px; box-shadow: inset 0 0 0 1px var(--tc-line), 0 0 0 2px var(--tc-ring); }\n.tcg-stack .tc-icon.is-in { animation: tc-pop 420ms var(--tc-ease) both; }\n.tcg-stack .tc-icon + .tc-icon { margin-left: -5px; }\n.tcg-stack .tc-icon > svg { width: 13px; height: 13px; }\n.tcg-title { display: flex; align-items: baseline; gap: 5px; min-width: 0; white-space: nowrap; }\n.tcg-title.is-in { animation: tc-in 360ms var(--tc-ease) both; }\n.tcg-label { position: relative; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: inherit; font-weight: 500; }\n.tcg-failed { flex: none; color: var(--tc-red); }\n.tcg-time { flex: none; color: var(--tc-muted); font-variant-numeric: tabular-nums; }\n.tcg-list { display: grid; gap: 8px; padding-top: 8px; }\n\n/* A band of the text colour sweeping over \u201CUsing tools\u2026\u201D */\n.tc-shine::after { content: attr(data-text); content: attr(data-text) / \"\"; position: absolute; inset: 0; white-space: nowrap;\n color: transparent; background: linear-gradient(90deg, transparent 38%, var(--tc-shine) 50%, transparent 62%) 100% 0 / 300% 100% no-repeat;\n -webkit-background-clip: text; background-clip: text; animation: tc-sweep 1.8s linear infinite; pointer-events: none; }\n\n@keyframes tc-in { from { opacity: 0; transform: translateY(3px); filter: blur(2px); } to { opacity: 1; transform: none; filter: none; } }\n@keyframes tc-pop { from { opacity: 0; transform: scale(0.6); } to { opacity: 1; transform: none; } }\n@keyframes tc-spin { to { transform: rotate(360deg); } }\n@keyframes tc-draw { to { stroke-dashoffset: 0; } }\n@keyframes tc-sweep { from { background-position: 100% 0; } to { background-position: 0% 0; } }\n\n/* Touch: every control gets a 44px target without changing the layout */\n@media (pointer: coarse) {\n .tcg-head { min-height: 44px; }\n .tc-copy::before, .tc-more::before, .tc-btn::before { content: \"\"; position: absolute; inset: -10px; }\n .tc-more::before { inset: -12px -8px; }\n .tc-btn::before { inset: -9px -4px; }\n}\n@media (prefers-reduced-motion: reduce) {\n .tc-shine::after { display: none; }\n .tc-spin { animation: none; }\n .is-in .tc-ok path { animation: none; stroke-dashoffset: 0; }\n}\n";
@@ -0,0 +1,49 @@
1
+ import { UiElement } from '../../core/element.js';
2
+ import { type ToolCallData, type ToolCallStatus } from './logic.js';
3
+ /** Which code block an event is about. */
4
+ export type ToolCallPart = 'args' | 'result';
5
+ export interface ToolCallToggleDetail {
6
+ open: boolean;
7
+ /** `data.id` when set. */
8
+ id?: string;
9
+ }
10
+ export interface ToolCallCopyDetail {
11
+ part: ToolCallPart;
12
+ text: string;
13
+ id?: string;
14
+ }
15
+ export interface ToolCallActionDetail {
16
+ id?: string;
17
+ }
18
+ export declare const SPIN_SVG = "<svg class=\"tc-spin\" viewBox=\"0 0 16 16\" aria-hidden=\"true\"><circle cx=\"8\" cy=\"8\" r=\"5.8\"/><path d=\"M8 2.2a5.8 5.8 0 0 1 5.8 5.8\"/></svg>";
19
+ export declare const CHEV_SVG = "<svg class=\"tc-chev\" viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"m4.4 6.2 3.6 3.6 3.6-3.6\"/></svg>";
20
+ /** A status the card knows; anything else reads as `interrupted` (neutral, no timer). */
21
+ export declare function normStatus(s: unknown): ToolCallStatus;
22
+ /** The tile glyph for a call: its `icon` when it names one, else `toolIcon(name)`. */
23
+ export declare function callIconSvg(d: Pick<ToolCallData, 'name' | 'icon'>): string;
24
+ /**
25
+ * `<ui-tool-call-card>` — one tool call of an agent: icon, name, summary and status (spinner with a live timer, a
26
+ * drawn check with the duration, Failed, or No result) on one line; open it for the arguments and the result as
27
+ * highlighted JSON with copy buttons, long blocks capped with Show all, errors with Retry.
28
+ */
29
+ export declare class UiToolCallCard extends UiElement<ToolCallData> {
30
+ #private;
31
+ static styles: string[];
32
+ static get observedAttributes(): string[];
33
+ constructor();
34
+ attributeChangedCallback(): void;
35
+ disconnectedCallback(): void;
36
+ /** Focus the header button (the card's one tab stop). */
37
+ /** Retry, Try again, a Show all that went away, an auto-closed body: focus stays on the card's header. */
38
+ protected focusFallback(): HTMLElement | null;
39
+ focus(options?: FocusOptions): void;
40
+ /** Whether the body is open now (controlled `data.open`, else the element's own state). */
41
+ get isOpen(): boolean;
42
+ protected render(): string;
43
+ protected updated(): void;
44
+ }
45
+ declare global {
46
+ interface HTMLElementTagNameMap {
47
+ 'ui-tool-call-card': UiToolCallCard;
48
+ }
49
+ }
@@ -0,0 +1,32 @@
1
+ import { UiElement } from '../../core/element.js';
2
+ import { type ToolCallData, type ToolCallGroupData } from './logic.js';
3
+ import { type UiToolCallCard } from './tool-call-card.js';
4
+ /** Stable keys for a group's calls: `id:<id>` when the call has an id, else `i:<index>` (duplicates get a suffix). */
5
+ export declare function callKeys(calls: ToolCallData[]): string[];
6
+ /**
7
+ * `<ui-tool-call-group>` — several tool calls folded into one quiet line: the last three tools' icons stacked,
8
+ * “Used 3 tools · 1 failed · 2.4s” (or “Using tools…” with a light sweep while any call runs). Open it for one
9
+ * `<ui-tool-call-card>` per call. The cards live in a list outside the re-rendered line and are reconciled by key,
10
+ * so a group update never re-creates them (open state, timers and copy checks survive).
11
+ */
12
+ export declare class UiToolCallGroup extends UiElement<ToolCallGroupData> {
13
+ #private;
14
+ static styles: string[];
15
+ static get observedAttributes(): string[];
16
+ constructor();
17
+ attributeChangedCallback(): void;
18
+ disconnectedCallback(): void;
19
+ /** Focus the group's header button. */
20
+ focus(options?: FocusOptions): void;
21
+ /** Whether the list is open now (controlled `data.open`, else the element's own state). */
22
+ get isOpen(): boolean;
23
+ /** The card elements, in call order. */
24
+ get cards(): UiToolCallCard[];
25
+ protected render(): string;
26
+ protected updated(): void;
27
+ }
28
+ declare global {
29
+ interface HTMLElementTagNameMap {
30
+ 'ui-tool-call-group': UiToolCallGroup;
31
+ }
32
+ }