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.
- package/CLAUDE.md +10 -13
- package/README.md +21 -15
- package/dashboard/dist/assets/index-BbR7zIdK.js.map +1 -1
- package/dashboard/package.json +1 -1
- package/dist/activities/hard-terminate.d.ts +2 -2
- package/dist/activities/hard-terminate.js +3 -9
- package/dist/activities/maestro.d.ts +8 -13
- package/dist/activities/maestro.js +3 -10
- package/dist/activities/outbox.js +16 -2
- package/dist/activities/resolve.d.ts +18 -7
- package/dist/activities/resolve.js +58 -46
- package/dist/adapters/base.js +3 -0
- package/dist/adapters/claude-code/adapter.js +7 -0
- package/dist/cli/command-center-command.js +15 -1
- package/dist/cli/commands.d.ts +8 -0
- package/dist/cli/commands.js +79 -11
- package/dist/cli/daemon-command.js +19 -33
- package/dist/cli/help-text.js +9 -7
- package/dist/cli/home-command.d.ts +26 -0
- package/dist/cli/home-command.js +156 -0
- package/dist/cli/removed-verbs.d.ts +0 -4
- package/dist/cli/removed-verbs.js +23 -12
- package/dist/cli/sa-preflight.d.ts +0 -14
- package/dist/cli/sa-preflight.js +1 -17
- package/dist/cli/startup.d.ts +1 -10
- package/dist/cli/startup.js +0 -40
- package/dist/cli.js +34 -80
- package/dist/client/core.js +13 -15
- package/dist/client/index.d.ts +2 -1
- package/dist/client/interface.d.ts +5 -14
- package/dist/client/with-spawn.d.ts +9 -4
- package/dist/client/with-spawn.js +7 -10
- package/dist/config.d.ts +14 -13
- package/dist/config.js +14 -0
- package/dist/constants.d.ts +50 -0
- package/dist/constants.js +60 -1
- package/dist/daemon.js +81 -0
- package/dist/http/auth.d.ts +3 -21
- package/dist/http/auth.js +10 -31
- package/dist/http/event-types.d.ts +38 -4
- package/dist/http/orphans.d.ts +3 -3
- package/dist/http/orphans.js +3 -3
- package/dist/http/server.d.ts +0 -5
- package/dist/http/server.js +79 -29
- package/dist/http/snapshot.js +3 -0
- package/dist/http/writes.d.ts +1 -1
- package/dist/http/writes.js +25 -2
- package/dist/observability/nondeterminism-alarm.d.ts +113 -0
- package/dist/observability/nondeterminism-alarm.js +162 -0
- package/dist/palette/index.d.ts +6 -7
- package/dist/palette/index.js +6 -7
- package/dist/pi/mission-control/actions.d.ts +67 -1
- package/dist/pi/mission-control/actions.js +61 -0
- package/dist/pi/mission-control/extension.d.ts +77 -0
- package/dist/pi/mission-control/extension.js +171 -0
- package/dist/pi/workflow-client.d.ts +14 -16
- package/dist/pi/workflow-client.js +18 -44
- package/dist/server-tools.js +30 -29
- package/dist/server.js +42 -0
- package/dist/tools/action-guard.d.ts +23 -0
- package/dist/tools/action-guard.js +33 -0
- package/dist/tools/coat-check.d.ts +20 -0
- package/dist/tools/coat-check.js +129 -0
- package/dist/tools/gate.d.ts +13 -0
- package/dist/tools/gate.js +88 -0
- package/dist/tools/schedule.d.ts +18 -0
- package/dist/tools/schedule.js +93 -1
- package/dist/tools/stage.d.ts +17 -0
- package/dist/tools/stage.js +85 -1
- package/dist/tools/state.d.ts +14 -0
- package/dist/tools/state.js +95 -0
- package/dist/types.d.ts +54 -7
- package/dist/upgrade/boot-guard.d.ts +95 -0
- package/dist/upgrade/boot-guard.js +88 -0
- package/dist/upgrade/from-upgrade.d.ts +95 -0
- package/dist/upgrade/from-upgrade.js +330 -0
- package/dist/upgrade/phase-engine.js +4 -1
- package/dist/utils/duration.d.ts +3 -6
- package/dist/utils/duration.js +3 -6
- package/dist/utils/format-hosts.d.ts +1 -1
- package/dist/utils/format-hosts.js +0 -2
- package/dist/utils/grpc-shutdown-guard.js +0 -4
- package/dist/utils/hosts.d.ts +4 -10
- package/dist/utils/hosts.js +12 -28
- package/dist/utils/orphan-guard.d.ts +37 -0
- package/dist/utils/orphan-guard.js +26 -0
- package/dist/utils/search-attributes.d.ts +28 -18
- package/dist/utils/search-attributes.js +48 -18
- package/dist/utils/visibility-deadline.d.ts +16 -0
- package/dist/utils/visibility-deadline.js +16 -0
- package/dist/workflows/maestro-signals.d.ts +0 -13
- package/dist/workflows/maestro-signals.js +6 -14
- package/dist/workflows/maestro.js +27 -32
- package/dist/workflows/scheduler.js +7 -1
- package/dist/workflows/session.js +237 -153
- package/dist/workflows/signals.d.ts +9 -1
- package/dist/workflows/signals.js +8 -6
- package/package.json +1 -6
- package/workflow-bundle.js +478 -225
- package/dist/cli/legacy-migration.d.ts +0 -35
- package/dist/cli/legacy-migration.js +0 -341
- package/dist/client/ensure-conductor-spawned.d.ts +0 -35
- package/dist/client/ensure-conductor-spawned.js +0 -48
- package/dist/tui/App.d.ts +0 -85
- package/dist/tui/App.js +0 -1791
- package/dist/tui/bootstrap-types.d.ts +0 -46
- package/dist/tui/bootstrap-types.js +0 -7
- package/dist/tui/commands.d.ts +0 -71
- package/dist/tui/commands.js +0 -1375
- package/dist/tui/components/ChatView.d.ts +0 -35
- package/dist/tui/components/ChatView.js +0 -54
- package/dist/tui/components/CommandPalette.d.ts +0 -21
- package/dist/tui/components/CommandPalette.js +0 -67
- package/dist/tui/components/ConversationStream.d.ts +0 -114
- package/dist/tui/components/ConversationStream.js +0 -307
- package/dist/tui/components/CreateEnsembleWizard.d.ts +0 -19
- package/dist/tui/components/CreateEnsembleWizard.js +0 -223
- package/dist/tui/components/DestroyConfirmModal.d.ts +0 -17
- package/dist/tui/components/DestroyConfirmModal.js +0 -62
- package/dist/tui/components/ErrorView.d.ts +0 -31
- package/dist/tui/components/ErrorView.js +0 -129
- package/dist/tui/components/HomeView.d.ts +0 -54
- package/dist/tui/components/HomeView.js +0 -306
- package/dist/tui/components/LoadLineupModal.d.ts +0 -18
- package/dist/tui/components/LoadLineupModal.js +0 -79
- package/dist/tui/components/MainView.d.ts +0 -21
- package/dist/tui/components/MainView.js +0 -107
- package/dist/tui/components/NewEnsembleModal.d.ts +0 -9
- package/dist/tui/components/NewEnsembleModal.js +0 -73
- package/dist/tui/components/Picker.d.ts +0 -23
- package/dist/tui/components/Picker.js +0 -70
- package/dist/tui/components/PlayerDetailView.d.ts +0 -26
- package/dist/tui/components/PlayerDetailView.js +0 -118
- package/dist/tui/components/PromptArea.d.ts +0 -50
- package/dist/tui/components/PromptArea.js +0 -303
- package/dist/tui/components/RecruitWizard.d.ts +0 -17
- package/dist/tui/components/RecruitWizard.js +0 -221
- package/dist/tui/components/RestoreConfirmModal.d.ts +0 -18
- package/dist/tui/components/RestoreConfirmModal.js +0 -71
- package/dist/tui/components/ScheduleWizard.d.ts +0 -19
- package/dist/tui/components/ScheduleWizard.js +0 -259
- package/dist/tui/components/Splash.d.ts +0 -23
- package/dist/tui/components/Splash.js +0 -221
- package/dist/tui/components/StatusBar.d.ts +0 -48
- package/dist/tui/components/StatusBar.js +0 -128
- package/dist/tui/components/StatusOverlay.d.ts +0 -15
- package/dist/tui/components/StatusOverlay.js +0 -76
- package/dist/tui/components/TitleBar.d.ts +0 -10
- package/dist/tui/components/TitleBar.js +0 -21
- package/dist/tui/index.d.ts +0 -15
- package/dist/tui/index.js +0 -162
- package/dist/tui/ink-context.d.ts +0 -18
- package/dist/tui/ink-context.js +0 -59
- package/dist/tui/ink-loader.d.ts +0 -26
- package/dist/tui/ink-loader.js +0 -42
- package/dist/tui/removed-commands.d.ts +0 -9
- package/dist/tui/removed-commands.js +0 -22
- package/dist/tui/sse-handler.d.ts +0 -52
- package/dist/tui/sse-handler.js +0 -157
- package/dist/tui/store.d.ts +0 -598
- package/dist/tui/store.js +0 -753
- package/dist/tui/utils/format.d.ts +0 -56
- package/dist/tui/utils/format.js +0 -155
- package/dist/tui/utils/fullscreen.d.ts +0 -23
- package/dist/tui/utils/fullscreen.js +0 -71
- package/dist/tui/utils/history.d.ts +0 -10
- package/dist/tui/utils/history.js +0 -85
- package/dist/tui/utils/platform.d.ts +0 -45
- package/dist/tui/utils/platform.js +0 -258
- package/dist/tui/utils/theme.d.ts +0 -21
- 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 {};
|