@agentdeck/shared 0.2.3 → 1.0.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 (57) hide show
  1. package/dist/command-builders.d.ts +2 -1
  2. package/dist/command-builders.d.ts.map +1 -1
  3. package/dist/command-builders.js +3 -0
  4. package/dist/command-builders.js.map +1 -1
  5. package/dist/creature-layout.d.ts +18 -0
  6. package/dist/creature-layout.d.ts.map +1 -0
  7. package/dist/creature-layout.js +131 -0
  8. package/dist/creature-layout.js.map +1 -0
  9. package/dist/d200h-layout.d.ts +15 -0
  10. package/dist/d200h-layout.d.ts.map +1 -1
  11. package/dist/d200h-layout.js +222 -41
  12. package/dist/d200h-layout.js.map +1 -1
  13. package/dist/design-tokens.d.ts +2 -0
  14. package/dist/design-tokens.d.ts.map +1 -1
  15. package/dist/design-tokens.js +1 -0
  16. package/dist/design-tokens.js.map +1 -1
  17. package/dist/eval-schema.d.ts +7 -1
  18. package/dist/eval-schema.d.ts.map +1 -1
  19. package/dist/eval-schema.js.map +1 -1
  20. package/dist/format-utils.d.ts +13 -0
  21. package/dist/format-utils.d.ts.map +1 -1
  22. package/dist/format-utils.js +25 -0
  23. package/dist/format-utils.js.map +1 -1
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/protocol.d.ts +48 -3
  29. package/dist/protocol.d.ts.map +1 -1
  30. package/dist/protocol.js.map +1 -1
  31. package/dist/svg-renderers/agent-logos.d.ts +4 -8
  32. package/dist/svg-renderers/agent-logos.d.ts.map +1 -1
  33. package/dist/svg-renderers/agent-logos.js +8 -38
  34. package/dist/svg-renderers/agent-logos.js.map +1 -1
  35. package/dist/svg-renderers/session-slot-renderer.d.ts +12 -0
  36. package/dist/svg-renderers/session-slot-renderer.d.ts.map +1 -1
  37. package/dist/svg-renderers/session-slot-renderer.js +32 -1
  38. package/dist/svg-renderers/session-slot-renderer.js.map +1 -1
  39. package/dist/terrarium-rules.d.ts +46 -0
  40. package/dist/terrarium-rules.d.ts.map +1 -0
  41. package/dist/terrarium-rules.js +63 -0
  42. package/dist/terrarium-rules.js.map +1 -0
  43. package/dist/timeline-icons.d.ts +15 -5
  44. package/dist/timeline-icons.d.ts.map +1 -1
  45. package/dist/timeline-icons.js +19 -6
  46. package/dist/timeline-icons.js.map +1 -1
  47. package/dist/timeline-task-display.d.ts +66 -0
  48. package/dist/timeline-task-display.d.ts.map +1 -0
  49. package/dist/timeline-task-display.js +94 -0
  50. package/dist/timeline-task-display.js.map +1 -0
  51. package/dist/timeline.d.ts +29 -0
  52. package/dist/timeline.d.ts.map +1 -1
  53. package/dist/timeline.js +66 -5
  54. package/dist/timeline.js.map +1 -1
  55. package/dist/trmnl-layout.d.ts.map +1 -0
  56. package/dist/trmnl-layout.js.map +1 -0
  57. package/package.json +2 -1
@@ -1,4 +1,4 @@
1
- import type { ResponseCommand, SelectOptionCommand, NavigateOptionCommand, SendPromptCommand, SwitchModeCommand, InterruptCommand, EscapeCommand, VoiceCommand, QueryUsageCommand, QuerySessionTimelineCommand, DiagCommand, UtilityCommand, SwitchAgentCommand, FocusSessionCommand, ClearSessionFocusCommand, SessionCommand, ClientRegisterCommand, ApmeVibeFeedbackCommand, ApmeRecommendCommand, PermissionDecisionCommand, Esp32OtaAckCommand, Esp32OtaErrorCommand } from "./protocol.js";
1
+ import type { ResponseCommand, SelectOptionCommand, NavigateOptionCommand, SendPromptCommand, SwitchModeCommand, InterruptCommand, EscapeCommand, VoiceCommand, QueryUsageCommand, QuerySessionTimelineCommand, DiagCommand, UtilityCommand, SwitchAgentCommand, FocusSessionCommand, ClearSessionFocusCommand, SessionCommand, ClientRegisterCommand, ApmeVibeFeedbackCommand, ApmeRecommendCommand, PermissionDecisionCommand, ReviewRunCommand, Esp32OtaAckCommand, Esp32OtaErrorCommand } from "./protocol.js";
2
2
  export declare const agentCommand: {
3
3
  readonly respond: (value: string) => ResponseCommand;
4
4
  readonly selectOption: (index: number, sessionId?: string) => SelectOptionCommand;
@@ -29,6 +29,7 @@ export declare const agentCommand: {
29
29
  readonly apmeVibe: (runId: string, verdict: "approve" | "reject" | "neutral", note?: string) => ApmeVibeFeedbackCommand;
30
30
  readonly apmeRecommend: (taskKind?: string, budgetUsd?: number, latencyBudgetMs?: number, preferLocal?: boolean) => ApmeRecommendCommand;
31
31
  readonly permissionDecision: (requestId: string, decision: "allow" | "deny") => PermissionDecisionCommand;
32
+ readonly reviewRun: (sessionId: string) => ReviewRunCommand;
32
33
  readonly esp32OtaAck: (otaId: string, stage: "begin" | "chunk" | "end" | "abort", seq?: number, offset?: number, written?: number) => Esp32OtaAckCommand;
33
34
  readonly esp32OtaError: (error: string, otaId?: string, stage?: string) => Esp32OtaErrorCommand;
34
35
  };
@@ -1 +1 @@
1
- {"version":3,"file":"command-builders.d.ts","sourceRoot":"","sources":["../src/command-builders.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,2BAA2B,EAC3B,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,oBAAoB,EACrB,MAAM,eAAe,CAAC;AAEvB,eAAO,MAAM,YAAY;8BACR,MAAM,KAAG,eAAe;mCAGnB,MAAM,cAAc,MAAM,KAAG,mBAAmB;yCAG1C,IAAI,GAAG,MAAM,KAAG,qBAAqB;gCAG9C,MAAM,KAAG,iBAAiB;iCAGzB,MAAM,GAAG,aAAa,GAAG,SAAS,KAAG,iBAAiB;8BAG3D,gBAAgB;2BAGnB,aAAa;6BAGT,OAAO,GAAG,MAAM,GAAG,QAAQ,KAAG,YAAY;+BAG1C,iBAAiB;+CAGC,MAAM,KAAG,2BAA2B;4BAGvD,MAAM,GAAG,SAAS,KAAG,WAAW;+BAG7B,eAAe,GAAG,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,GAAG,YAAY,GAAG,YAAY,UAAU,MAAM,KAAG,cAAc;kCAGtI,UAAU,GAAG,aAAa,KAAG,kBAAkB;uCAG1C,MAAM,KAAG,mBAAmB;sCAG/B,wBAAwB;yCAGnB,MAAM,WAAW;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAAE,KAAG,cAAc;0CAG1E,MAAM,gBAAgB,MAAM,YAAY,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;KAAE,CAAC,KAAG,qBAAqB;+BAGjK,MAAM,WAAW,SAAS,GAAG,QAAQ,GAAG,SAAS,SAAS,MAAM,KAAG,uBAAuB;wCAGjF,MAAM,cAAc,MAAM,oBAAoB,MAAM,gBAAgB,OAAO,KAAG,oBAAoB;6CAG7F,MAAM,YAAY,OAAO,GAAG,MAAM,KAAG,yBAAyB;kCAGzE,MAAM,SAAS,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,QAAQ,MAAM,WAAW,MAAM,YAAY,MAAM,KAAG,kBAAkB;oCAGtH,MAAM,UAAU,MAAM,UAAU,MAAM,KAAG,oBAAoB;CAG1E,CAAC"}
1
+ {"version":3,"file":"command-builders.d.ts","sourceRoot":"","sources":["../src/command-builders.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,2BAA2B,EAC3B,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACrB,MAAM,eAAe,CAAC;AAEvB,eAAO,MAAM,YAAY;8BACR,MAAM,KAAG,eAAe;mCAGnB,MAAM,cAAc,MAAM,KAAG,mBAAmB;yCAG1C,IAAI,GAAG,MAAM,KAAG,qBAAqB;gCAG9C,MAAM,KAAG,iBAAiB;iCAGzB,MAAM,GAAG,aAAa,GAAG,SAAS,KAAG,iBAAiB;8BAG3D,gBAAgB;2BAGnB,aAAa;6BAGT,OAAO,GAAG,MAAM,GAAG,QAAQ,KAAG,YAAY;+BAG1C,iBAAiB;+CAGC,MAAM,KAAG,2BAA2B;4BAGvD,MAAM,GAAG,SAAS,KAAG,WAAW;+BAG7B,eAAe,GAAG,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,GAAG,YAAY,GAAG,YAAY,UAAU,MAAM,KAAG,cAAc;kCAGtI,UAAU,GAAG,aAAa,KAAG,kBAAkB;uCAG1C,MAAM,KAAG,mBAAmB;sCAG/B,wBAAwB;yCAGnB,MAAM,WAAW;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAAE,KAAG,cAAc;0CAG1E,MAAM,gBAAgB,MAAM,YAAY,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;KAAE,CAAC,KAAG,qBAAqB;+BAGjK,MAAM,WAAW,SAAS,GAAG,QAAQ,GAAG,SAAS,SAAS,MAAM,KAAG,uBAAuB;wCAGjF,MAAM,cAAc,MAAM,oBAAoB,MAAM,gBAAgB,OAAO,KAAG,oBAAoB;6CAG7F,MAAM,YAAY,OAAO,GAAG,MAAM,KAAG,yBAAyB;oCAGvE,MAAM,KAAG,gBAAgB;kCAG3B,MAAM,SAAS,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,QAAQ,MAAM,WAAW,MAAM,YAAY,MAAM,KAAG,kBAAkB;oCAGtH,MAAM,UAAU,MAAM,UAAU,MAAM,KAAG,oBAAoB;CAG1E,CAAC"}
@@ -65,6 +65,9 @@ export const agentCommand = {
65
65
  permissionDecision(requestId, decision) {
66
66
  return { type: "permission_decision", requestId, decision };
67
67
  },
68
+ reviewRun(sessionId) {
69
+ return { type: "review_run", sessionId };
70
+ },
68
71
  esp32OtaAck(otaId, stage, seq, offset, written) {
69
72
  return { type: "esp32_ota_ack", otaId, stage, seq, offset, written };
70
73
  },
@@ -1 +1 @@
1
- {"version":3,"file":"command-builders.js","sourceRoot":"","sources":["../src/command-builders.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"command-builders.js","sourceRoot":"","sources":["../src/command-builders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /** One creature's placement within its agent-type band. */
2
+ export interface CreatureSlot {
3
+ /** Center X as a fraction of world width. */
4
+ x: number;
5
+ /** Center Y as a fraction of world height. */
6
+ y: number;
7
+ /** Crowd-driven size multiplier — shrinks as a row fills up. */
8
+ scale: number;
9
+ }
10
+ /** Coding agents (Claude Code) — left-center band. */
11
+ export declare function layoutOctopuses(count: number): CreatureSlot[];
12
+ /** Codex clouds — float in the upper-center, above the octopuses. */
13
+ export declare function layoutCloudCreatures(count: number): CreatureSlot[];
14
+ /** OpenCode rings — mid-center band. */
15
+ export declare function layoutOpenCodeCreatures(count: number): CreatureSlot[];
16
+ /** Antigravity peak/arc marks — upper-right band. */
17
+ export declare function layoutAntigravityCreatures(count: number): CreatureSlot[];
18
+ //# sourceMappingURL=creature-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creature-layout.d.ts","sourceRoot":"","sources":["../src/creature-layout.ts"],"names":[],"mappings":"AAYA,2DAA2D;AAC3D,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,CAAC,EAAE,MAAM,CAAC;IACV,8CAA8C;IAC9C,CAAC,EAAE,MAAM,CAAC;IACV,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAY7D;AAED,qEAAqE;AACrE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAYlE;AAED,wCAAwC;AACxC,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAYrE;AAED,qDAAqD;AACrD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAYxE"}
@@ -0,0 +1,131 @@
1
+ // creature-layout.ts — Multi-session creature positioning.
2
+ //
3
+ // Third mirror of the canonical band layout. The other two:
4
+ // android/app/src/main/kotlin/dev/agentdeck/terrarium/CreatureLayout.kt
5
+ // apple/AgentDeck/Terrarium/CreatureLayout.swift
6
+ // Keep the band constants, row rules, and the overlap cap identical across all
7
+ // three — a divergence shows up as the same session set drawn in different
8
+ // places on the Pixoo depending on which daemon is driving it.
9
+ //
10
+ // Kotlin/Swift compute in Float and this mirror in double; the sub-1e-7 drift
11
+ // that introduces cannot survive the rounding to a 64px device grid.
12
+ /** Coding agents (Claude Code) — left-center band. */
13
+ export function layoutOctopuses(count) {
14
+ return layoutBand({
15
+ count,
16
+ xMin: 0.20,
17
+ xMax: 0.50,
18
+ frontY: 0.42,
19
+ backY: 0.52,
20
+ singleRowLimit: 4,
21
+ baseScale: 1.0,
22
+ minScale: 0.58,
23
+ creatureWidth: 0.11,
24
+ });
25
+ }
26
+ /** Codex clouds — float in the upper-center, above the octopuses. */
27
+ export function layoutCloudCreatures(count) {
28
+ return layoutBand({
29
+ count,
30
+ xMin: 0.30,
31
+ xMax: 0.55,
32
+ frontY: 0.16,
33
+ backY: 0.28,
34
+ singleRowLimit: 3,
35
+ baseScale: 0.98,
36
+ minScale: 0.56,
37
+ creatureWidth: 0.080,
38
+ });
39
+ }
40
+ /** OpenCode rings — mid-center band. */
41
+ export function layoutOpenCodeCreatures(count) {
42
+ return layoutBand({
43
+ count,
44
+ xMin: 0.45,
45
+ xMax: 0.68,
46
+ frontY: 0.34,
47
+ backY: 0.46,
48
+ singleRowLimit: 3,
49
+ baseScale: 0.96,
50
+ minScale: 0.56,
51
+ creatureWidth: 0.078,
52
+ });
53
+ }
54
+ /** Antigravity peak/arc marks — upper-right band. */
55
+ export function layoutAntigravityCreatures(count) {
56
+ return layoutBand({
57
+ count,
58
+ xMin: 0.58,
59
+ xMax: 0.82,
60
+ frontY: 0.22,
61
+ backY: 0.34,
62
+ singleRowLimit: 3,
63
+ baseScale: 0.96,
64
+ minScale: 0.56,
65
+ creatureWidth: 0.096,
66
+ });
67
+ }
68
+ /**
69
+ * Hard floor for the crowd-driven shrink. Below the per-band `minScale` so
70
+ * tightly packed bands can still shrink enough to honor the overlap cap before
71
+ * we give up and accept brief overlap.
72
+ */
73
+ const CROWDED_MIN_SCALE = 0.40;
74
+ /**
75
+ * Max fraction of a creature's width that two neighbors may overlap. 0.5 →
76
+ * centers stay at least half a body-width apart (≤50% overlap).
77
+ */
78
+ const MAX_OVERLAP_FRACTION = 0.5;
79
+ function layoutBand(spec) {
80
+ const { count, xMin, xMax, frontY, backY, singleRowLimit, baseScale, minScale, creatureWidth } = spec;
81
+ if (count <= 0)
82
+ return [];
83
+ const rows = count <= singleRowLimit ? 1 : count <= singleRowLimit * 2 ? 2 : 3;
84
+ const scale = Math.max(minScale, baseScale - Math.max(0, count - 1) * 0.055);
85
+ const rowCounts = distribute(count, rows);
86
+ const slots = [];
87
+ let absoluteIndex = 0;
88
+ for (let row = 0; row < rows; row++) {
89
+ const rowCount = rowCounts[row];
90
+ if (rowCount <= 0)
91
+ continue;
92
+ const rowT = rows === 1 ? 0 : row / (rows - 1);
93
+ const rowY = frontY + (backY - frontY) * rowT;
94
+ const rowInset = 0.015 + row * 0.02;
95
+ const rowMinX = xMin + rowInset;
96
+ const rowMaxX = xMax - rowInset;
97
+ // Alternating jitter magnitude — constant within a row.
98
+ const spread = Math.max(0.003, Math.min(0.012, (rowMaxX - rowMinX) / Math.max(rowCount * 5, 1)));
99
+ // Overlap cap: keep neighbor center-spacing ≥ MAX_OVERLAP_FRACTION of the
100
+ // on-screen body width so creatures never overlap by more than ~half. When
101
+ // the band is too tight to honor that at the count-based scale, shrink every
102
+ // creature in the row by the same ratio (down to CROWDED_MIN_SCALE) instead
103
+ // of letting them pile up.
104
+ let rowScale = Math.max(minScale, scale - row * 0.04);
105
+ if (rowCount >= 2) {
106
+ // Worst-case gap after the alternating jitter squeezes a pair.
107
+ const spacing = (rowMaxX - rowMinX) / (rowCount - 1) - 2 * spread;
108
+ const overlapCapScale = Math.max(0, spacing) / (MAX_OVERLAP_FRACTION * creatureWidth);
109
+ rowScale = Math.max(CROWDED_MIN_SCALE, Math.min(rowScale, overlapCapScale));
110
+ }
111
+ for (let col = 0; col < rowCount; col++) {
112
+ const t = rowCount === 1 ? 0.5 : col / (rowCount - 1);
113
+ const baseX = rowMinX + (rowMaxX - rowMinX) * t;
114
+ const phase = (absoluteIndex + row) % 2 === 0 ? -1 : 1;
115
+ const x = Math.min(xMax, Math.max(xMin, baseX + spread * phase));
116
+ const yJitter = ((absoluteIndex % 3) - 1) * 0.008;
117
+ slots.push({ x, y: rowY + yJitter, scale: rowScale });
118
+ absoluteIndex += 1;
119
+ }
120
+ }
121
+ return slots;
122
+ }
123
+ function distribute(count, rows) {
124
+ if (rows <= 0)
125
+ return [];
126
+ const result = new Array(rows).fill(Math.floor(count / rows));
127
+ for (let index = 0; index < count % rows; index++)
128
+ result[index] += 1;
129
+ return result;
130
+ }
131
+ //# sourceMappingURL=creature-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creature-layout.js","sourceRoot":"","sources":["../src/creature-layout.ts"],"names":[],"mappings":"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"}
@@ -53,6 +53,21 @@ export declare function parseState(evt: any): DashState;
53
53
  export declare function renderUsageButton(label: string, percent: number, color: string, known?: boolean): string;
54
54
  /** Wide merged slot (3_2) — 288×144 SVG. Two columns: 5H | 7D. Direct-HID only. */
55
55
  export declare function renderUsageWideSlot(fiveHourPct: number, sevenDayPct: number, known?: boolean): string;
56
+ /**
57
+ * Compact label for a rolling-window length in minutes, mirroring the Swift
58
+ * `TopologyRail.windowLabel`: whole days → "Nd" (10080 → "7D"), whole hours →
59
+ * "Nh" (300 → "5H"), else "Nm". Codex now sometimes reports the weekly window
60
+ * in the `primary` slot with `secondary` null, so usage gauges MUST label by a
61
+ * window's own length, never by its slot position — otherwise the 7D gauge
62
+ * silently vanishes and the weekly window is mislabelled "5H".
63
+ */
64
+ export declare function usageWindowLabel(windowMinutes: number | undefined): string;
65
+ /**
66
+ * Which gauge bucket ('5h' short vs '7d' long) a rolling window belongs to,
67
+ * derived from its length (≥ a day → long) so the clip id / styling is right
68
+ * regardless of whether the window arrived in the primary or secondary slot.
69
+ */
70
+ export declare function usageWindowKind(windowMinutes: number | undefined): '5h' | '7d';
56
71
  export interface UsageTankData {
57
72
  agent: 'claude' | 'codex';
58
73
  /** Rolling window this tile represents (drives the clip id + label fallback). */
@@ -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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"}
@@ -1,7 +1,7 @@
1
1
  /**
2
- * D200H / deck layout engine SHARED between the direct-HID renderer
3
- * (bridge/src/d200h/image-renderer.ts) and the Ulanzi Studio plugin
4
- * (plugin-ulanzi). Given the current agent state it computes, for a 5×3 key
2
+ * D200H / deck layout engine used by the Ulanzi Studio plugin
3
+ * (plugin-ulanzi) and Apple device previews. Given the current agent state it
4
+ * computes, for a 5×3 key
5
5
  * grid, what each key shows (SVG) and does (command) — reflowing dynamically:
6
6
  * idle → sessions; a focused awaiting session → its options + ESC; processing →
7
7
  * STOP; plus mode / model / usage / tokens / cost.
@@ -16,10 +16,22 @@ import { PASSIVE_OFFLINE_LABEL, OPEN_AGENTDECK_LABEL } from './connection-status
16
16
  import { CLAUDE_LOGO_PATH, CODEX_LOGO_PATH } from './svg-renderers/agent-logos.js';
17
17
  /** 5 columns × 3 rows. Physical key index == row * GRID_COLS + col. */
18
18
  export const GRID_COLS = 5;
19
- /** D200H usage placement: the 2×2 block directly above the wide bottom-right
20
- * button (cols 3–4, rows 0–1), in reading order. Cleaner than the trailing keys
21
- * Claude 5H/7D fill the top row, Codex 5H/7D the row below. */
22
- const USAGE_PREFERRED_POS = ['3_0', '4_0', '3_1', '4_1'];
19
+ /**
20
+ * D200H usage placement: the three bottom-row keys immediately LEFT of the wide
21
+ * bottom-right clock widget (0_2, 1_2, 2_2) a single horizontal quota strip
22
+ * along the bottom edge, so the whole upper grid stays session tiles.
23
+ *
24
+ * The strip is filled from its RIGHT end (see `buildList`), keeping the gauges
25
+ * flush against the clock: with the usual three tiles (Claude 5H, Claude 7D,
26
+ * Codex) it fills exactly; when a tile is absent — Codex now commonly reports
27
+ * only its weekly window, and an unlinked Claude reports none — the LEFTMOST
28
+ * key falls out of the strip and flows back to sessions instead of leaving a
29
+ * hole in the middle of the row.
30
+ *
31
+ * Its length also caps usage at three keys: a fourth tile (Codex 5H *and* 7D)
32
+ * drops, Claude prioritised by `buildUsageTiles` order.
33
+ */
34
+ const USAGE_PREFERRED_POS = ['0_2', '1_2', '2_2'];
23
35
  export function parseState(evt) {
24
36
  return {
25
37
  state: evt?.state ?? 'DISCONNECTED',
@@ -167,6 +179,31 @@ function usageRampColor(used, stale = false) {
167
179
  return { fill: '#eab308', hi: '#fde047' };
168
180
  return { fill: '#22c55e', hi: '#86efac' };
169
181
  }
182
+ /**
183
+ * Compact label for a rolling-window length in minutes, mirroring the Swift
184
+ * `TopologyRail.windowLabel`: whole days → "Nd" (10080 → "7D"), whole hours →
185
+ * "Nh" (300 → "5H"), else "Nm". Codex now sometimes reports the weekly window
186
+ * in the `primary` slot with `secondary` null, so usage gauges MUST label by a
187
+ * window's own length, never by its slot position — otherwise the 7D gauge
188
+ * silently vanishes and the weekly window is mislabelled "5H".
189
+ */
190
+ export function usageWindowLabel(windowMinutes) {
191
+ if (!windowMinutes || windowMinutes <= 0)
192
+ return '';
193
+ if (windowMinutes % 1440 === 0)
194
+ return `${windowMinutes / 1440}D`;
195
+ if (windowMinutes % 60 === 0)
196
+ return `${windowMinutes / 60}H`;
197
+ return `${windowMinutes}M`;
198
+ }
199
+ /**
200
+ * Which gauge bucket ('5h' short vs '7d' long) a rolling window belongs to,
201
+ * derived from its length (≥ a day → long) so the clip id / styling is right
202
+ * regardless of whether the window arrived in the primary or secondary slot.
203
+ */
204
+ export function usageWindowKind(windowMinutes) {
205
+ return (windowMinutes ?? 0) >= 1440 ? '7d' : '5h';
206
+ }
170
207
  export function renderUsageGauge(data) {
171
208
  const W = 144, H = 144, RX = 12;
172
209
  const known = data.known !== false;
@@ -236,7 +273,8 @@ export function renderCreditsTile(data) {
236
273
  /**
237
274
  * Usage tiles for the session deck, in placement order. Every tile is
238
275
  * hide-if-absent: Claude 5H/7D appear only when that window's quota is actually
239
- * known, and Codex 5H (primary) / 7D (secondary) only when present in
276
+ * known, and each present Codex window (labelled by its own length, not its
277
+ * primary/secondary slot) only when present in
240
278
  * `codexRateLimits`. An unlinked or partial usage state therefore emits fewer
241
279
  * (or zero) tiles, so `buildList` reserves fewer keys and the freed slots flow
242
280
  * to session tiles instead of leaving reserved "—" ghost gauges behind.
@@ -255,12 +293,14 @@ function buildUsageTiles(state) {
255
293
  }
256
294
  const cx = state.codexRateLimits;
257
295
  // Codex windows carry the same short "5H"/"7D" labels — the brand dot conveys
258
- // the agent, not a "CX " prefix.
259
- if (cx?.primary) {
260
- tiles.push({ svg: renderUsageGauge({ agent: 'codex', window: '5h', label: '5H', usedPercent: cx.primary.usedPercent, resetsAt: cx.primary.resetsAt, known: true, stale: cx.primary.stale === true }), action });
261
- }
262
- if (cx?.secondary) {
263
- tiles.push({ svg: renderUsageGauge({ agent: 'codex', window: '7d', label: '7D', usedPercent: cx.secondary.usedPercent, resetsAt: cx.secondary.resetsAt, known: true, stale: cx.secondary.stale === true }), action });
296
+ // the agent, not a "CX " prefix. Label each present window by its own length
297
+ // (windowMinutes), never by slot: Codex now sometimes reports the weekly
298
+ // (10080-min) window as `primary` with `secondary` null, so a slot-based "7D
299
+ // = secondary" would drop the gauge entirely.
300
+ for (const w of [cx?.primary, cx?.secondary]) {
301
+ if (!w)
302
+ continue;
303
+ 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 }), action });
264
304
  }
265
305
  // Credit-based Codex plan: no windows, show the credits balance instead so the
266
306
  // Codex usage doesn't silently vanish.
@@ -447,6 +487,47 @@ function actionTile(label, color, subtitle) {
447
487
  ].join('');
448
488
  return svgFrame('#16181d', els);
449
489
  }
490
+ /**
491
+ * REVIEW = independent on-demand eval (review_run), NOT a prompt to the
492
+ * agent — the daemon judges the session's latest COMPLETED work with a
493
+ * separate model, so this tile is valid for EVERY session type (managed,
494
+ * observed Claude/OpenCode, even control-less observed Codex) — but only
495
+ * once the turn has ended. Shows the last verdict as a badge; REVIEWING
496
+ * while the judge runs.
497
+ */
498
+ function reviewTile(sess, sid) {
499
+ if (sess?.reviewStatus === 'running') {
500
+ return { svg: renderInfoSlot('REVIEWING', 'judge running', 'activity', 'info'), action: null };
501
+ }
502
+ const risk = sess?.reviewRisk;
503
+ const subtitle = risk
504
+ ? `risk ${risk}${sess?.reviewFindings != null ? ` · ${sess.reviewFindings}` : ''}`
505
+ : 'independent eval';
506
+ const color = risk === 'high' ? '#f87171' : risk === 'medium' ? '#fbbf24' : '#93c5fd';
507
+ return {
508
+ svg: actionTile('REVIEW', color, subtitle),
509
+ action: { kind: 'command', command: { type: 'review_run', sessionId: sid } },
510
+ };
511
+ }
512
+ /**
513
+ * Non-pressable review status for the PROCESSING branch: REVIEWING while the
514
+ * judge runs, or the last verdict as an inert badge. Null when there is
515
+ * nothing to show. Mid-turn the tile must not fire review_run — the work is
516
+ * not complete yet (the Swift daemon judges the session trajectory, which
517
+ * mid-turn has no assistant response and systematically reads as
518
+ * "incomplete/unverified"; the Node daemon would judge a half-written diff).
519
+ */
520
+ function reviewBadgeTile(sess) {
521
+ if (sess?.reviewStatus === 'running') {
522
+ return { svg: renderInfoSlot('REVIEWING', 'judge running', 'activity', 'info'), action: null };
523
+ }
524
+ const risk = sess?.reviewRisk;
525
+ if (!risk)
526
+ return null;
527
+ const subtitle = `risk ${risk}${sess?.reviewFindings != null ? ` · ${sess.reviewFindings}` : ''}`;
528
+ const color = risk === 'high' ? '#f87171' : risk === 'medium' ? '#fbbf24' : '#93c5fd';
529
+ return { svg: actionTile('REVIEW', color, subtitle), action: null };
530
+ }
450
531
  export function buildSessionDeck(stateEvt, view, positions) {
451
532
  const state = parseState(stateEvt);
452
533
  const slots = sortPositions(positions);
@@ -482,29 +563,30 @@ export function buildSessionDeck(stateEvt, view, positions) {
482
563
  }
483
564
  function buildList(state, view, slots, animFrame, animated, out) {
484
565
  const sessions = sortSessions(foldCodexSessionsForDisplay(state.allSessions));
485
- // Pin the trailing keys to global usage gauges (opt-in, water-tank style). On
486
- // the D200H these land just left of the native clock widget; on classic Stream
487
- // Deck they replace the encoder LCD this surface lacks. We reserve as many keys
488
- // as we have usage tiles (Claude 5H/7D + any Codex windows), but NEVER more
489
- // than `slots.length - 1` so at least one key stays for sessions this is the
490
- // fix for the old `slots.length >= 6` gate that silently dropped ALL usage when
491
- // the user placed only a few AgentDeck keys. Codex tiles drop first on tiny
492
- // decks (Claude prioritised). Reserved keys are pinned on EVERY page (usage is
493
- // global), and paging math below treats them as unavailable for sessions.
566
+ // Pin the bottom-row usage strip to the global quota gauges (opt-in,
567
+ // water-tank style). On the D200H the strip sits just left of the native clock
568
+ // widget; on classic Stream Deck it replaces the encoder LCD this surface
569
+ // lacks. We reserve as many keys as we have usage tiles (Claude 5H/7D + any
570
+ // Codex window), but never more than the strip is wide, and never more than
571
+ // `slots.length - 1` so at least one key stays for sessions — the latter is
572
+ // the fix for the old `slots.length >= 6` gate that silently dropped ALL usage
573
+ // when the user placed only a few AgentDeck keys. Reserved keys are pinned on
574
+ // EVERY page (usage is global), and paging math below treats them as
575
+ // unavailable for sessions.
494
576
  const usageHere = new Map();
495
577
  if (view.showUsage) {
496
578
  const usageTiles = buildUsageTiles(state);
497
579
  const maxReserve = Math.max(0, slots.length - 1);
498
- const reserveCount = Math.min(usageTiles.length, maxReserve);
499
- // Prefer the 2×2 block directly ABOVE the wide bottom-right button
500
- // (cols 3–4, rows 0–1) cleaner on the D200H than the trailing keys.
501
- // Reading order (top→bottom) puts Claude 5H/7D on the top row and any
502
- // Codex 5H/7D below. Falls back to trailing positions for tiles whose
503
- // preferred slot wasn't placed by the user.
504
- const preferred = USAGE_PREFERRED_POS.filter((p) => slots.includes(p));
505
- const rest = slots.filter((p) => !preferred.includes(p));
506
- const fallback = rest.slice(rest.length - Math.max(0, reserveCount - preferred.length));
507
- const reserved = sortPositions([...preferred, ...fallback]).slice(0, reserveCount);
580
+ const preferred = sortPositions(USAGE_PREFERRED_POS.filter((p) => slots.includes(p)));
581
+ const reserveCount = Math.min(usageTiles.length, USAGE_PREFERRED_POS.length, maxReserve);
582
+ // Fill the strip from its RIGHT end so a missing tile frees the LEFTMOST key
583
+ // (which flows back to sessions) and the gauges stay flush against the clock
584
+ // never a hole mid-strip.
585
+ const pinned = preferred.slice(Math.max(0, preferred.length - reserveCount));
586
+ // Tiles whose strip key the user didn't place fall back to trailing keys.
587
+ const rest = slots.filter((p) => !pinned.includes(p));
588
+ const fallback = rest.slice(rest.length - Math.max(0, reserveCount - pinned.length));
589
+ const reserved = sortPositions([...pinned, ...fallback]).slice(0, reserveCount);
508
590
  reserved.forEach((pos, i) => usageHere.set(pos, usageTiles[i]));
509
591
  }
510
592
  // Positions left for sessions / NEXT after carving out usage.
@@ -552,7 +634,10 @@ function buildDetail(state, stateEvt, view, slots, animFrame, animated, out) {
552
634
  const sState = (focused ? state.state : (sess?.state ?? 'idle')).toLowerCase();
553
635
  const options = (focused ? state.options : (sess?.options ?? [])) ?? [];
554
636
  const tool = focused ? state.currentTool : sess?.currentTool;
555
- const model = sess?.modelName ?? state.modelName;
637
+ // A selected session with no model is UNKNOWN, not an invitation to borrow
638
+ // the daemon-global model from another agent. Only a matching focused event
639
+ // may override the sessions_list row.
640
+ const model = focused ? (state.modelName || sess?.modelName || '') : (sess?.modelName || '');
556
641
  const heroSess = sess ?? {
557
642
  id: sid, port: 0, alive: true, projectName: state.projectName,
558
643
  agentType: state.agentType, state: sState, modelName: model,
@@ -564,14 +649,52 @@ function buildDetail(state, stateEvt, view, slots, animFrame, animated, out) {
564
649
  svg: renderDetailInfo(heroSess, sState, tool, model, undefined),
565
650
  action: null,
566
651
  });
652
+ // Observed (hook-only, no PTY) sessions steer through hook primitives, so
653
+ // every actionable cell must map to something the daemon can actually
654
+ // deliver: STOP → soft stop (deny at next tool call), quick actions →
655
+ // queued for turn end, ALLOW/DENY → held PreToolUse gate. Anything
656
+ // undeliverable renders inert instead of pretending to work.
657
+ const isObserved = sess?.controlMode === 'observed';
658
+ // Which observed agents actually have a delivery path: Claude = hook RPC
659
+ // (soft stop / turn-end queue / gate), OpenCode = observer-plugin command
660
+ // queue (immediate abort / prompt injection). Codex hooks are notify-only —
661
+ // no steering exists, so its buttons must stay inert.
662
+ const observedSteerable = isObserved
663
+ && (sess?.agentType === 'claude-code' || sess?.agentType === 'opencode');
664
+ const stopRequested = Boolean(sess?.stopRequested);
665
+ const gateRequestId = sess?.requestId;
567
666
  if (processingState(sState)) {
568
- out.set(last, { svg: renderStopButton(true), action: { kind: 'command', command: { type: 'interrupt' } } });
667
+ if (isObserved && !observedSteerable) {
668
+ out.set(last, { svg: renderStopButton(false), action: null });
669
+ }
670
+ else if (isObserved && stopRequested) {
671
+ // Soft stop already requested — pressing again does nothing new.
672
+ out.set(last, { svg: renderInfoSlot('STOPPING', 'at next tool', 'status', 'warning'), action: null });
673
+ }
674
+ else {
675
+ const command = isObserved
676
+ ? { type: 'session_command', sessionId: sid, command: { type: 'interrupt' } }
677
+ : { type: 'interrupt' };
678
+ out.set(last, { svg: renderStopButton(true), action: { kind: 'command', command } });
679
+ }
569
680
  }
570
681
  else if (awaitingState(sState)) {
571
- out.set(last, { svg: renderEscButton(true), action: { kind: 'command', command: { type: 'escape' } } });
682
+ if (isObserved) {
683
+ // ESC can't reach an observed terminal; the gate (if any) is answered
684
+ // by the ALLOW/DENY content cells below.
685
+ out.set(last, { svg: renderEscButton(false), action: null });
686
+ }
687
+ else {
688
+ out.set(last, { svg: renderEscButton(true), action: { kind: 'command', command: { type: 'escape' } } });
689
+ }
572
690
  }
573
691
  else {
574
- out.set(last, { svg: renderStopButton(false), action: { kind: 'command', command: { type: 'interrupt' } } });
692
+ // Idle: an observed session has nothing to interrupt render STOP inert
693
+ // (the old always-armed interrupt here was a silent-drop trap).
694
+ out.set(last, {
695
+ svg: renderStopButton(false),
696
+ action: isObserved ? null : { kind: 'command', command: { type: 'interrupt' } },
697
+ });
575
698
  }
576
699
  // Content slots between INFO and STOP.
577
700
  const content = slots.slice(2, slots.length - 1);
@@ -593,6 +716,19 @@ function buildDetail(state, stateEvt, view, slots, animFrame, animated, out) {
593
716
  cells.push({ svg: renderOptionButton(opt, i), action: { kind: 'command', command } });
594
717
  });
595
718
  }
719
+ else if (isObserved && gateRequestId) {
720
+ // Held PreToolUse gate: these two answers are device-native semantics
721
+ // (permit/deny THIS tool call), not a mirror of the TUI prompt — the
722
+ // daemon only holds calls it verified Claude would genuinely prompt for.
723
+ cells.push({
724
+ svg: actionTile('ALLOW', '#22c55e', 'this tool call'),
725
+ action: { kind: 'command', command: { type: 'permission_decision', requestId: gateRequestId, decision: 'allow' } },
726
+ });
727
+ cells.push({
728
+ svg: actionTile('DENY', '#f87171', 'this tool call'),
729
+ action: { kind: 'command', command: { type: 'permission_decision', requestId: gateRequestId, decision: 'deny' } },
730
+ });
731
+ }
596
732
  else {
597
733
  // Awaiting but no real options to render — only PTY-managed sessions expose
598
734
  // Claude's actual choices. Don't fabricate Allow/Deny that may not match the
@@ -601,12 +737,57 @@ function buildDetail(state, stateEvt, view, slots, animFrame, animated, out) {
601
737
  }
602
738
  }
603
739
  else if (processingState(sState)) {
604
- cells.push({ svg: renderInfoSlot('RUNNING', tool || 'working', 'activity', 'info'), action: null });
740
+ const queued = sess?.queuedDirectives ?? 0;
741
+ cells.push({
742
+ svg: renderInfoSlot('RUNNING', queued > 0 ? `${queued} queued` : (tool || 'working'), 'activity', 'info'),
743
+ action: null,
744
+ });
745
+ // No queued task buttons mid-turn: PROCESSING is deliberately limited to
746
+ // live status + STOP so users cannot mistake a future directive for the
747
+ // agent's current work. Keep the
748
+ // REVIEWING spinner / last-verdict badge visible as inert status.
749
+ const badge = reviewBadgeTile(sess);
750
+ if (badge)
751
+ cells.push(badge);
752
+ }
753
+ else if (isObserved) {
754
+ if (sess?.agentType === 'opencode') {
755
+ // OpenCode observed injects immediately even while idle (observer
756
+ // plugin + in-process SDK) — same semantics as managed idle presets.
757
+ const inject = [
758
+ ['GO ON', 'continue'], ['COMMIT', 'commit the changes'],
759
+ ];
760
+ inject.forEach(([label, text]) => cells.push({
761
+ svg: actionTile(label, '#cbd5e1', 'inject now'),
762
+ action: { kind: 'command', command: { type: 'session_command', sessionId: sid, command: { type: 'send_prompt', text } } },
763
+ }));
764
+ cells.push(reviewTile(sess, sid));
765
+ }
766
+ else {
767
+ // Idle observed Claude/Codex: no prompt-delivery path exists, but the
768
+ // independent review needs no control at all — it is the one action
769
+ // that stays live here.
770
+ cells.push(reviewTile(sess, sid));
771
+ cells.push({ svg: renderInfoSlot('OBSERVED', 'control in terminal', 'status', 'info'), action: null });
772
+ }
605
773
  }
606
774
  else {
607
- // idle quick-actions
608
- const presets = [['GO ON', 'continue'], ['REVIEW', 'review the changes'], ['COMMIT', 'commit the changes'], ['CLEAR', '/clear']];
609
- presets.forEach(([label, text]) => cells.push({ svg: actionTile(label, '#cbd5e1'), action: { kind: 'command', command: { type: 'send_prompt', text } } }));
775
+ // Managed idle quick-actions. REVIEW routes to the independent eval
776
+ // (uniform semantics across every session type); the rest type into the
777
+ // PTY as before.
778
+ cells.push({
779
+ svg: actionTile('GO ON', '#cbd5e1'),
780
+ action: { kind: 'command', command: { type: 'send_prompt', text: 'continue' } },
781
+ });
782
+ cells.push(reviewTile(sess, sid));
783
+ cells.push({
784
+ svg: actionTile('COMMIT', '#cbd5e1'),
785
+ action: { kind: 'command', command: { type: 'send_prompt', text: 'commit the changes' } },
786
+ });
787
+ cells.push({
788
+ svg: actionTile('CLEAR', '#cbd5e1'),
789
+ action: { kind: 'command', command: { type: 'send_prompt', text: '/clear' } },
790
+ });
610
791
  }
611
792
  // Paginate cells into content slots; reserve last content slot for MORE if overflow.
612
793
  const cap = content.length;