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
- /** Marker file name dropped in the new home post-migration. */
2
- export declare const MIGRATION_MARKER_FILENAME = ".migrated-from-claude-tempo";
3
- export type MigrationStatus = 'no-legacy' | 'already-migrated' | 'migrated' | 'skipped' | 'failed';
4
- export interface LegacyMigrationResult {
5
- status: MigrationStatus;
6
- legacyHome?: string;
7
- newHome?: string;
8
- /** Relative paths inside the new home that were copied this run. */
9
- copiedFiles?: string[];
10
- /** Errors collected during partial-copy resume; populated when status === 'failed'. */
11
- errors?: string[];
12
- /** Set when status === 'skipped' to explain why. */
13
- reason?: string;
14
- }
15
- export interface LegacyMigrationOpts {
16
- /** Don't write anything; report what would happen. */
17
- dryRun?: boolean;
18
- /** Override the conflict guard (existing new-home without marker) AND the volatile-state guard. */
19
- force?: boolean;
20
- /** Which profile to migrate. `'prod'` = `~/.claude-tempo` → `~/.agent-tempo`; `'dev'` = `-dev` variants. Default `'prod'`. */
21
- profile?: 'prod' | 'dev';
22
- /** Test seam — override `homedir()`. */
23
- homeDir?: string;
24
- }
25
- /**
26
- * One-shot legacy home migration. See module doc-comment for the full
27
- * contract. Never throws — all error paths return a structured
28
- * {@link LegacyMigrationResult}.
29
- */
30
- export declare function migrateLegacyHome(opts?: LegacyMigrationOpts): Promise<LegacyMigrationResult>;
31
- /**
32
- * Format a {@link LegacyMigrationResult} for human display (CLI verb output
33
- * + bootstrap step `notes`).
34
- */
35
- export declare function formatMigrationResult(r: LegacyMigrationResult): string;
@@ -1,341 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.MIGRATION_MARKER_FILENAME = void 0;
37
- exports.migrateLegacyHome = migrateLegacyHome;
38
- exports.formatMigrationResult = formatMigrationResult;
39
- /**
40
- * Legacy home migration — `~/.claude-tempo/` → `~/.agent-tempo/`.
41
- *
42
- * PR-2 of the v1.0 rebrand renames the on-disk profile directory. This
43
- * helper performs a one-shot copy on first boot of the new binary so
44
- * users don't lose their saved config / lineups / state.
45
- *
46
- * Contract (see brief):
47
- * 1. **Idempotent.** Re-running on a migrated home is `'already-migrated'`.
48
- * 2. **Copy, not move.** Legacy `~/.claude-tempo/` stays untouched as a
49
- * safety net for one release.
50
- * 3. **Marker file.** Writes `~/.agent-tempo/.migrated-from-claude-tempo`
51
- * with `{ migratedAt, copiedFromHash, files }`.
52
- * 4. **Conflict policy.** If `~/.agent-tempo/` exists AND has no marker,
53
- * refuse with `'skipped'`; user must pass `force: true` or delete.
54
- * Don't clobber a user-initiated new home.
55
- * 5. **Partial-copy resume.** Per-file SHA-256 in the marker — re-running
56
- * a partially-completed run finishes only the missing/changed files.
57
- * 6. **Files copied.** Allowlist — `config.json`, `.bootstrap-cache.json`,
58
- * any `*.yaml` user-stashed lineup files, plus subdirs `ensembles/`,
59
- * `state/`, `coat-check/` (forward-compat — fine if absent). The
60
- * volatile runtime trio (`daemon.pid`, `daemon.port`, `daemon.log`)
61
- * is intentionally skipped — let the daemon recreate them.
62
- * (Historical note: the original brief named the lineup subdir
63
- * `lineups/`, but the actual on-disk name has always been
64
- * `ensembles/` — see `src/ensemble/{saver,loader}.ts`. The original
65
- * implementation copied the brief's typo verbatim, which silently
66
- * stranded user lineups across the v0.x → v1.x migration. Fixed by
67
- * pointing the allowlist at the real directory name.)
68
- * 7. **Volatile-state guard.** If `daemon.pid` is present in the legacy
69
- * home (likely-running daemon), refuses unless `force: true`.
70
- *
71
- * Single owner file — narrow blast radius. The CLI verb in `src/cli.ts`
72
- * and the bootstrap step in `src/cli/startup.ts` are the only callers.
73
- */
74
- const fs = __importStar(require("fs"));
75
- const path = __importStar(require("path"));
76
- const crypto = __importStar(require("crypto"));
77
- const os_1 = require("os");
78
- const config_1 = require("../config");
79
- /** Legacy POSIX/macOS/Windows home dir name (pre-v1.0 rebrand). */
80
- const LEGACY_PROD_HOME_DIR_NAME = '.claude-tempo';
81
- const LEGACY_DEV_HOME_DIR_NAME = '.claude-tempo-dev';
82
- /** Marker file name dropped in the new home post-migration. */
83
- exports.MIGRATION_MARKER_FILENAME = '.migrated-from-claude-tempo';
84
- /** Volatile runtime state — never copied (daemon recreates on boot). */
85
- const VOLATILE_FILES = new Set(['daemon.pid', 'daemon.port', 'daemon.log']);
86
- /** Allowlisted top-level files. `*.yaml` is matched as a glob. */
87
- const ALLOWLIST_FILES = new Set(['config.json', '.bootstrap-cache.json']);
88
- /** Allowlisted top-level subdirs (recursive copy). Forward-compat — fine if absent. */
89
- const ALLOWLIST_SUBDIRS = ['ensembles', 'state', 'coat-check'];
90
- function legacyHomeFor(profile, home) {
91
- return path.join(home, profile === 'dev' ? LEGACY_DEV_HOME_DIR_NAME : LEGACY_PROD_HOME_DIR_NAME);
92
- }
93
- function newHomeFor(profile, home) {
94
- return path.join(home, profile === 'dev' ? config_1.DEV_HOME_DIR_NAME : config_1.PROD_HOME_DIR_NAME);
95
- }
96
- /** SHA-256 hex digest of a file's contents. */
97
- function hashFile(absPath) {
98
- const hash = crypto.createHash('sha256');
99
- hash.update(fs.readFileSync(absPath));
100
- return hash.digest('hex');
101
- }
102
- /** Aggregate hash of a whole tree — order-stable by sorting relative paths. */
103
- function hashTree(root, files) {
104
- const hash = crypto.createHash('sha256');
105
- for (const f of files.slice().sort((a, b) => a.rel.localeCompare(b.rel))) {
106
- hash.update(f.rel + ':' + hashFile(f.abs) + '\n');
107
- }
108
- return hash.digest('hex');
109
- }
110
- /** Read the migration marker, returning `null` if absent / malformed. */
111
- function readMarker(newHome) {
112
- const markerPath = path.join(newHome, exports.MIGRATION_MARKER_FILENAME);
113
- if (!fs.existsSync(markerPath))
114
- return null;
115
- try {
116
- const parsed = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
117
- if (typeof parsed.copiedFromHash !== 'string' || !Array.isArray(parsed.files))
118
- return null;
119
- return {
120
- migratedAt: parsed.migratedAt ?? '',
121
- copiedFromHash: parsed.copiedFromHash,
122
- files: parsed.files,
123
- fileHashes: parsed.fileHashes ?? {},
124
- };
125
- }
126
- catch {
127
- return null;
128
- }
129
- }
130
- /**
131
- * Walk a directory tree, yielding `{ rel, abs }` entries for files matching
132
- * the allowlist. Returns `[]` if `legacyHome` doesn't exist.
133
- *
134
- * Allowlist:
135
- * - top-level files in {@link ALLOWLIST_FILES}
136
- * - top-level `*.yaml` / `*.yml` files
137
- * - everything (recursively) under top-level subdirs in {@link ALLOWLIST_SUBDIRS}
138
- *
139
- * The volatile trio (`daemon.pid`/`.port`/`.log`) is excluded everywhere.
140
- */
141
- function walkLegacyTree(legacyHome) {
142
- if (!fs.existsSync(legacyHome))
143
- return [];
144
- const out = [];
145
- // Top-level entries
146
- let entries;
147
- try {
148
- entries = fs.readdirSync(legacyHome, { withFileTypes: true });
149
- }
150
- catch {
151
- return [];
152
- }
153
- for (const entry of entries) {
154
- if (VOLATILE_FILES.has(entry.name))
155
- continue;
156
- if (entry.name === exports.MIGRATION_MARKER_FILENAME)
157
- continue;
158
- const abs = path.join(legacyHome, entry.name);
159
- if (entry.isFile()) {
160
- if (ALLOWLIST_FILES.has(entry.name) || /\.ya?ml$/i.test(entry.name)) {
161
- out.push({ rel: entry.name, abs });
162
- }
163
- continue;
164
- }
165
- if (entry.isDirectory() && ALLOWLIST_SUBDIRS.includes(entry.name)) {
166
- walkSubtree(abs, entry.name, out);
167
- }
168
- }
169
- return out;
170
- }
171
- function walkSubtree(absRoot, relRoot, out) {
172
- let entries;
173
- try {
174
- entries = fs.readdirSync(absRoot, { withFileTypes: true });
175
- }
176
- catch {
177
- return;
178
- }
179
- for (const entry of entries) {
180
- const abs = path.join(absRoot, entry.name);
181
- const rel = path.posix.join(relRoot, entry.name);
182
- if (entry.isFile()) {
183
- out.push({ rel, abs });
184
- }
185
- else if (entry.isDirectory()) {
186
- walkSubtree(abs, rel, out);
187
- }
188
- }
189
- }
190
- /**
191
- * One-shot legacy home migration. See module doc-comment for the full
192
- * contract. Never throws — all error paths return a structured
193
- * {@link LegacyMigrationResult}.
194
- */
195
- async function migrateLegacyHome(opts = {}) {
196
- const profile = opts.profile ?? 'prod';
197
- const home = opts.homeDir ?? (0, os_1.homedir)();
198
- const legacyHome = legacyHomeFor(profile, home);
199
- const newHome = newHomeFor(profile, home);
200
- // 1. No legacy → nothing to do.
201
- if (!fs.existsSync(legacyHome)) {
202
- return { status: 'no-legacy', legacyHome, newHome };
203
- }
204
- // 2. Volatile state guard — daemon.pid likely means a running daemon.
205
- const pidPath = path.join(legacyHome, 'daemon.pid');
206
- if (!opts.force && fs.existsSync(pidPath)) {
207
- return {
208
- status: 'skipped',
209
- legacyHome,
210
- newHome,
211
- reason: `Legacy daemon.pid present at ${pidPath}. The daemon may still be running — ` +
212
- `stop it first (\`agent-tempo daemon stop\` or \`claude-tempo daemon stop\`) ` +
213
- `and re-run, or pass \`--force\` to migrate anyway.`,
214
- };
215
- }
216
- // 3. Conflict policy — new home exists without our marker = user-initiated, refuse.
217
- const newHomeExists = fs.existsSync(newHome);
218
- const existingMarker = newHomeExists ? readMarker(newHome) : null;
219
- if (newHomeExists && !existingMarker && !opts.force) {
220
- return {
221
- status: 'skipped',
222
- legacyHome,
223
- newHome,
224
- reason: `Refusing to overwrite ${newHome} — directory exists and was not created ` +
225
- `by a previous migration (no \`${exports.MIGRATION_MARKER_FILENAME}\` marker found). ` +
226
- `Pass \`--force\` to migrate anyway, or delete the directory first.`,
227
- };
228
- }
229
- // 4. Enumerate legacy files + hash the source tree.
230
- const legacyFiles = walkLegacyTree(legacyHome);
231
- if (legacyFiles.length === 0) {
232
- // Legacy dir exists but holds only volatile state / nothing to migrate.
233
- return { status: 'no-legacy', legacyHome, newHome };
234
- }
235
- const sourceHash = hashTree(legacyHome, legacyFiles);
236
- // 5. Idempotency — same source content + valid marker = already migrated.
237
- if (existingMarker && existingMarker.copiedFromHash === sourceHash) {
238
- return {
239
- status: 'already-migrated',
240
- legacyHome,
241
- newHome,
242
- copiedFiles: [],
243
- };
244
- }
245
- // 6. Plan the copy. Partial-copy resume: skip files whose dest already has the right SHA.
246
- const plan = [];
247
- const errors = [];
248
- for (const f of legacyFiles) {
249
- let srcHash;
250
- try {
251
- srcHash = hashFile(f.abs);
252
- }
253
- catch (err) {
254
- errors.push(`hash failed for ${f.rel}: ${err.message}`);
255
- continue;
256
- }
257
- const destAbs = path.join(newHome, f.rel);
258
- if (existingMarker?.fileHashes?.[f.rel] === srcHash && fs.existsSync(destAbs)) {
259
- try {
260
- if (hashFile(destAbs) === srcHash)
261
- continue; // already copied with right content
262
- }
263
- catch { /* fall through and re-copy */ }
264
- }
265
- plan.push({ rel: f.rel, abs: f.abs, destAbs, sourceHash: srcHash });
266
- }
267
- if (opts.dryRun) {
268
- return {
269
- status: 'migrated',
270
- legacyHome,
271
- newHome,
272
- copiedFiles: plan.map((p) => p.rel),
273
- };
274
- }
275
- // 7. Execute the copy.
276
- const copied = [];
277
- const fileHashes = { ...(existingMarker?.fileHashes ?? {}) };
278
- try {
279
- fs.mkdirSync(newHome, { recursive: true });
280
- }
281
- catch (err) {
282
- errors.push(`mkdir ${newHome}: ${err.message}`);
283
- return { status: 'failed', legacyHome, newHome, errors };
284
- }
285
- for (const p of plan) {
286
- try {
287
- fs.mkdirSync(path.dirname(p.destAbs), { recursive: true });
288
- fs.copyFileSync(p.abs, p.destAbs);
289
- copied.push(p.rel);
290
- fileHashes[p.rel] = p.sourceHash;
291
- }
292
- catch (err) {
293
- errors.push(`copy ${p.rel}: ${err.message}`);
294
- }
295
- }
296
- if (errors.length > 0 && copied.length === 0) {
297
- return { status: 'failed', legacyHome, newHome, errors };
298
- }
299
- // 8. Drop the marker — captures final state for idempotency.
300
- const allFiles = legacyFiles.map((f) => f.rel).sort();
301
- const marker = {
302
- migratedAt: new Date().toISOString(),
303
- copiedFromHash: sourceHash,
304
- files: allFiles,
305
- fileHashes,
306
- };
307
- try {
308
- fs.writeFileSync(path.join(newHome, exports.MIGRATION_MARKER_FILENAME), JSON.stringify(marker, null, 2) + '\n');
309
- }
310
- catch (err) {
311
- errors.push(`marker write: ${err.message}`);
312
- }
313
- return {
314
- status: 'migrated',
315
- legacyHome,
316
- newHome,
317
- copiedFiles: copied,
318
- ...(errors.length > 0 ? { errors } : {}),
319
- };
320
- }
321
- /**
322
- * Format a {@link LegacyMigrationResult} for human display (CLI verb output
323
- * + bootstrap step `notes`).
324
- */
325
- function formatMigrationResult(r) {
326
- switch (r.status) {
327
- case 'no-legacy':
328
- return `No legacy home at ${r.legacyHome} — nothing to migrate.`;
329
- case 'already-migrated':
330
- return `Already migrated — ${r.newHome} is up-to-date with ${r.legacyHome}.`;
331
- case 'migrated': {
332
- const n = r.copiedFiles?.length ?? 0;
333
- const errSuffix = r.errors?.length ? ` (${r.errors.length} error(s) recorded)` : '';
334
- return `Migrated ${n} file(s) ${r.legacyHome} → ${r.newHome}${errSuffix}.`;
335
- }
336
- case 'skipped':
337
- return r.reason ?? 'Migration skipped.';
338
- case 'failed':
339
- return `Migration failed: ${(r.errors ?? []).join('; ')}`;
340
- }
341
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Shared helper for restore-after-shutdown: make sure a conductor terminal
3
- * is attached to an ensemble before `/restore` completes. Called by the
4
- * TUI `/restore` slash command (`App.tsx`, `commands.ts`).
5
- *
6
- * NOTE: This file lives in `src/client/` because the CLI `restore` command
7
- * was expected to adopt it. If no CLI consumer adopts within 2 PRs after
8
- * #308 merges, move to `src/tui/utils/` — current consumers are TUI-only.
9
- *
10
- * Returns a structured outcome so callers can render a summary without
11
- * parsing strings. Never throws — callers should treat a missing conductor
12
- * as a soft failure.
13
- */
14
- import type { TempoClientWithSpawn } from './interface';
15
- export type EnsureConductorSpawnedOutcome = {
16
- spawned: false;
17
- reason: 'alreadyLive';
18
- } | {
19
- spawned: true;
20
- } | {
21
- spawned: false;
22
- reason: 'spawnFailed';
23
- error: string;
24
- };
25
- /**
26
- * If the ensemble already has a conductor session in a live phase, no-op.
27
- * Otherwise shell out via {@link TempoClientWithSpawn.spawnConductor} to
28
- * open a conductor terminal. The `agent-tempo up` path is idempotent at
29
- * the workflow layer, so a benign race (two restores in flight) converges
30
- * on one workflow.
31
- *
32
- * Typed against `TempoClientWithSpawn` (not `TempoClient` alias) so the
33
- * spawn dependency is explicit at the call site (#308 follow-up).
34
- */
35
- export declare function ensureConductorSpawned(ensemble: string, client: TempoClientWithSpawn): Promise<EnsureConductorSpawnedOutcome>;
@@ -1,48 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensureConductorSpawned = ensureConductorSpawned;
4
- /**
5
- * If the ensemble already has a conductor session in a live phase, no-op.
6
- * Otherwise shell out via {@link TempoClientWithSpawn.spawnConductor} to
7
- * open a conductor terminal. The `agent-tempo up` path is idempotent at
8
- * the workflow layer, so a benign race (two restores in flight) converges
9
- * on one workflow.
10
- *
11
- * Typed against `TempoClientWithSpawn` (not `TempoClient` alias) so the
12
- * spawn dependency is explicit at the call site (#308 follow-up).
13
- */
14
- async function ensureConductorSpawned(ensemble, client) {
15
- try {
16
- const info = await client.attachmentInfo(ensemble, 'conductor');
17
- const phase = info.phase;
18
- const alreadyLive = phase === 'attached' || phase === 'processing'
19
- || phase === 'awaiting' || phase === 'booting';
20
- if (alreadyLive)
21
- return { spawned: false, reason: 'alreadyLive' };
22
- }
23
- catch {
24
- // No conductor session / query failed — fall through to spawn.
25
- }
26
- try {
27
- await client.spawnConductor({ ensemble });
28
- return { spawned: true };
29
- }
30
- catch (err) {
31
- const message = err instanceof Error ? err.message : String(err);
32
- // #306: `/restore` runs two parallel paths that can both spawn the
33
- // conductor — `restoreOrphansOnce` reattaches the orphan adapter via
34
- // `deliverRestart`, and this helper falls through to a fresh spawn when
35
- // the attachment-info query returns a non-live phase. If the orphan
36
- // reattach wins the race, our spawn (`agent-tempo up <ensemble>`)
37
- // throws "A conductor is already running for ensemble" — which is the
38
- // success condition for THIS helper. Swallow the race and report alive.
39
- if (/conductor is already running/i.test(message)) {
40
- return { spawned: false, reason: 'alreadyLive' };
41
- }
42
- return {
43
- spawned: false,
44
- reason: 'spawnFailed',
45
- error: message,
46
- };
47
- }
48
- }
package/dist/tui/App.d.ts DELETED
@@ -1,85 +0,0 @@
1
- /**
2
- * Root TUI application component — chat-focused shell with slash commands.
3
- *
4
- * Layout (top to bottom):
5
- * - TitleBar (pinned)
6
- * - Divider
7
- * - Static scroll-up history
8
- * - Live content area (splash, main, chat, error)
9
- * - Divider
10
- * - PromptArea (pinned)
11
- */
12
- import React from 'react';
13
- import type { TuiState } from './store';
14
- import type { TempoClient } from '../client';
15
- interface AppProps {
16
- api: TempoClient;
17
- /** If provided, start directly in ensemble view. */
18
- ensemble?: string;
19
- /** Default agent type from config (defaults to 'claude'). */
20
- defaultAgent?: 'claude' | 'copilot';
21
- }
22
- export declare function App({ api, ensemble, defaultAgent }: AppProps): React.ReactSVGElement | React.FunctionComponentElement<React.FragmentProps>;
23
- /**
24
- * #306: Strip a leading kind-icon (`✗ `, `⚠ `, `ⓘ `) from notification
25
- * content. Defensive: many call sites historically prepended the icon into
26
- * the message string, and the renderer also prepends a kind-based icon —
27
- * without this normalization the user sees the icon twice (e.g.
28
- * `✗ ✗ Cannot destroy the conductor …`). Exported for unit testing.
29
- */
30
- export declare function stripLeadingIcon(content: string): string;
31
- /**
32
- * #306: True when the TUI is on the home picker view — `phase === 'main'`
33
- * AND `view === 'home'`. The chat input has no target on this view (the
34
- * ensemble has not been entered yet), and HomeView owns Enter to navigate
35
- * its own row list. Render guard for PromptArea + the second divider so
36
- * the input cannot double-fire alongside HomeView's own `useInput`.
37
- *
38
- * Pure function, exported so tests can pin the guard's logic without
39
- * standing up an Ink render. Mirrors the splash-phase bypass pattern.
40
- */
41
- export declare function isHomeView(state: Pick<TuiState, 'phase' | 'view'>): boolean;
42
- /**
43
- * #306: Build the pinned-confirmation line(s) for the current state. Returns
44
- * an array (possibly empty) of `{ key, text }` entries — one per active
45
- * `confirming*` state field. Rendered above the notifications stack by
46
- * `renderPinnedConfirmations` and sized by `countPinnedConfirmationLines`
47
- * so `FOOTER_LINES` reserves terminal rows correctly.
48
- *
49
- * Pure function, exported for unit testing — no Ink imports, no dispatch.
50
- * The render helper below is the only caller that wraps these in Text nodes.
51
- */
52
- export declare function pinnedConfirmationLines(state: Pick<TuiState, 'confirmingStop' | 'confirmingStopReason' | 'confirmingDisband' | 'confirmingEnsembleDestroy' | 'confirmingLineup'>): Array<{
53
- key: string;
54
- text: string;
55
- }>;
56
- /**
57
- * #306: Count of pinned confirmation lines — one per active `confirming*`
58
- * state field. Consumed by the `FOOTER_LINES` reservation so the live
59
- * content area shrinks when a confirmation is active, keeping the pinned
60
- * prompt on-screen.
61
- */
62
- export declare function countPinnedConfirmationLines(state: Pick<TuiState, 'confirmingStop' | 'confirmingStopReason' | 'confirmingDisband' | 'confirmingEnsembleDestroy' | 'confirmingLineup'>): number;
63
- /**
64
- * #306 follow-up: Build the pinned tip line for the current paused/held
65
- * state. Returns `null` when neither flag is set — no tip should render.
66
- *
67
- * The tip appears below the input prompt in a dim color (informational,
68
- * not an error or warning) and tells the user which slash commands they
69
- * need to fully resume the ensemble. `/load_lineup` flips both flags;
70
- * `/play` clears only paused; `/go` clears only held — without this
71
- * tip users would unpause an ensemble and stare at frozen players.
72
- *
73
- * Pure function, exported for unit testing — no Ink imports.
74
- */
75
- export declare function pinnedTipLine(state: Pick<TuiState, 'ensemblePaused' | 'ensembleHeld' | 'activeEnsemble'>): {
76
- key: string;
77
- text: string;
78
- } | null;
79
- /**
80
- * Count of pinned tip lines (0 or 1). Mirrors
81
- * {@link countPinnedConfirmationLines} so `FOOTER_LINES` can reserve a
82
- * row for the tip without re-evaluating the state shape twice.
83
- */
84
- export declare function countPinnedTipLines(state: Pick<TuiState, 'ensemblePaused' | 'ensembleHeld' | 'activeEnsemble'>): number;
85
- export {};