agent-tempo 1.7.0 → 2.0.0-beta.2

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 (171) hide show
  1. package/CLAUDE.md +10 -13
  2. package/README.md +21 -15
  3. package/dashboard/dist/assets/index-BbR7zIdK.js.map +1 -1
  4. package/dashboard/package.json +1 -1
  5. package/dist/activities/hard-terminate.d.ts +2 -2
  6. package/dist/activities/hard-terminate.js +3 -9
  7. package/dist/activities/maestro.d.ts +8 -13
  8. package/dist/activities/maestro.js +3 -10
  9. package/dist/activities/outbox.js +16 -2
  10. package/dist/activities/resolve.d.ts +18 -7
  11. package/dist/activities/resolve.js +58 -46
  12. package/dist/adapters/base.js +3 -0
  13. package/dist/adapters/claude-code/adapter.js +7 -0
  14. package/dist/cli/command-center-command.js +15 -1
  15. package/dist/cli/commands.d.ts +8 -0
  16. package/dist/cli/commands.js +79 -11
  17. package/dist/cli/daemon-command.js +19 -33
  18. package/dist/cli/help-text.js +9 -7
  19. package/dist/cli/home-command.d.ts +26 -0
  20. package/dist/cli/home-command.js +156 -0
  21. package/dist/cli/removed-verbs.d.ts +0 -4
  22. package/dist/cli/removed-verbs.js +23 -12
  23. package/dist/cli/sa-preflight.d.ts +0 -14
  24. package/dist/cli/sa-preflight.js +1 -17
  25. package/dist/cli/startup.d.ts +1 -10
  26. package/dist/cli/startup.js +0 -40
  27. package/dist/cli.js +34 -80
  28. package/dist/client/core.js +13 -15
  29. package/dist/client/index.d.ts +2 -1
  30. package/dist/client/interface.d.ts +5 -14
  31. package/dist/client/with-spawn.d.ts +9 -4
  32. package/dist/client/with-spawn.js +7 -10
  33. package/dist/config.d.ts +14 -13
  34. package/dist/config.js +14 -0
  35. package/dist/constants.d.ts +50 -0
  36. package/dist/constants.js +60 -1
  37. package/dist/daemon.js +81 -0
  38. package/dist/http/auth.d.ts +3 -21
  39. package/dist/http/auth.js +10 -31
  40. package/dist/http/event-types.d.ts +38 -4
  41. package/dist/http/orphans.d.ts +3 -3
  42. package/dist/http/orphans.js +3 -3
  43. package/dist/http/server.d.ts +0 -5
  44. package/dist/http/server.js +79 -29
  45. package/dist/http/snapshot.js +3 -0
  46. package/dist/http/writes.d.ts +1 -1
  47. package/dist/http/writes.js +25 -2
  48. package/dist/observability/nondeterminism-alarm.d.ts +113 -0
  49. package/dist/observability/nondeterminism-alarm.js +162 -0
  50. package/dist/palette/index.d.ts +6 -7
  51. package/dist/palette/index.js +6 -7
  52. package/dist/pi/mission-control/actions.d.ts +67 -1
  53. package/dist/pi/mission-control/actions.js +61 -0
  54. package/dist/pi/mission-control/extension.d.ts +77 -0
  55. package/dist/pi/mission-control/extension.js +171 -0
  56. package/dist/pi/workflow-client.d.ts +14 -16
  57. package/dist/pi/workflow-client.js +18 -44
  58. package/dist/server-tools.js +30 -29
  59. package/dist/server.js +42 -0
  60. package/dist/tools/action-guard.d.ts +23 -0
  61. package/dist/tools/action-guard.js +33 -0
  62. package/dist/tools/coat-check.d.ts +20 -0
  63. package/dist/tools/coat-check.js +129 -0
  64. package/dist/tools/gate.d.ts +13 -0
  65. package/dist/tools/gate.js +88 -0
  66. package/dist/tools/schedule.d.ts +18 -0
  67. package/dist/tools/schedule.js +93 -1
  68. package/dist/tools/stage.d.ts +17 -0
  69. package/dist/tools/stage.js +85 -1
  70. package/dist/tools/state.d.ts +14 -0
  71. package/dist/tools/state.js +95 -0
  72. package/dist/types.d.ts +54 -7
  73. package/dist/upgrade/boot-guard.d.ts +95 -0
  74. package/dist/upgrade/boot-guard.js +88 -0
  75. package/dist/upgrade/from-upgrade.d.ts +95 -0
  76. package/dist/upgrade/from-upgrade.js +330 -0
  77. package/dist/upgrade/phase-engine.js +4 -1
  78. package/dist/utils/duration.d.ts +3 -6
  79. package/dist/utils/duration.js +3 -6
  80. package/dist/utils/format-hosts.d.ts +1 -1
  81. package/dist/utils/format-hosts.js +0 -2
  82. package/dist/utils/grpc-shutdown-guard.js +0 -4
  83. package/dist/utils/hosts.d.ts +4 -10
  84. package/dist/utils/hosts.js +12 -28
  85. package/dist/utils/orphan-guard.d.ts +37 -0
  86. package/dist/utils/orphan-guard.js +26 -0
  87. package/dist/utils/search-attributes.d.ts +28 -18
  88. package/dist/utils/search-attributes.js +48 -18
  89. package/dist/utils/visibility-deadline.d.ts +16 -0
  90. package/dist/utils/visibility-deadline.js +16 -0
  91. package/dist/workflows/maestro-signals.d.ts +0 -13
  92. package/dist/workflows/maestro-signals.js +6 -14
  93. package/dist/workflows/maestro.js +27 -32
  94. package/dist/workflows/scheduler.js +7 -1
  95. package/dist/workflows/session.js +237 -153
  96. package/dist/workflows/signals.d.ts +9 -1
  97. package/dist/workflows/signals.js +8 -6
  98. package/package.json +1 -6
  99. package/workflow-bundle.js +478 -225
  100. package/dist/cli/legacy-migration.d.ts +0 -35
  101. package/dist/cli/legacy-migration.js +0 -341
  102. package/dist/client/ensure-conductor-spawned.d.ts +0 -35
  103. package/dist/client/ensure-conductor-spawned.js +0 -48
  104. package/dist/tui/App.d.ts +0 -85
  105. package/dist/tui/App.js +0 -1791
  106. package/dist/tui/bootstrap-types.d.ts +0 -46
  107. package/dist/tui/bootstrap-types.js +0 -7
  108. package/dist/tui/commands.d.ts +0 -71
  109. package/dist/tui/commands.js +0 -1375
  110. package/dist/tui/components/ChatView.d.ts +0 -35
  111. package/dist/tui/components/ChatView.js +0 -54
  112. package/dist/tui/components/CommandPalette.d.ts +0 -21
  113. package/dist/tui/components/CommandPalette.js +0 -67
  114. package/dist/tui/components/ConversationStream.d.ts +0 -114
  115. package/dist/tui/components/ConversationStream.js +0 -307
  116. package/dist/tui/components/CreateEnsembleWizard.d.ts +0 -19
  117. package/dist/tui/components/CreateEnsembleWizard.js +0 -223
  118. package/dist/tui/components/DestroyConfirmModal.d.ts +0 -17
  119. package/dist/tui/components/DestroyConfirmModal.js +0 -62
  120. package/dist/tui/components/ErrorView.d.ts +0 -31
  121. package/dist/tui/components/ErrorView.js +0 -129
  122. package/dist/tui/components/HomeView.d.ts +0 -54
  123. package/dist/tui/components/HomeView.js +0 -306
  124. package/dist/tui/components/LoadLineupModal.d.ts +0 -18
  125. package/dist/tui/components/LoadLineupModal.js +0 -79
  126. package/dist/tui/components/MainView.d.ts +0 -21
  127. package/dist/tui/components/MainView.js +0 -107
  128. package/dist/tui/components/NewEnsembleModal.d.ts +0 -9
  129. package/dist/tui/components/NewEnsembleModal.js +0 -73
  130. package/dist/tui/components/Picker.d.ts +0 -23
  131. package/dist/tui/components/Picker.js +0 -70
  132. package/dist/tui/components/PlayerDetailView.d.ts +0 -26
  133. package/dist/tui/components/PlayerDetailView.js +0 -118
  134. package/dist/tui/components/PromptArea.d.ts +0 -50
  135. package/dist/tui/components/PromptArea.js +0 -303
  136. package/dist/tui/components/RecruitWizard.d.ts +0 -17
  137. package/dist/tui/components/RecruitWizard.js +0 -221
  138. package/dist/tui/components/RestoreConfirmModal.d.ts +0 -18
  139. package/dist/tui/components/RestoreConfirmModal.js +0 -71
  140. package/dist/tui/components/ScheduleWizard.d.ts +0 -19
  141. package/dist/tui/components/ScheduleWizard.js +0 -259
  142. package/dist/tui/components/Splash.d.ts +0 -23
  143. package/dist/tui/components/Splash.js +0 -221
  144. package/dist/tui/components/StatusBar.d.ts +0 -48
  145. package/dist/tui/components/StatusBar.js +0 -128
  146. package/dist/tui/components/StatusOverlay.d.ts +0 -15
  147. package/dist/tui/components/StatusOverlay.js +0 -76
  148. package/dist/tui/components/TitleBar.d.ts +0 -10
  149. package/dist/tui/components/TitleBar.js +0 -21
  150. package/dist/tui/index.d.ts +0 -15
  151. package/dist/tui/index.js +0 -162
  152. package/dist/tui/ink-context.d.ts +0 -18
  153. package/dist/tui/ink-context.js +0 -59
  154. package/dist/tui/ink-loader.d.ts +0 -26
  155. package/dist/tui/ink-loader.js +0 -42
  156. package/dist/tui/removed-commands.d.ts +0 -9
  157. package/dist/tui/removed-commands.js +0 -22
  158. package/dist/tui/sse-handler.d.ts +0 -52
  159. package/dist/tui/sse-handler.js +0 -157
  160. package/dist/tui/store.d.ts +0 -598
  161. package/dist/tui/store.js +0 -753
  162. package/dist/tui/utils/format.d.ts +0 -56
  163. package/dist/tui/utils/format.js +0 -155
  164. package/dist/tui/utils/fullscreen.d.ts +0 -23
  165. package/dist/tui/utils/fullscreen.js +0 -71
  166. package/dist/tui/utils/history.d.ts +0 -10
  167. package/dist/tui/utils/history.js +0 -85
  168. package/dist/tui/utils/platform.d.ts +0 -45
  169. package/dist/tui/utils/platform.js +0 -258
  170. package/dist/tui/utils/theme.d.ts +0 -21
  171. package/dist/tui/utils/theme.js +0 -24
@@ -1,35 +0,0 @@
1
- /**
2
- * ChatView — header for a conversation with a specific player.
3
- * Messages are rendered via <Static> in App.tsx, not here.
4
- * This component only shows context (player name, part, branch, counts).
5
- *
6
- * PR-F cross-host indicator (design §16, brief §8 answer 3 — Option 3):
7
- * - Local session (targetHost === localHost) → omit host entirely (no noise).
8
- * - Remote session → prepend `{host} · ` to the existing status line in
9
- * `THEME.accent` (amber).
10
- * Zero new Yoga nodes: the host prefix goes into the same `<Text>` children
11
- * tree as an additional string + nested accent-colored `<Text>` (which is a
12
- * virtual text node, not a Box).
13
- */
14
- import React from 'react';
15
- export interface ChatMessage {
16
- direction: 'received' | 'sent';
17
- from: string;
18
- text: string;
19
- timestamp: string;
20
- }
21
- export interface ChatViewProps {
22
- targetPlayer: string;
23
- targetPart?: string;
24
- targetBranch?: string;
25
- targetStatus?: string;
26
- /** The session's home hostname (PR-A metadata). Compare vs `localHost` to decide cross-host rendering. */
27
- targetHost?: string;
28
- /** The local TUI process's hostname. */
29
- localHost?: string;
30
- isConductor?: boolean;
31
- receivedCount: number;
32
- sentCount: number;
33
- messages: ChatMessage[];
34
- }
35
- export declare function ChatView({ targetPlayer, targetPart, targetBranch, targetHost, localHost, isConductor, receivedCount, sentCount, }: ChatViewProps): React.CElement<{}, React.Component<{}, any, any>>;
@@ -1,54 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ChatView = ChatView;
7
- /**
8
- * ChatView — header for a conversation with a specific player.
9
- * Messages are rendered via <Static> in App.tsx, not here.
10
- * This component only shows context (player name, part, branch, counts).
11
- *
12
- * PR-F cross-host indicator (design §16, brief §8 answer 3 — Option 3):
13
- * - Local session (targetHost === localHost) → omit host entirely (no noise).
14
- * - Remote session → prepend `{host} · ` to the existing status line in
15
- * `THEME.accent` (amber).
16
- * Zero new Yoga nodes: the host prefix goes into the same `<Text>` children
17
- * tree as an additional string + nested accent-colored `<Text>` (which is a
18
- * virtual text node, not a Box).
19
- */
20
- const react_1 = __importDefault(require("react"));
21
- const ink_context_1 = require("../ink-context");
22
- const theme_1 = require("../utils/theme");
23
- function ChatView({ targetPlayer, targetPart, targetBranch, targetHost, localHost, isConductor, receivedCount, sentCount, }) {
24
- const { Text } = (0, ink_context_1.useInk)();
25
- const icon = isConductor ? '\u2605 ' : '';
26
- const label = isConductor ? 'Conductor' : 'Conversation with';
27
- // §8 answer 3 — remote host only; local omits entirely.
28
- const showRemoteHost = Boolean(targetHost && localHost && targetHost !== localHost);
29
- const children = [];
30
- children.push(react_1.default.createElement(Text, { key: 'h1', bold: true, color: theme_1.THEME.accent }, ` ${icon}${label} ${targetPlayer}`));
31
- if (targetPart) {
32
- children.push('\n');
33
- children.push(react_1.default.createElement(Text, { key: 'h2', color: theme_1.THEME.dim }, ` Part: ${targetPart}`));
34
- }
35
- children.push('\n');
36
- // Status line — prepend amber {host} · for remote sessions only.
37
- // Zero new Yoga nodes: composed inline as nested <Text> within h3.
38
- const h3Children = [];
39
- h3Children.push(' ');
40
- if (showRemoteHost) {
41
- h3Children.push(react_1.default.createElement(Text, { key: 'host', color: theme_1.THEME.accent }, targetHost));
42
- h3Children.push(' \u00B7 ');
43
- }
44
- if (targetBranch) {
45
- h3Children.push(`Branch: ${targetBranch} \u00B7 `);
46
- }
47
- h3Children.push(`${receivedCount} received, ${sentCount} sent`);
48
- children.push(react_1.default.createElement(Text, { key: 'h3', color: theme_1.THEME.dim }, ...h3Children));
49
- children.push('\n');
50
- children.push(react_1.default.createElement(Text, { key: 'h4', color: theme_1.THEME.border }, ' ' + '\u2500'.repeat(55)));
51
- children.push('\n');
52
- children.push(react_1.default.createElement(Text, { key: 'h5', color: theme_1.THEME.dim }, ' Messages appear above \u2191 (scroll up in terminal)'));
53
- return react_1.default.createElement(Text, null, ...children);
54
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * CommandPalette — dropdown overlay showing filtered slash commands.
3
- * Renders as a SINGLE Text element (1 Yoga node) with newlines for rows.
4
- */
5
- import React from 'react';
6
- export interface PaletteCommand {
7
- name: string;
8
- usage: string;
9
- description: string;
10
- }
11
- export interface CommandPaletteProps {
12
- commands: PaletteCommand[];
13
- selectedIndex: number;
14
- /** Prefix character for display (default: "/"). Use "@" for player palette. */
15
- prefix?: string;
16
- }
17
- export declare function CommandPalette({ commands, selectedIndex, prefix }: CommandPaletteProps): React.CElement<{}, React.Component<{}, any, any>> | React.CElement<{
18
- color: string;
19
- }, React.Component<{
20
- color: string;
21
- }, any, any>>;
@@ -1,67 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.CommandPalette = CommandPalette;
7
- /**
8
- * CommandPalette — dropdown overlay showing filtered slash commands.
9
- * Renders as a SINGLE Text element (1 Yoga node) with newlines for rows.
10
- */
11
- const react_1 = __importDefault(require("react"));
12
- const ink_context_1 = require("../ink-context");
13
- const theme_1 = require("../utils/theme");
14
- const MAX_VISIBLE = 6;
15
- function CommandPalette({ commands, selectedIndex, prefix = '/' }) {
16
- const { Text } = (0, ink_context_1.useInk)();
17
- if (commands.length === 0) {
18
- return react_1.default.createElement(Text, { color: theme_1.THEME.dim }, ' No matching commands');
19
- }
20
- // Window the visible commands around the selected index
21
- let startIdx = 0;
22
- if (commands.length > MAX_VISIBLE) {
23
- startIdx = Math.max(0, Math.min(selectedIndex - Math.floor(MAX_VISIBLE / 2), commands.length - MAX_VISIBLE));
24
- }
25
- const visible = commands.slice(startIdx, startIdx + MAX_VISIBLE);
26
- const lines = [];
27
- // Scroll-up indicator
28
- if (startIdx > 0) {
29
- lines.push(` \u2191 ${startIdx} more above`);
30
- }
31
- // Visible rows (plain text — color via outer Text)
32
- for (let i = 0; i < visible.length; i++) {
33
- const cmd = visible[i];
34
- const actualIdx = startIdx + i;
35
- const isSelected = actualIdx === selectedIndex;
36
- const indicator = isSelected ? '\u25B8 ' : ' ';
37
- lines.push(` ${indicator}/${cmd.name} ${cmd.description}`);
38
- }
39
- // Scroll-down indicator
40
- const hasBelow = startIdx + MAX_VISIBLE < commands.length;
41
- if (hasBelow) {
42
- lines.push(` \u2193 ${commands.length - startIdx - MAX_VISIBLE} more below`);
43
- }
44
- // Single Text element with newlines — all one Yoga node
45
- // Use nested Text for selected item highlighting
46
- const children = [];
47
- let lineIdx = 0;
48
- if (startIdx > 0) {
49
- children.push(react_1.default.createElement(Text, { key: 'above', color: theme_1.THEME.dim }, lines[lineIdx]));
50
- children.push('\n');
51
- lineIdx++;
52
- }
53
- for (let i = 0; i < visible.length; i++) {
54
- const cmd = visible[i];
55
- const actualIdx = startIdx + i;
56
- const isSelected = actualIdx === selectedIndex;
57
- const indicator = isSelected ? '\u25B8 ' : ' ';
58
- if (i > 0)
59
- children.push('\n');
60
- children.push(react_1.default.createElement(react_1.default.Fragment, { key: cmd.name }, react_1.default.createElement(Text, { color: isSelected ? theme_1.THEME.accent : theme_1.THEME.dim }, ` ${indicator}`), react_1.default.createElement(Text, { color: isSelected ? theme_1.THEME.accent : theme_1.THEME.success, bold: isSelected }, `${prefix}${cmd.name}`), react_1.default.createElement(Text, { color: theme_1.THEME.muted }, ` ${cmd.description}`)));
61
- }
62
- if (hasBelow) {
63
- children.push('\n');
64
- children.push(react_1.default.createElement(Text, { key: 'below', color: theme_1.THEME.dim }, ` \u2193 ${commands.length - startIdx - MAX_VISIBLE} more below`));
65
- }
66
- return react_1.default.createElement(Text, null, ...children);
67
- }
@@ -1,114 +0,0 @@
1
- /**
2
- * ConversationStream — live message area showing ensemble conversation.
3
- *
4
- * Merges server conversation with local echo (optimistic sent messages),
5
- * formats messages with header/body layout and word-wrap, and renders
6
- * the most recent that fit in the viewport.
7
- *
8
- * Performance: Single <Text> root with nested virtual-text children.
9
- * Zero Yoga <Box> nodes.
10
- */
11
- import React from 'react';
12
- export interface ConversationMessage {
13
- id: string;
14
- from: string;
15
- to: string;
16
- text: string;
17
- timestamp: string;
18
- direction: 'in' | 'out';
19
- /** Message role from ensemble chat feed. */
20
- role?: 'maestro-out' | 'maestro-in' | 'conductor-out' | 'conductor-in';
21
- /** True for conductor↔player traffic (rendered dimmed). */
22
- thirdParty?: boolean;
23
- /**
24
- * #357: Mirrors `EnsembleChatMessage.broadcastId`. When set, consecutive
25
- * messages sharing the same id collapse into a single rendered row with
26
- * a `📡 broadcast → N players` badge. `undefined` for direct cues.
27
- */
28
- broadcastId?: string;
29
- }
30
- export interface SentMessage {
31
- to: string;
32
- text: string;
33
- timestamp: string;
34
- /** #357: Mirrors `Message.broadcastId` on the sender side. */
35
- broadcastId?: string;
36
- }
37
- export interface ConversationStreamProps {
38
- conversation: ConversationMessage[];
39
- sentMessages: SentMessage[];
40
- contentHeight: number;
41
- /** Ref to store overflow data for parent to commit to Static scrollback. */
42
- overflowRef: React.MutableRefObject<{
43
- formatted: Array<{
44
- sender: string;
45
- time: string;
46
- body: string;
47
- direction: 'in' | 'out';
48
- thirdParty?: boolean;
49
- routeLabel?: string;
50
- }>;
51
- startIdx: number;
52
- } | null>;
53
- /**
54
- * #360: playerId of the conductor in the active ensemble (derived from
55
- * `state.players.find(p => p.isConductor)`). Used to suppress the
56
- * `→ @<to>` recipient prefix on outbound messages addressed to the
57
- * conductor — those route through the implicit "send to conductor"
58
- * path and the prefix would be visually noisy. `undefined` when no
59
- * conductor is in the ensemble.
60
- */
61
- conductorPlayerId?: string;
62
- }
63
- export interface FormattedMsg {
64
- sender: string;
65
- time: string;
66
- body: string;
67
- direction: 'in' | 'out';
68
- role?: string;
69
- thirdParty?: boolean;
70
- /** For conductor traffic: show routing (from → to). */
71
- routeLabel?: string;
72
- /**
73
- * #360: For directed `maestro-out` messages where the recipient is
74
- * NOT the conductor — prepend `→ @<recipientLabel>` to the rendered
75
- * body so the user can see who they actually messaged. Empty for
76
- * inbound, conductor-bound, or third-party messages.
77
- *
78
- * Mutually exclusive with `broadcastBadge` — when a row represents a
79
- * folded broadcast group, the badge enumerates recipients and the
80
- * single-recipient prefix would duplicate that information.
81
- */
82
- recipientLabel?: string;
83
- /**
84
- * #357: When set, this `FormattedMsg` represents a folded group of
85
- * consecutive messages sharing the same `broadcastId`. The renderer
86
- * surfaces a `📡 broadcast → <count> players` segment in front of
87
- * the body in place of the `→ @<to>` recipient prefix. The first
88
- * `recipients.length` names are listed (cap = 3 + "+N more").
89
- */
90
- broadcastBadge?: {
91
- count: number;
92
- recipients: string[];
93
- };
94
- /**
95
- * Pre-rendered first-line prefix string (broadcast badge or directed
96
- * `→ @<to> `). Computed once in `buildFormattedMessages` so the line
97
- * estimator and the renderer share the exact same value without each
98
- * having to call `broadcastBadgeText` independently.
99
- */
100
- firstLinePrefix?: string;
101
- }
102
- /**
103
- * Pure projection: merge server conversation with local-echo sent
104
- * messages, sort by timestamp, fold broadcast fan-out groups, and
105
- * format each entry into the render-ready `FormattedMsg` shape.
106
- * Exported so tests can assert on `recipientLabel` / `broadcastBadge`
107
- * / `routeLabel` derivation without mounting Ink.
108
- *
109
- * `conductorPlayerId` is the active ensemble's conductor id. Used to
110
- * suppress the `recipientLabel` on conductor-bound `maestro-out` rows
111
- * so the `\u2192 @<conductor>` prefix doesn't dominate every message.
112
- */
113
- export declare function buildFormattedMessages(conversation: ConversationMessage[], sentMessages: SentMessage[], conductorPlayerId?: string): FormattedMsg[];
114
- export declare function ConversationStream({ conversation, sentMessages, contentHeight, overflowRef, conductorPlayerId }: ConversationStreamProps): React.CElement<{}, React.Component<{}, any, any>>;
@@ -1,307 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.buildFormattedMessages = buildFormattedMessages;
7
- exports.ConversationStream = ConversationStream;
8
- /**
9
- * ConversationStream — live message area showing ensemble conversation.
10
- *
11
- * Merges server conversation with local echo (optimistic sent messages),
12
- * formats messages with header/body layout and word-wrap, and renders
13
- * the most recent that fit in the viewport.
14
- *
15
- * Performance: Single <Text> root with nested virtual-text children.
16
- * Zero Yoga <Box> nodes.
17
- */
18
- const react_1 = __importDefault(require("react"));
19
- const ink_context_1 = require("../ink-context");
20
- const theme_1 = require("../utils/theme");
21
- const format_1 = require("../utils/format");
22
- function formatTime(timestamp) {
23
- try {
24
- const d = new Date(timestamp);
25
- return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
26
- }
27
- catch {
28
- return '??:??';
29
- }
30
- }
31
- const INDENT = ' '; // 3-space indent for message body (aligns with input area)
32
- const MAX_DISPLAY_LINES_THIRD_PARTY = 4; // Cap visible body lines for third-party (conductor) traffic
33
- /** Direct messages (maestro-in/out) show in full; third-party traffic is capped. */
34
- function maxLines(msg) {
35
- return msg.thirdParty ? MAX_DISPLAY_LINES_THIRD_PARTY : Infinity;
36
- }
37
- /** Maximum recipient names listed inline in the broadcast badge text. */
38
- const BROADCAST_BADGE_NAME_CAP = 3;
39
- /**
40
- * Render the broadcast-badge inline text (#357). Format:
41
- * `📡 broadcast → 3 players (alice, bob, carol) `
42
- * `📡 broadcast → 8 players (alice, bob, carol +5 more) `
43
- * Trailing space is intentional — the body text follows on the same line.
44
- */
45
- function broadcastBadgeText(badge) {
46
- const head = `\u{1F4E1} broadcast → ${badge.count} player${badge.count === 1 ? '' : 's'}`;
47
- const named = badge.recipients.slice(0, BROADCAST_BADGE_NAME_CAP);
48
- const remainder = Math.max(0, badge.count - named.length);
49
- if (named.length === 0)
50
- return `${head} `;
51
- const list = remainder > 0 ? `${named.join(', ')} +${remainder} more` : named.join(', ');
52
- return `${head} (${list}) `;
53
- }
54
- /**
55
- * Build the inline first-line prefix string added to outbound messages:
56
- * either the broadcast badge (#357) — which dominates when present —
57
- * or the directed-recipient prefix (#360). Empty string when neither
58
- * applies. Used by both `buildFormattedMessages` (precomputes once on
59
- * `FormattedMsg.firstLinePrefix`) and as a fallback in `estimateLines`.
60
- */
61
- function makeFirstLinePrefix(msg) {
62
- if (msg.broadcastBadge)
63
- return broadcastBadgeText(msg.broadcastBadge);
64
- // `→ @<label> ` — arrow + space + @ + label + trailing space.
65
- if (msg.recipientLabel)
66
- return `→ @${msg.recipientLabel} `;
67
- return '';
68
- }
69
- function estimateLines(msg, termCols) {
70
- const bodyWidth = Math.max(20, termCols - 4);
71
- const originalLines = msg.body.split('\n');
72
- const cap = maxLines(msg);
73
- // When a first-line prefix (broadcast badge / `→ @<to>`) is shown,
74
- // wrap the FIRST source line at a narrower width. Continuation source
75
- // lines (and continuation wraps) use the full bodyWidth.
76
- const prefixLen = msg.firstLinePrefix?.length ?? 0;
77
- const firstLineWidth = Math.max(20, bodyWidth - prefixLen);
78
- // Wrap ALL lines, then cap — matches rendering logic exactly
79
- let wrappedCount = 0;
80
- for (let li = 0; li < originalLines.length; li++) {
81
- const w = li === 0 ? firstLineWidth : bodyWidth;
82
- wrappedCount += (0, format_1.wordWrap)(originalLines[li], w).length;
83
- }
84
- let total = msg.direction === 'out' ? 0 : 1; // header line for inbound
85
- total += Math.min(wrappedCount, cap); // body (capped by WRAPPED count)
86
- if (wrappedCount > cap)
87
- total += 1; // overflow indicator "… (N more lines)"
88
- total += 1; // separator (\n or \n\n)
89
- return total;
90
- }
91
- /**
92
- * #357: Fold consecutive `ConversationMessage`s sharing the same
93
- * non-null `broadcastId` into a single group. Runs BEFORE per-message
94
- * line estimation so `usedLines` doesn't over-count an unfolded
95
- * fan-out and shrink the viewport.
96
- *
97
- * Fold key is `broadcastId + direction` only \u2014 role is excluded so
98
- * local-echo entries (`from: 'you'`, undefined role) fold with their
99
- * server-projected counterparts (`role: 'maestro-out'`) sharing the
100
- * same id while the broadcast is in flight. Direction split keeps the
101
- * inbound/outbound perspective intact.
102
- */
103
- function foldByBroadcastId(sorted) {
104
- const groups = [];
105
- for (const m of sorted) {
106
- const last = groups[groups.length - 1];
107
- if (m.broadcastId &&
108
- last &&
109
- last[0].broadcastId === m.broadcastId &&
110
- last[0].direction === m.direction) {
111
- last.push(m);
112
- }
113
- else {
114
- groups.push([m]);
115
- }
116
- }
117
- return groups;
118
- }
119
- /**
120
- * Pure projection: merge server conversation with local-echo sent
121
- * messages, sort by timestamp, fold broadcast fan-out groups, and
122
- * format each entry into the render-ready `FormattedMsg` shape.
123
- * Exported so tests can assert on `recipientLabel` / `broadcastBadge`
124
- * / `routeLabel` derivation without mounting Ink.
125
- *
126
- * `conductorPlayerId` is the active ensemble's conductor id. Used to
127
- * suppress the `recipientLabel` on conductor-bound `maestro-out` rows
128
- * so the `\u2192 @<conductor>` prefix doesn't dominate every message.
129
- */
130
- function buildFormattedMessages(conversation, sentMessages, conductorPlayerId) {
131
- // Merge server conversation with local echo (optimistic sent not yet on server)
132
- const allConvoMsgs = [...conversation];
133
- for (const m of sentMessages) {
134
- const ts = new Date(m.timestamp).getTime();
135
- // Strip @player prefix from sent text for comparison (server doesn't have it)
136
- const sentBody = m.text.replace(/^@\S+\s+/, '');
137
- const alreadyOnServer = conversation.some(c => c.direction === 'out' &&
138
- Math.abs(new Date(c.timestamp).getTime() - ts) < 30000 &&
139
- c.text.slice(0, 60) === sentBody.slice(0, 60));
140
- if (!alreadyOnServer) {
141
- allConvoMsgs.push({
142
- id: `local-${m.timestamp}`,
143
- from: 'you',
144
- to: m.to,
145
- text: m.text,
146
- timestamp: m.timestamp,
147
- direction: 'out',
148
- // Forward broadcastId from the local-echo SentMessage so a
149
- // freshly-sent broadcast still folds before the server projection
150
- // catches up. (#357)
151
- ...(m.broadcastId !== undefined ? { broadcastId: m.broadcastId } : {}),
152
- });
153
- }
154
- }
155
- const sorted = allConvoMsgs.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
156
- // #357: fold consecutive same-broadcastId entries into groups, then
157
- // project each group as one FormattedMsg. The badge enumerates
158
- // recipients; the first message's body/timestamp/sender stand in for
159
- // the group's display fields.
160
- const groups = foldByBroadcastId(sorted);
161
- return groups.map(group => {
162
- const m = group[0];
163
- const role = m.role;
164
- const thirdParty = m.thirdParty;
165
- let routeLabel;
166
- if (role === 'conductor-out')
167
- routeLabel = `${m.from} \u2192 ${m.to}`;
168
- else if (role === 'conductor-in')
169
- routeLabel = `${m.from} \u2192 ${m.to}`;
170
- // #357: badge wins over recipientLabel \u2014 when the row represents a
171
- // folded broadcast, recipients are already enumerated inside the
172
- // badge text and the single-recipient prefix would duplicate that.
173
- let broadcastBadge;
174
- if (m.broadcastId) {
175
- broadcastBadge = {
176
- count: group.length,
177
- recipients: group.map(g => g.to),
178
- };
179
- }
180
- // #360: only set recipientLabel for `maestro-out` messages where
181
- // the recipient is neither the active conductor (whose role is the
182
- // implicit default for bare-text input) nor the legacy `'conductor'`
183
- // literal. `to` of `'maestro'` is also excluded \u2014 outbound to maestro
184
- // is meaningless (the maestro is the user's own session). Suppressed
185
- // when `broadcastBadge` is set (#357 \u2194 #360 composition).
186
- let recipientLabel;
187
- if (!broadcastBadge &&
188
- role === 'maestro-out' &&
189
- m.to && m.to !== 'maestro' && m.to !== 'conductor' && m.to !== conductorPlayerId) {
190
- recipientLabel = m.to;
191
- }
192
- // Pre-render the inline first-line prefix once so `estimateLines`
193
- // and the renderer don't each call `broadcastBadgeText` separately.
194
- const firstLinePrefix = makeFirstLinePrefix({ broadcastBadge, recipientLabel });
195
- return {
196
- sender: m.from,
197
- time: formatTime(m.timestamp),
198
- body: m.text,
199
- direction: m.direction,
200
- role,
201
- thirdParty,
202
- routeLabel,
203
- recipientLabel,
204
- broadcastBadge,
205
- ...(firstLinePrefix ? { firstLinePrefix } : {}),
206
- };
207
- });
208
- }
209
- function ConversationStream({ conversation, sentMessages, contentHeight, overflowRef, conductorPlayerId }) {
210
- const { Text } = (0, ink_context_1.useInk)();
211
- const termCols = process.stdout.columns || 80;
212
- const bodyWidth = Math.max(20, termCols - 4);
213
- const formatted = buildFormattedMessages(conversation, sentMessages, conductorPlayerId);
214
- // Work backwards from newest — include as many as fit in viewport
215
- let usedLines = 0;
216
- let startIdx = formatted.length;
217
- for (let i = formatted.length - 1; i >= 0; i--) {
218
- const needed = estimateLines(formatted[i], termCols);
219
- if (usedLines + needed > contentHeight - 1)
220
- break;
221
- usedLines += needed;
222
- startIdx = i;
223
- }
224
- // Store overflow data for parent to commit to Static scrollback
225
- overflowRef.current = { formatted, startIdx };
226
- const visibleMsgs = formatted.slice(startIdx);
227
- const children = [];
228
- if (visibleMsgs.length === 0) {
229
- children.push('\n');
230
- children.push(react_1.default.createElement(Text, { key: 'empty', color: theme_1.THEME.dim }, ' No messages yet. Type to send.'));
231
- }
232
- else {
233
- for (let i = 0; i < visibleMsgs.length; i++) {
234
- const msg = visibleMsgs[i];
235
- children.push(i === 0 ? '\n' : '\n\n');
236
- const isOut = msg.direction === 'out';
237
- const bg = isOut ? theme_1.THEME.inputBg : undefined;
238
- // Word-wrap body. #360: when a recipientLabel prefix is rendered
239
- // inline on the first line, wrap the FIRST source line at a
240
- // narrower width to leave room. Continuation source lines (and
241
- // their wraps) use the full bodyWidth.
242
- const prefixLen = msg.firstLinePrefix?.length ?? 0;
243
- const firstLineWidth = Math.max(20, bodyWidth - prefixLen);
244
- const originalLines = msg.body.split('\n');
245
- const wrappedLines = [];
246
- for (let li = 0; li < originalLines.length; li++) {
247
- const w = li === 0 ? firstLineWidth : bodyWidth;
248
- wrappedLines.push(...(0, format_1.wordWrap)(originalLines[li], w));
249
- }
250
- const cap = maxLines(msg);
251
- const displayLines = wrappedLines.slice(0, cap);
252
- if (isOut) {
253
- // Outbound: inline — ♩ first line, then wrapped continuation lines (no timestamp).
254
- // The first-line prefix string is precomputed in
255
- // `buildFormattedMessages` (#357 broadcast badge / #360 directed
256
- // recipient — mutually exclusive). Color picks are local: badge
257
- // accent vs recipient dim.
258
- const prefixEl = msg.firstLinePrefix
259
- ? react_1.default.createElement(Text, { key: `pre-${i}`, backgroundColor: bg, color: msg.broadcastBadge ? theme_1.THEME.accent : theme_1.THEME.dim }, msg.firstLinePrefix)
260
- : null;
261
- for (let j = 0; j < displayLines.length; j++) {
262
- if (j > 0)
263
- children.push('\n');
264
- if (j === 0) {
265
- const firstText = displayLines[0];
266
- const pad = ' '.repeat(Math.max(0, termCols - 2 - 3 - prefixLen - firstText.length));
267
- children.push(react_1.default.createElement(react_1.default.Fragment, { key: `bl-${i}-0` }, react_1.default.createElement(Text, { backgroundColor: bg, color: theme_1.THEME.accent, bold: true }, ' \u2669 '), ...(prefixEl ? [prefixEl] : []), react_1.default.createElement(Text, { backgroundColor: bg, color: theme_1.THEME.text }, firstText), react_1.default.createElement(Text, { backgroundColor: bg, color: theme_1.THEME.dim }, pad)));
268
- }
269
- else {
270
- const contLine = `${INDENT}${displayLines[j]}`;
271
- children.push(react_1.default.createElement(Text, { key: `bl-${i}-${j}`, backgroundColor: bg, color: theme_1.THEME.text }, contLine.padEnd(termCols - 2)));
272
- }
273
- }
274
- if (wrappedLines.length > cap) {
275
- const moreLine = `${INDENT}\u2026 (${wrappedLines.length - cap} more lines)`;
276
- children.push('\n');
277
- children.push(react_1.default.createElement(Text, { key: `mt-${i}`, backgroundColor: bg, color: theme_1.THEME.dim }, moreLine.padEnd(termCols - 2)));
278
- }
279
- }
280
- else {
281
- // Inbound: header line + body lines
282
- const headerLabel = msg.routeLabel || msg.sender;
283
- const headerColor = msg.thirdParty ? theme_1.THEME.dim : theme_1.THEME.accent;
284
- const bodyColor = msg.thirdParty ? theme_1.THEME.textMuted : theme_1.THEME.text;
285
- const headerPrefix = msg.thirdParty ? ' ' : ' \u2190 ';
286
- children.push(react_1.default.createElement(react_1.default.Fragment, { key: `hdr-${i}` }, react_1.default.createElement(Text, { color: theme_1.THEME.dim }, headerPrefix), react_1.default.createElement(Text, { color: headerColor }, headerLabel), react_1.default.createElement(Text, { color: theme_1.THEME.dim }, ` ${msg.time}`)));
287
- for (let j = 0; j < displayLines.length; j++) {
288
- children.push('\n');
289
- children.push(react_1.default.createElement(Text, { key: `bl-${i}-${j}`, color: bodyColor }, `${INDENT}${displayLines[j]}`));
290
- }
291
- if (wrappedLines.length > cap) {
292
- children.push('\n');
293
- children.push(react_1.default.createElement(Text, { key: `mt-${i}`, color: theme_1.THEME.dim }, `${INDENT}\u2026 (${wrappedLines.length - cap} more lines)`));
294
- }
295
- }
296
- }
297
- }
298
- // Explicit padding: fill remaining space so content = exactly contentHeight lines
299
- // usedLines = exact terminal lines for messages (from fitting loop)
300
- // 1 line reserved for footer margin
301
- const paddingLines = Math.max(0, contentHeight - usedLines - 1);
302
- if (paddingLines > 0) {
303
- children.push('\n'.repeat(paddingLines));
304
- }
305
- children.push('\n'); // exactly 1-line footer margin
306
- return react_1.default.createElement(Text, null, ...children);
307
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * CreateEnsembleWizard — step-by-step flow for creating a new ensemble.
3
- *
4
- * Steps: name → workDir → lineup (optional) → confirm
5
- *
6
- * Minimal-Box pattern: single <Text> root for static content,
7
- * one <Box> for Ink's TextInput on text-input steps.
8
- */
9
- import React from 'react';
10
- import type { CreateEnsembleState, CreateEnsembleAnswers } from '../store';
11
- export interface CreateEnsembleWizardProps {
12
- state: CreateEnsembleState;
13
- onAnswer: (answer: Partial<CreateEnsembleAnswers>) => void;
14
- onBack: () => void;
15
- onConfirm: () => void;
16
- onCancel: () => void;
17
- onDone: () => void;
18
- }
19
- export declare function CreateEnsembleWizard({ state, onAnswer, onBack, onConfirm, onCancel, onDone }: CreateEnsembleWizardProps): React.FunctionComponentElement<React.FragmentProps> | React.CElement<{}, React.Component<{}, any, any>>;