@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.
- package/dist/action-fold.d.ts +71 -0
- package/dist/action-fold.d.ts.map +1 -0
- package/dist/action-fold.js +93 -0
- package/dist/action-fold.js.map +1 -0
- package/dist/connection-status.d.ts +11 -2
- package/dist/connection-status.d.ts.map +1 -1
- package/dist/connection-status.js +11 -2
- package/dist/connection-status.js.map +1 -1
- package/dist/d200h-layout.d.ts +11 -0
- package/dist/d200h-layout.d.ts.map +1 -1
- package/dist/d200h-layout.js +126 -40
- package/dist/d200h-layout.js.map +1 -1
- package/dist/esp32-boards.d.ts.map +1 -1
- package/dist/esp32-boards.js +34 -0
- package/dist/esp32-boards.js.map +1 -1
- package/dist/eval-schema.d.ts +72 -1
- package/dist/eval-schema.d.ts.map +1 -1
- package/dist/eval-schema.js +23 -0
- package/dist/eval-schema.js.map +1 -1
- package/dist/format-utils.d.ts +9 -18
- package/dist/format-utils.d.ts.map +1 -1
- package/dist/format-utils.js +9 -18
- package/dist/format-utils.js.map +1 -1
- package/dist/gateway-protocol.d.ts +104 -23
- package/dist/gateway-protocol.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model-provider.d.ts +10 -1
- package/dist/model-provider.d.ts.map +1 -1
- package/dist/model-provider.js +22 -0
- package/dist/model-provider.js.map +1 -1
- package/dist/openclaw-approval.d.ts.map +1 -1
- package/dist/openclaw-approval.js +9 -2
- package/dist/openclaw-approval.js.map +1 -1
- package/dist/pairing-code.d.ts +69 -0
- package/dist/pairing-code.d.ts.map +1 -1
- package/dist/pairing-code.js +138 -0
- package/dist/pairing-code.js.map +1 -1
- package/dist/protocol.d.ts +157 -5
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +4 -0
- package/dist/protocol.js.map +1 -1
- package/dist/sample.d.ts +4 -0
- package/dist/sample.d.ts.map +1 -1
- package/dist/svg-renderers/session-slot-renderer.d.ts +36 -0
- package/dist/svg-renderers/session-slot-renderer.d.ts.map +1 -1
- package/dist/svg-renderers/session-slot-renderer.js +109 -3
- package/dist/svg-renderers/session-slot-renderer.js.map +1 -1
- package/dist/task-title.d.ts +51 -0
- package/dist/task-title.d.ts.map +1 -0
- package/dist/task-title.js +111 -0
- package/dist/task-title.js.map +1 -0
- package/dist/telemetry-envelope.d.ts +11 -1
- package/dist/telemetry-envelope.d.ts.map +1 -1
- package/dist/telemetry-envelope.js.map +1 -1
- package/dist/timeline-icons.d.ts.map +1 -1
- package/dist/timeline-icons.js +12 -0
- package/dist/timeline-icons.js.map +1 -1
- package/dist/timeline-task-display.d.ts +8 -4
- package/dist/timeline-task-display.d.ts.map +1 -1
- package/dist/timeline-task-display.js +9 -3
- package/dist/timeline-task-display.js.map +1 -1
- package/dist/timeline.d.ts +12 -1
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js.map +1 -1
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"}
|
package/dist/d200h-layout.d.ts
CHANGED
|
@@ -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":"
|
|
1
|
+
{"version":3,"file":"d200h-layout.d.ts","sourceRoot":"","sources":["../src/d200h-layout.ts"],"names":[],"mappings":"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"}
|
package/dist/d200h-layout.js
CHANGED
|
@@ -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
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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
|
|
333
|
+
const claudeWindows = [];
|
|
304
334
|
if (known && state.fiveHourPercent != null) {
|
|
305
|
-
|
|
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
|
-
|
|
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)
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
// the
|
|
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
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
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
|
-
|
|
340
|
-
|
|
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 →
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
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
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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 (
|
|
643
|
-
//
|
|
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
|
|
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
|
|
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' } });
|