@agentdeck/shared 1.0.24 → 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.
Files changed (68) hide show
  1. package/dist/action-fold.d.ts +71 -0
  2. package/dist/action-fold.d.ts.map +1 -0
  3. package/dist/action-fold.js +93 -0
  4. package/dist/action-fold.js.map +1 -0
  5. package/dist/connection-status.d.ts +11 -2
  6. package/dist/connection-status.d.ts.map +1 -1
  7. package/dist/connection-status.js +11 -2
  8. package/dist/connection-status.js.map +1 -1
  9. package/dist/d200h-layout.d.ts +11 -0
  10. package/dist/d200h-layout.d.ts.map +1 -1
  11. package/dist/d200h-layout.js +126 -40
  12. package/dist/d200h-layout.js.map +1 -1
  13. package/dist/esp32-boards.d.ts.map +1 -1
  14. package/dist/esp32-boards.js +34 -0
  15. package/dist/esp32-boards.js.map +1 -1
  16. package/dist/eval-schema.d.ts +72 -1
  17. package/dist/eval-schema.d.ts.map +1 -1
  18. package/dist/eval-schema.js +23 -0
  19. package/dist/eval-schema.js.map +1 -1
  20. package/dist/format-utils.d.ts +9 -18
  21. package/dist/format-utils.d.ts.map +1 -1
  22. package/dist/format-utils.js +9 -18
  23. package/dist/format-utils.js.map +1 -1
  24. package/dist/gateway-protocol.d.ts +104 -23
  25. package/dist/gateway-protocol.d.ts.map +1 -1
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/model-provider.d.ts +10 -1
  31. package/dist/model-provider.d.ts.map +1 -1
  32. package/dist/model-provider.js +22 -0
  33. package/dist/model-provider.js.map +1 -1
  34. package/dist/openclaw-approval.d.ts.map +1 -1
  35. package/dist/openclaw-approval.js +9 -2
  36. package/dist/openclaw-approval.js.map +1 -1
  37. package/dist/pairing-code.d.ts +69 -0
  38. package/dist/pairing-code.d.ts.map +1 -1
  39. package/dist/pairing-code.js +138 -0
  40. package/dist/pairing-code.js.map +1 -1
  41. package/dist/protocol.d.ts +157 -5
  42. package/dist/protocol.d.ts.map +1 -1
  43. package/dist/protocol.js +4 -0
  44. package/dist/protocol.js.map +1 -1
  45. package/dist/sample.d.ts +4 -0
  46. package/dist/sample.d.ts.map +1 -1
  47. package/dist/svg-renderers/session-slot-renderer.d.ts +36 -0
  48. package/dist/svg-renderers/session-slot-renderer.d.ts.map +1 -1
  49. package/dist/svg-renderers/session-slot-renderer.js +109 -3
  50. package/dist/svg-renderers/session-slot-renderer.js.map +1 -1
  51. package/dist/task-title.d.ts +51 -0
  52. package/dist/task-title.d.ts.map +1 -0
  53. package/dist/task-title.js +111 -0
  54. package/dist/task-title.js.map +1 -0
  55. package/dist/telemetry-envelope.d.ts +11 -1
  56. package/dist/telemetry-envelope.d.ts.map +1 -1
  57. package/dist/telemetry-envelope.js.map +1 -1
  58. package/dist/timeline-icons.d.ts.map +1 -1
  59. package/dist/timeline-icons.js +12 -0
  60. package/dist/timeline-icons.js.map +1 -1
  61. package/dist/timeline-task-display.d.ts +8 -4
  62. package/dist/timeline-task-display.d.ts.map +1 -1
  63. package/dist/timeline-task-display.js +9 -3
  64. package/dist/timeline-task-display.js.map +1 -1
  65. package/dist/timeline.d.ts +12 -1
  66. package/dist/timeline.d.ts.map +1 -1
  67. package/dist/timeline.js.map +1 -1
  68. package/package.json +1 -1
@@ -0,0 +1,71 @@
1
+ /**
2
+ * action-fold.ts — the SINGLE source of truth for folding a task's tool
3
+ * activity into one summary line: `Edit×9 Read×24 Bash×11 +2 · 3 files`.
4
+ *
5
+ * A task detail used to enumerate turns and steps; a list row has no room for
6
+ * that, and a bare total ("44 tools") says nothing about the SHAPE of the
7
+ * work. The fold is a pure projection over per-tool counts the store already
8
+ * holds (sample_events kind='tool' grouped by tool_name) — no schema change,
9
+ * no new collection.
10
+ *
11
+ * Consumers (keep in the same commit):
12
+ * - GET /apme/tasks response `actionFold` field (bridge/src/apme/http.ts)
13
+ */
14
+ export interface ActionFoldInput {
15
+ /** Per-tool call counts, any order. Names are the raw tool names the agent
16
+ * reported (`Read`, `Bash`, `mcp__server__tool`). */
17
+ tools: ReadonlyArray<{
18
+ name: string;
19
+ count: number;
20
+ }>;
21
+ /** Distinct files the task touched (modified + created), when known. */
22
+ filesTouched?: number | null;
23
+ }
24
+ /** How many named tools the fold shows before collapsing the rest to `+N`.
25
+ * Four keeps the line inside one row beside its chips at the dashboard's
26
+ * default width. */
27
+ export declare const ACTION_FOLD_MAX_TOOLS = 4;
28
+ /** `mcp__claude-in-chrome__navigate` → `navigate`: the server prefix is
29
+ * provenance, not shape, and at row width it would crowd out every other
30
+ * tool. Non-MCP names pass through unchanged. */
31
+ export declare function foldToolName(name: string): string;
32
+ /** Tool names that DISPATCH another agent (fan-out). Measured on the real
33
+ * store 2026-08-28: `Agent` (Claude Code subagent tool, 126 calls), `Task`
34
+ * (its pre-rename spelling), `task` (OpenCode's), `Workflow` (multi-agent
35
+ * orchestration — counted as ONE dispatch per call even though it fans out
36
+ * internally, because parent-side hooks see only the call). TaskCreate /
37
+ * TaskUpdate and friends are todo bookkeeping, not dispatch. */
38
+ export declare const DISPATCH_TOOL_NAMES: ReadonlySet<string>;
39
+ /** Tool names that MESSAGE another agent (peer/teammate traffic). */
40
+ export declare const MESSAGING_TOOL_NAMES: ReadonlySet<string>;
41
+ export interface AgentCoordinationSummary {
42
+ /** Subagent/workflow dispatch calls in this task. */
43
+ dispatches: number;
44
+ /** Agent-to-agent messages sent from this task. */
45
+ messages: number;
46
+ }
47
+ /**
48
+ * Count the coordination shape of a task from its per-tool counts: how often
49
+ * it fanned out to other agents and how often it messaged them. Returns null
50
+ * when the task did neither, so a plain single-agent task renders no
51
+ * coordination chip at all. Matches on RAW tool names (before `foldToolName`),
52
+ * since the dispatch/messaging sets are exact names, not display shapes.
53
+ */
54
+ export declare function agentCoordinationSummary(tools: ReadonlyArray<{
55
+ name: string;
56
+ count: number;
57
+ }>): AgentCoordinationSummary | null;
58
+ /**
59
+ * Fold tool counts into the summary line. Returns null when there is nothing
60
+ * to say (no tools and no files) — callers render nothing rather than an
61
+ * empty string, matching the retain-on-absent display rules elsewhere.
62
+ *
63
+ * Determinism: ties sort by name so the same task always folds to the same
64
+ * line (renderer output is used as identity in dedup paths — see the
65
+ * session-slot renderer rule in CLAUDE.md). The tie-break is a plain
66
+ * code-unit compare, NOT localeCompare: localeCompare's order depends on the
67
+ * host locale (and case-folds, so `Bash` vs `apply` flips), which both breaks
68
+ * determinism across machines and cannot be mirrored by Swift's `<`.
69
+ */
70
+ export declare function foldActionCounts(input: ActionFoldInput): string | null;
71
+ //# sourceMappingURL=action-fold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-fold.d.ts","sourceRoot":"","sources":["../src/action-fold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,eAAe;IAC9B;0DACsD;IACtD,KAAK,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtD,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;qBAEqB;AACrB,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC;;kDAEkD;AAClD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED;;;;;iEAKiE;AACjE,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAkD,CAAC;AAEvG,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAA4B,CAAC;AAElF,MAAM,WAAW,wBAAwB;IACvC,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GACpD,wBAAwB,GAAG,IAAI,CAUjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAkBtE"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * action-fold.ts — the SINGLE source of truth for folding a task's tool
3
+ * activity into one summary line: `Edit×9 Read×24 Bash×11 +2 · 3 files`.
4
+ *
5
+ * A task detail used to enumerate turns and steps; a list row has no room for
6
+ * that, and a bare total ("44 tools") says nothing about the SHAPE of the
7
+ * work. The fold is a pure projection over per-tool counts the store already
8
+ * holds (sample_events kind='tool' grouped by tool_name) — no schema change,
9
+ * no new collection.
10
+ *
11
+ * Consumers (keep in the same commit):
12
+ * - GET /apme/tasks response `actionFold` field (bridge/src/apme/http.ts)
13
+ */
14
+ /** How many named tools the fold shows before collapsing the rest to `+N`.
15
+ * Four keeps the line inside one row beside its chips at the dashboard's
16
+ * default width. */
17
+ export const ACTION_FOLD_MAX_TOOLS = 4;
18
+ /** `mcp__claude-in-chrome__navigate` → `navigate`: the server prefix is
19
+ * provenance, not shape, and at row width it would crowd out every other
20
+ * tool. Non-MCP names pass through unchanged. */
21
+ export function foldToolName(name) {
22
+ const trimmed = name.trim();
23
+ if (!trimmed.toLowerCase().startsWith('mcp__'))
24
+ return trimmed;
25
+ const segments = trimmed.split('__').filter((s) => s.length > 0);
26
+ return segments.length > 0 ? segments[segments.length - 1] : trimmed;
27
+ }
28
+ /** Tool names that DISPATCH another agent (fan-out). Measured on the real
29
+ * store 2026-08-28: `Agent` (Claude Code subagent tool, 126 calls), `Task`
30
+ * (its pre-rename spelling), `task` (OpenCode's), `Workflow` (multi-agent
31
+ * orchestration — counted as ONE dispatch per call even though it fans out
32
+ * internally, because parent-side hooks see only the call). TaskCreate /
33
+ * TaskUpdate and friends are todo bookkeeping, not dispatch. */
34
+ export const DISPATCH_TOOL_NAMES = new Set(['Agent', 'Task', 'task', 'Workflow']);
35
+ /** Tool names that MESSAGE another agent (peer/teammate traffic). */
36
+ export const MESSAGING_TOOL_NAMES = new Set(['SendMessage']);
37
+ /**
38
+ * Count the coordination shape of a task from its per-tool counts: how often
39
+ * it fanned out to other agents and how often it messaged them. Returns null
40
+ * when the task did neither, so a plain single-agent task renders no
41
+ * coordination chip at all. Matches on RAW tool names (before `foldToolName`),
42
+ * since the dispatch/messaging sets are exact names, not display shapes.
43
+ */
44
+ export function agentCoordinationSummary(tools) {
45
+ let dispatches = 0;
46
+ let messages = 0;
47
+ for (const t of tools) {
48
+ if (!(t.count > 0))
49
+ continue;
50
+ if (DISPATCH_TOOL_NAMES.has(t.name.trim()))
51
+ dispatches += t.count;
52
+ if (MESSAGING_TOOL_NAMES.has(t.name.trim()))
53
+ messages += t.count;
54
+ }
55
+ if (dispatches === 0 && messages === 0)
56
+ return null;
57
+ return { dispatches, messages };
58
+ }
59
+ /**
60
+ * Fold tool counts into the summary line. Returns null when there is nothing
61
+ * to say (no tools and no files) — callers render nothing rather than an
62
+ * empty string, matching the retain-on-absent display rules elsewhere.
63
+ *
64
+ * Determinism: ties sort by name so the same task always folds to the same
65
+ * line (renderer output is used as identity in dedup paths — see the
66
+ * session-slot renderer rule in CLAUDE.md). The tie-break is a plain
67
+ * code-unit compare, NOT localeCompare: localeCompare's order depends on the
68
+ * host locale (and case-folds, so `Bash` vs `apply` flips), which both breaks
69
+ * determinism across machines and cannot be mirrored by Swift's `<`.
70
+ */
71
+ export function foldActionCounts(input) {
72
+ const merged = new Map();
73
+ for (const t of input.tools) {
74
+ const name = foldToolName(t.name);
75
+ if (!name || !(t.count > 0))
76
+ continue;
77
+ merged.set(name, (merged.get(name) ?? 0) + t.count);
78
+ }
79
+ const sorted = [...merged.entries()].sort((a, b) => (b[1] - a[1]) || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
80
+ const parts = [];
81
+ if (sorted.length > 0) {
82
+ const shown = sorted.slice(0, ACTION_FOLD_MAX_TOOLS);
83
+ const rest = sorted.length - shown.length;
84
+ parts.push(shown.map(([name, count]) => `${name}×${count}`).join(' ') + (rest > 0 ? ` +${rest}` : ''));
85
+ }
86
+ const files = input.filesTouched ?? 0;
87
+ if (files > 0)
88
+ parts.push(`${files} file${files === 1 ? '' : 's'}`);
89
+ if (parts.length === 0)
90
+ return null;
91
+ return parts.join(' · ');
92
+ }
93
+ //# sourceMappingURL=action-fold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-fold.js","sourceRoot":"","sources":["../src/action-fold.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * Two device classes, two vocabularies:
6
6
  *
7
- * 1. Self-connecting clients (Apple app, Android app, ESP32 WiFi panels, TUI)
7
+ * 1. Self-connecting clients (Apple app, Android app, ESP32 WiFi panels
8
+ * including InkDeck — and TUI)
8
9
  * own their link to the daemon: they discover via mDNS, connect, and
9
10
  * auto-reconnect. They surface the *phase* they are actually in:
10
11
  * - SEARCHING "Searching for AgentDeck..." (mDNS discovery, no target yet)
@@ -15,11 +16,19 @@
15
16
  * "No AgentDeck found on this network".
16
17
  *
17
18
  * 2. Daemon-rendered passive displays (Stream Deck keys/encoders, D200H,
18
- * Pixoo, Timebox, iDotMatrix, TRMNL) are painted BY the daemon/plugin and
19
+ * Pixoo, Timebox, iDotMatrix) are painted BY the daemon/plugin and
19
20
  * cannot search on their own. When the link is down they show a single
20
21
  * terminal state: "OFFLINE" (optionally with the "Open AgentDeck"
21
22
  * call-to-action subtitle). They never claim Connecting/Reconnecting.
22
23
  *
24
+ * Across both classes the VISUAL grammar stays stable: near-black field on
25
+ * emissive/color displays (paper white on e-ink), muted cyan AgentDeck
26
+ * identity/accent where color exists, one large status line, and at most one quiet
27
+ * explanatory line. Tiny pixel displays may replace the words with the same
28
+ * static offline badge, but must keep the field dark and non-animated. A live
29
+ * daemon with an empty roster is a different state ("No active sessions") and
30
+ * must not be inferred from the aggregate `state:'disconnected'` value alone.
31
+ *
23
32
  * Mirrors (update together — grep the literal when changing copy):
24
33
  * - Swift: apple/AgentDeck/UI/Monitor/ConnectionOverlay.swift (ConnectionLexicon)
25
34
  * - Kotlin: android/.../ui/common/ConnectionComponents.kt (ConnectionLexicon)
@@ -1 +1 @@
1
- {"version":3,"file":"connection-status.d.ts","sourceRoot":"","sources":["../src/connection-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GACvB,WAAW,GACX,YAAY,GACZ,cAAc,GACd,YAAY,GACZ,WAAW,CAAC;AAEhB,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAM9D,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAMtE,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AAEjD,sEAAsE;AACtE,eAAO,MAAM,uBAAuB,uCAAuC,CAAC;AAE5E,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,YAAY,CAAC;AAE/C,oEAAoE;AACpE,eAAO,MAAM,oBAAoB,mBAAmB,CAAC"}
1
+ {"version":3,"file":"connection-status.d.ts","sourceRoot":"","sources":["../src/connection-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GACvB,WAAW,GACX,YAAY,GACZ,cAAc,GACd,YAAY,GACZ,WAAW,CAAC;AAEhB,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAM9D,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAMtE,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AAEjD,sEAAsE;AACtE,eAAO,MAAM,uBAAuB,uCAAuC,CAAC;AAE5E,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,YAAY,CAAC;AAE/C,oEAAoE;AACpE,eAAO,MAAM,oBAAoB,mBAAmB,CAAC"}
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * Two device classes, two vocabularies:
6
6
  *
7
- * 1. Self-connecting clients (Apple app, Android app, ESP32 WiFi panels, TUI)
7
+ * 1. Self-connecting clients (Apple app, Android app, ESP32 WiFi panels
8
+ * including InkDeck — and TUI)
8
9
  * own their link to the daemon: they discover via mDNS, connect, and
9
10
  * auto-reconnect. They surface the *phase* they are actually in:
10
11
  * - SEARCHING "Searching for AgentDeck..." (mDNS discovery, no target yet)
@@ -15,11 +16,19 @@
15
16
  * "No AgentDeck found on this network".
16
17
  *
17
18
  * 2. Daemon-rendered passive displays (Stream Deck keys/encoders, D200H,
18
- * Pixoo, Timebox, iDotMatrix, TRMNL) are painted BY the daemon/plugin and
19
+ * Pixoo, Timebox, iDotMatrix) are painted BY the daemon/plugin and
19
20
  * cannot search on their own. When the link is down they show a single
20
21
  * terminal state: "OFFLINE" (optionally with the "Open AgentDeck"
21
22
  * call-to-action subtitle). They never claim Connecting/Reconnecting.
22
23
  *
24
+ * Across both classes the VISUAL grammar stays stable: near-black field on
25
+ * emissive/color displays (paper white on e-ink), muted cyan AgentDeck
26
+ * identity/accent where color exists, one large status line, and at most one quiet
27
+ * explanatory line. Tiny pixel displays may replace the words with the same
28
+ * static offline badge, but must keep the field dark and non-animated. A live
29
+ * daemon with an empty roster is a different state ("No active sessions") and
30
+ * must not be inferred from the aggregate `state:'disconnected'` value alone.
31
+ *
23
32
  * Mirrors (update together — grep the literal when changing copy):
24
33
  * - Swift: apple/AgentDeck/UI/Monitor/ConnectionOverlay.swift (ConnectionLexicon)
25
34
  * - Kotlin: android/.../ui/common/ConnectionComponents.kt (ConnectionLexicon)
@@ -1 +1 @@
1
- {"version":3,"file":"connection-status.js","sourceRoot":"","sources":["../src/connection-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAUH,qEAAqE;AACrE,MAAM,CAAC,MAAM,kBAAkB,GAAoC;IACjE,SAAS,EAAE,4BAA4B;IACvC,UAAU,EAAE,eAAe;IAC3B,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE,WAAW;CACvB,CAAC;AAEF,wEAAwE;AACxE,MAAM,CAAC,MAAM,0BAA0B,GAAoC;IACzE,SAAS,EAAE,cAAc;IACzB,UAAU,EAAE,eAAe;IAC3B,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE,WAAW;CACvB,CAAC;AAEF,uEAAuE;AACvE,MAAM,CAAC,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAEjD,sEAAsE;AACtE,MAAM,CAAC,MAAM,uBAAuB,GAAG,oCAAoC,CAAC;AAE5E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAAC;AAE/C,oEAAoE;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,gBAAgB,CAAC"}
1
+ {"version":3,"file":"connection-status.js","sourceRoot":"","sources":["../src/connection-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAUH,qEAAqE;AACrE,MAAM,CAAC,MAAM,kBAAkB,GAAoC;IACjE,SAAS,EAAE,4BAA4B;IACvC,UAAU,EAAE,eAAe;IAC3B,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE,WAAW;CACvB,CAAC;AAEF,wEAAwE;AACxE,MAAM,CAAC,MAAM,0BAA0B,GAAoC;IACzE,SAAS,EAAE,cAAc;IACzB,UAAU,EAAE,eAAe;IAC3B,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE,WAAW;CACvB,CAAC;AAEF,uEAAuE;AACvE,MAAM,CAAC,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAEjD,sEAAsE;AACtE,MAAM,CAAC,MAAM,uBAAuB,GAAG,oCAAoC,CAAC;AAE5E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAAC;AAE/C,oEAAoE;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,gBAAgB,CAAC"}
@@ -17,6 +17,10 @@ export interface KeySlot {
17
17
  export declare const GRID_COLS = 5;
18
18
  export interface DashState {
19
19
  state: string;
20
+ /** Explicit plugin↔daemon transport state. The daemon's aggregate
21
+ * `state:'disconnected'` also means "connected, but no active session", so
22
+ * it is not sufficient to select the OFFLINE surface. */
23
+ daemonConnected?: boolean;
20
24
  projectName: string;
21
25
  modelName: string;
22
26
  mode: string;
@@ -102,6 +106,13 @@ export interface UsageTankData {
102
106
  inactive?: boolean;
103
107
  }
104
108
  export declare function renderUsageGauge(data: UsageTankData): string;
109
+ /**
110
+ * Two rolling windows compacted into one physical key. The D200H reserves only
111
+ * the three keys left of its wide clock, so Claude 5H/7D + Codex 5H/7D cannot
112
+ * be represented as four independent tiles. Compacting a provider pair keeps
113
+ * every real window visible without stealing the clock or dropping a limit.
114
+ */
115
+ export declare function renderUsagePairGauge(agent: 'claude' | 'codex', windows: [UsageTankData, UsageTankData]): string;
105
116
  /**
106
117
  * Codex credit-based plans (e.g. `limit_id: "premium"`) report null 5h/7d
107
118
  * windows and convey usage via a credits balance instead. Render a flat readout
@@ -1 +1 @@
1
- {"version":3,"file":"d200h-layout.d.ts","sourceRoot":"","sources":["../src/d200h-layout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"d200h-layout.d.ts","sourceRoot":"","sources":["../src/d200h-layout.ts"],"names":[],"mappings":"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"}
@@ -9,9 +9,9 @@
9
9
  * Key position is addressed as `col_row` (e.g. "0_0", "3_2") — the same scheme
10
10
  * the D200H firmware and Ulanzi Studio both use for keys.
11
11
  */
12
- import { renderSessionSlot, renderEmptySlot, renderOptionButton, renderStopButton, renderEscButton, renderDetailInfo, renderBackButton, renderNextPageButton, renderInfoSlot, svgFrame, escSvgText, } from './svg-renderers/index.js';
12
+ import { renderSessionSlot, renderEmptySlot, renderOptionButton, renderStopButton, renderEscButton, renderDetailInfo, renderBackButton, renderNextPageButton, renderInfoSlot, renderDisconnectedSlot, svgFrame, escSvgText, } from './svg-renderers/index.js';
13
13
  import { sortSessions, foldCodexSessionsForDisplay } from './session-utils.js';
14
- import { Brand, UI } from './design-tokens.js';
14
+ import { Brand, Tide, UI } from './design-tokens.js';
15
15
  import { PASSIVE_OFFLINE_LABEL, OPEN_AGENTDECK_LABEL } from './connection-status.js';
16
16
  import { CLAUDE_LOGO_PATH, CODEX_LOGO_PATH } from './svg-renderers/agent-logos.js';
17
17
  import { formatScopedLabel, codexUsageFootnote, scopedLimitClaimsUsageKey, codexWindowsBeside } from './format-utils.js';
@@ -29,14 +29,16 @@ export const GRID_COLS = 5;
29
29
  * key falls out of the strip and flows back to sessions instead of leaving a
30
30
  * hole in the middle of the row.
31
31
  *
32
- * Its length also caps usage at three keys: a fourth tile (Codex 5H *and* 7D)
33
- * drops, Claude prioritised by `buildUsageTiles` order. That cap is why the
34
- * scoped per-model caps contribute at most ONE tile see `buildUsageTiles`.
32
+ * Its length caps usage at three physical keys. When four or five windows are
33
+ * present, `buildUsageTiles` compacts same-provider 5H+7D windows into a dual
34
+ * readout so no real limit is dropped. Scoped per-model caps still contribute
35
+ * at most one tile.
35
36
  */
36
37
  const USAGE_PREFERRED_POS = ['0_2', '1_2', '2_2'];
37
38
  export function parseState(evt) {
38
39
  return {
39
40
  state: evt?.state ?? 'DISCONNECTED',
41
+ daemonConnected: typeof evt?.daemonConnected === 'boolean' ? evt.daemonConnected : undefined,
40
42
  projectName: evt?.projectName ?? '',
41
43
  modelName: evt?.modelName ?? '',
42
44
  mode: evt?.mode ?? 'default',
@@ -265,6 +267,34 @@ export function renderUsageGauge(data) {
265
267
  + (reset ? `<text x="72" y="122" text-anchor="middle" font-family="Arial,sans-serif" font-size="17" font-weight="bold" fill="${resetColor}">${escXml(reset)}</text>` : '')
266
268
  + `</svg>`;
267
269
  }
270
+ /**
271
+ * Two rolling windows compacted into one physical key. The D200H reserves only
272
+ * the three keys left of its wide clock, so Claude 5H/7D + Codex 5H/7D cannot
273
+ * be represented as four independent tiles. Compacting a provider pair keeps
274
+ * every real window visible without stealing the clock or dropping a limit.
275
+ */
276
+ export function renderUsagePairGauge(agent, windows) {
277
+ const W = 144, H = 144, RX = 12;
278
+ const bg = `<rect width="${W}" height="${H}" rx="${RX}" fill="${UI.popupBgMid}"/>`;
279
+ const logo = usageBrandLogo(agent, 128, 16, 16, false);
280
+ const rows = windows.map((window, index) => {
281
+ const used = Math.max(0, Math.min(100, window.usedPercent));
282
+ const dim = window.stale === true || Boolean(window.footnote);
283
+ const ramp = usageRampColor(used, dim, window.inactive === true);
284
+ const y = index === 0 ? 0 : 72;
285
+ const reset = window.footnote || (window.stale ? 'stale' : formatResetCountdown(window.resetsAt));
286
+ const textColor = dim ? UI.ttyDim : Tide.s50;
287
+ const barW = Math.round(120 * used / 100);
288
+ return `<text x="12" y="${y + 29}" font-family="JetBrains Mono, monospace" font-size="18" font-weight="bold" fill="${textColor}">${escXml(window.label)}</text>`
289
+ + `<text x="100" y="${y + 29}" text-anchor="end" font-family="IBM Plex Sans, sans-serif" font-size="20" font-weight="bold" fill="${textColor}">${Math.round(used)}</text>`
290
+ + `<text x="102" y="${y + 29}" font-family="IBM Plex Sans, sans-serif" font-size="12" font-weight="bold" fill="${textColor}">%</text>`
291
+ + (reset ? `<text x="12" y="${y + 51}" font-family="JetBrains Mono, monospace" font-size="11" font-weight="bold" fill="${textColor}">${escXml(reset)}</text>` : '')
292
+ + `<rect x="12" y="${y + 62}" width="120" height="3" rx="1.5" fill="${UI.ttyFaint}"/>`
293
+ + (barW > 0 ? `<rect x="12" y="${y + 62}" width="${barW}" height="3" rx="1.5" fill="${ramp.fill}" opacity="${dim ? 0.55 : 1}"/>` : '');
294
+ }).join('');
295
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">`
296
+ + bg + `<rect x="8" y="71" width="128" height="1" fill="${UI.ttyFaint}"/>` + rows + logo + `</svg>`;
297
+ }
268
298
  /**
269
299
  * Codex credit-based plans (e.g. `limit_id: "premium"`) report null 5h/7d
270
300
  * windows and convey usage via a credits balance instead. Render a flat readout
@@ -300,27 +330,25 @@ export function renderCreditsTile(data) {
300
330
  function buildUsageTiles(state) {
301
331
  const action = { kind: 'command', command: { type: 'query_usage' } };
302
332
  const known = state.usageKnown !== false;
303
- const tiles = [];
333
+ const claudeWindows = [];
304
334
  if (known && state.fiveHourPercent != null) {
305
- tiles.push({ svg: renderUsageGauge({ agent: 'claude', window: '5h', label: '5H', usedPercent: state.fiveHourPercent, resetsAt: state.fiveHourResetsAt, known: true }), action });
335
+ claudeWindows.push({ agent: 'claude', window: '5h', label: '5H', usedPercent: state.fiveHourPercent, resetsAt: state.fiveHourResetsAt, known: true });
306
336
  }
307
337
  if (known && state.sevenDayPercent != null) {
308
- tiles.push({ svg: renderUsageGauge({ agent: 'claude', window: '7d', label: '7D', usedPercent: state.sevenDayPercent, resetsAt: state.sevenDayResetsAt, known: true }), action });
338
+ claudeWindows.push({ agent: 'claude', window: '7d', label: '7D', usedPercent: state.sevenDayPercent, resetsAt: state.sevenDayResetsAt, known: true });
309
339
  }
310
340
  // At most ONE scoped tile — the worst-sorted cap (active desc, then percent
311
341
  // desc), rendered muted when it isn't the binding one.
312
342
  //
313
- // The usage strip is three keys wide (USAGE_PREFERRED_POS) and `buildSessionDeck`
314
- // drops every tile past it, so scoped tiles never stack: only [0] could ever
315
- // reach a key, and building the rest is dead work. Paging through them lives on
316
- // the SD+ encoder, which has room for it.
343
+ // The usage strip is three keys wide (USAGE_PREFERRED_POS), so scoped tiles
344
+ // never stack: only [0] can reach a key, and building the rest is dead work.
345
+ // Same-provider rolling windows compact below when the total would overflow;
346
+ // paging through the remaining scoped caps lives on the SD+ encoder.
317
347
  //
318
- // Whether it takes a key at all is `scopedLimitClaimsUsageKey`, and what Codex
319
- // keeps once it has is `codexWindowsBeside` — both shared with the Stream Deck
320
- // keypad strip so the two decks can't disagree about which limit the user is
321
- // looking at. An ACTIVE cap is placed ahead of Codex and TAKES one of its keys
322
- // (it replaces, never stacks); an inactive one only lands when Codex reports
323
- // nothing at all.
348
+ // Inclusion comes from `scopedLimitClaimsUsageKey`, and the Codex windows to
349
+ // retain from `codexWindowsBeside` — both shared with the Stream Deck keypad.
350
+ // Active caps sort ahead of Codex; inactive caps follow it. Capacity pressure
351
+ // is solved by compacting provider pairs below, never by deleting a window.
324
352
  const cx = state.codexRateLimits;
325
353
  const allCodexWindows = [cx?.primary, cx?.secondary].filter((w) => w != null);
326
354
  const worstScoped = known ? state.scopedLimits?.[0] : undefined;
@@ -329,23 +357,46 @@ function buildUsageTiles(state) {
329
357
  const scopedTile = scopedClaims && worstScoped
330
358
  ? { svg: renderUsageGauge({ agent: 'claude', window: '7d', label: formatScopedLabel(worstScoped.label, 6), usedPercent: worstScoped.percent, resetsAt: worstScoped.resetsAt, known: true, inactive: worstScoped.active !== true }), action }
331
359
  : undefined;
332
- if (scopedTile && worstScoped?.active === true)
333
- tiles.push(scopedTile);
334
360
  // Codex windows carry the same short "5H"/"7D" labels — the brand dot conveys
335
361
  // the agent, not a "CX " prefix. Label each present window by its own length
336
362
  // (windowMinutes), never by slot: Codex now sometimes reports the weekly
337
363
  // (10080-min) window as `primary` with `secondary` null, so a slot-based "7D
338
364
  // = secondary" would drop the gauge entirely.
339
- for (const w of codexWindows) {
340
- tiles.push({ svg: renderUsageGauge({ agent: 'codex', window: usageWindowKind(w.windowMinutes), label: usageWindowLabel(w.windowMinutes) || '5H', usedPercent: w.usedPercent, resetsAt: w.resetsAt, known: true, stale: w.stale === true, footnote: codexUsageFootnote(w, cx?.capturedAt)?.text }), action });
341
- }
365
+ const codexWindowData = codexWindows.map((w) => ({
366
+ agent: 'codex',
367
+ window: usageWindowKind(w.windowMinutes),
368
+ label: usageWindowLabel(w.windowMinutes) || '5H',
369
+ usedPercent: w.usedPercent,
370
+ resetsAt: w.resetsAt,
371
+ known: true,
372
+ stale: w.stale === true,
373
+ footnote: codexUsageFootnote(w, cx?.capturedAt)?.text,
374
+ }));
375
+ // Compact only as much as the fixed three-key strip requires. In the common
376
+ // Plus shape, Claude keeps its two familiar tiles and Codex 5H+7D share one.
377
+ // If a binding scoped cap also exists, Claude compacts too, preserving all
378
+ // five readings in three physical keys.
379
+ const creditsTile = !cx?.primary && !cx?.secondary && (cx?.credits || cx?.limitId)
380
+ ? { svg: renderCreditsTile({ limitId: cx.limitId, balance: cx.credits?.balance, unlimited: cx.credits?.unlimited }), action }
381
+ : undefined;
382
+ const logicalCount = claudeWindows.length + codexWindowData.length + (scopedTile ? 1 : 0) + (creditsTile ? 1 : 0);
383
+ const compactCodex = logicalCount > USAGE_PREFERRED_POS.length && codexWindowData.length === 2;
384
+ const afterCodex = logicalCount - (compactCodex ? 1 : 0);
385
+ const compactClaude = afterCodex > USAGE_PREFERRED_POS.length && claudeWindows.length === 2;
386
+ const cellsFor = (agent, windows, compact) => {
387
+ if (compact && windows.length === 2) {
388
+ return [{ svg: renderUsagePairGauge(agent, [windows[0], windows[1]]), action }];
389
+ }
390
+ return windows.map((window) => ({ svg: renderUsageGauge(window), action }));
391
+ };
392
+ const tiles = cellsFor('claude', claudeWindows, compactClaude);
393
+ if (scopedTile && worstScoped?.active === true)
394
+ tiles.push(scopedTile);
395
+ tiles.push(...cellsFor('codex', codexWindowData, compactCodex));
396
+ if (creditsTile)
397
+ tiles.push(creditsTile);
342
398
  if (scopedTile && worstScoped?.active !== true)
343
399
  tiles.push(scopedTile);
344
- // Credit-based Codex plan: no windows, show the credits balance instead so the
345
- // Codex usage doesn't silently vanish.
346
- if (!cx?.primary && !cx?.secondary && (cx?.credits || cx?.limitId)) {
347
- tiles.push({ svg: renderCreditsTile({ limitId: cx.limitId, balance: cx.credits?.balance, unlimited: cx.credits?.unlimited }), action });
348
- }
349
400
  return tiles;
350
401
  }
351
402
  /**
@@ -609,10 +660,10 @@ export function buildSessionDeck(stateEvt, view, positions) {
609
660
  const animated = view.animated ?? false;
610
661
  if (slots.length === 0)
611
662
  return out;
612
- // Daemon down → OFFLINE hero on the center key, rest dim. Every key launches
613
- // the companion app on press (parity with the SD/SD+ keypad). If AgentDeck
614
- // isn't installed yet, the hero shows the install command so a marketplace-only
615
- // user knows the daemon is the missing piece.
663
+ // Daemon down → one deck-spanning OFFLINE card. Every key launches the
664
+ // companion app on press (parity with the SD/SD+ keypad). The Ulanzi plugin
665
+ // supplies `daemonConnected`; the legacy state+empty fallback remains for
666
+ // direct callers and older previews.
616
667
  //
617
668
  // Gate on an EMPTY session list, not the top-level state alone: the daemon
618
669
  // reports `state:'disconnected'` whenever no managed/focused session is active
@@ -621,10 +672,24 @@ export function buildSessionDeck(stateEvt, view, positions) {
621
672
  // `sessions_list`, so showing OFFLINE while sessions are present would hide a
622
673
  // live deck. Genuine link-down funnels through the store as DISCONNECTED with
623
674
  // an empty list, so this still fires for a truly absent daemon.
624
- if ((state.state === 'DISCONNECTED' || state.state === 'disconnected') && state.allSessions.length === 0) {
625
- const hero = Math.floor(slots.length / 2);
626
- slots.forEach((pos, i) => out.set(pos, {
627
- svg: i === hero ? renderInfoSlot(PASSIVE_OFFLINE_LABEL, OPEN_AGENTDECK_LABEL, 'agentdeck', 'brand', 'npx @agentdeck/setup') : renderEmptySlot(),
675
+ const daemonOffline = state.daemonConnected === false
676
+ || (state.daemonConnected == null
677
+ && (state.state === 'DISCONNECTED' || state.state === 'disconnected')
678
+ && state.allSessions.length === 0);
679
+ if (daemonOffline) {
680
+ const coordinates = slots.map((pos) => {
681
+ const [col = 0, row = 0] = pos.split('_').map(Number);
682
+ return { pos, col, row };
683
+ });
684
+ const cols = Math.max(1, ...coordinates.map((p) => p.col + 1));
685
+ const rows = Math.max(1, ...coordinates.map((p) => p.row + 1));
686
+ coordinates.forEach(({ pos, col, row }) => out.set(pos, {
687
+ svg: renderDisconnectedSlot({
688
+ kind: 'open-app',
689
+ label: PASSIVE_OFFLINE_LABEL,
690
+ subtitle: OPEN_AGENTDECK_LABEL,
691
+ col, row, cols, rows,
692
+ }),
628
693
  action: { kind: 'launch' },
629
694
  }));
630
695
  return out;
@@ -639,8 +704,8 @@ function buildList(state, view, slots, animFrame, animated, out) {
639
704
  // Pin the bottom-row usage strip to the global quota gauges (opt-in,
640
705
  // water-tank style). On the D200H the strip sits just left of the native clock
641
706
  // widget; on classic Stream Deck it replaces the encoder LCD this surface
642
- // lacks. We reserve as many keys as we have usage tiles (Claude 5H/7D + any
643
- // Codex window), but never more than the strip is wide, and never more than
707
+ // lacks. We reserve as many keys as we have usage tiles (same-provider pairs
708
+ // compact when necessary), but never more than the strip is wide or more than
644
709
  // `slots.length - 1` so at least one key stays for sessions — the latter is
645
710
  // the fix for the old `slots.length >= 6` gate that silently dropped ALL usage
646
711
  // when the user placed only a few AgentDeck keys. Reserved keys are pinned on
@@ -665,8 +730,13 @@ function buildList(state, view, slots, animFrame, animated, out) {
665
730
  // Positions left for sessions / NEXT after carving out usage.
666
731
  const freeSlots = slots.filter((pos) => !usageHere.has(pos));
667
732
  if (sessions.length === 0) {
733
+ const ready = [
734
+ renderInfoSlot('HUB READY', 'connected', 'hub', 'ready'),
735
+ renderInfoSlot('NO SESSION', 'waiting', 'no-session', 'idle'),
736
+ renderInfoSlot('AgentDeck', 'idle', 'agentdeck', 'agent'),
737
+ ];
668
738
  freeSlots.forEach((pos, i) => out.set(pos, {
669
- svg: i === 0 ? renderInfoSlot('NO SESSION', 'waiting', 'activity', 'info') : renderEmptySlot(),
739
+ svg: ready[i] ?? renderEmptySlot(),
670
740
  action: null,
671
741
  }));
672
742
  for (const [pos, cell] of usageHere)
@@ -732,10 +802,26 @@ function buildDetail(state, stateEvt, view, slots, animFrame, animated, out) {
732
802
  // the daemon-global model from another agent. Only a matching focused event
733
803
  // may override the sessions_list row.
734
804
  const model = focused ? (state.modelName || sess?.modelName || '') : (sess?.modelName || '');
735
- const heroSess = sess ?? {
805
+ const baseHero = sess ?? {
736
806
  id: sid, port: 0, alive: true, projectName: state.projectName,
737
807
  agentType: state.agentType, state: sState, modelName: model,
738
808
  };
809
+ // The hero cell is the ONLY place this view can say what is being asked — the
810
+ // option keys below carry labels ("Allow once"), never a subject. `question`
811
+ // was resolved above for the press echo alone, so the detail view rendered
812
+ // three live buttons over no statement of what they applied to. Hand the
813
+ // resolved prompt to the renderer, and take the supporting lines from the
814
+ // sessions_list row, which is the SSOT for an observed/virtual session's
815
+ // prompt (a focused `state_update` is the daemon's global snapshot and would
816
+ // blank them).
817
+ const heroSess = (question || sess?.questionDetail)
818
+ ? {
819
+ ...baseHero,
820
+ state: sState,
821
+ ...(question ? { question } : {}),
822
+ ...(sess?.questionDetail ? { questionDetail: sess.questionDetail } : {}),
823
+ }
824
+ : baseHero;
739
825
  const first = slots[0];
740
826
  const last = slots[slots.length - 1];
741
827
  out.set(first, { svg: renderBackButton(), action: { kind: 'back' } });