agent-tempo 1.7.0 → 2.0.0-beta.1

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 (147) 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 +0 -10
  9. package/dist/activities/outbox.js +4 -2
  10. package/dist/adapters/base.js +3 -0
  11. package/dist/cli/commands.d.ts +8 -0
  12. package/dist/cli/commands.js +79 -11
  13. package/dist/cli/daemon-command.js +19 -33
  14. package/dist/cli/help-text.js +7 -7
  15. package/dist/cli/home-command.d.ts +26 -0
  16. package/dist/cli/home-command.js +156 -0
  17. package/dist/cli/removed-verbs.d.ts +0 -4
  18. package/dist/cli/removed-verbs.js +23 -12
  19. package/dist/cli/sa-preflight.d.ts +0 -14
  20. package/dist/cli/sa-preflight.js +1 -17
  21. package/dist/cli/startup.d.ts +1 -10
  22. package/dist/cli/startup.js +0 -40
  23. package/dist/cli.js +34 -80
  24. package/dist/client/core.js +13 -15
  25. package/dist/client/index.d.ts +2 -1
  26. package/dist/client/interface.d.ts +5 -14
  27. package/dist/client/with-spawn.d.ts +9 -4
  28. package/dist/client/with-spawn.js +7 -10
  29. package/dist/config.d.ts +0 -13
  30. package/dist/constants.d.ts +22 -0
  31. package/dist/constants.js +23 -1
  32. package/dist/daemon.js +52 -0
  33. package/dist/http/auth.d.ts +3 -21
  34. package/dist/http/auth.js +10 -31
  35. package/dist/http/event-types.d.ts +5 -4
  36. package/dist/http/orphans.d.ts +3 -3
  37. package/dist/http/orphans.js +3 -3
  38. package/dist/http/server.d.ts +0 -5
  39. package/dist/http/server.js +69 -29
  40. package/dist/http/writes.d.ts +1 -1
  41. package/dist/http/writes.js +25 -2
  42. package/dist/palette/index.d.ts +6 -7
  43. package/dist/palette/index.js +6 -7
  44. package/dist/pi/mission-control/actions.d.ts +67 -1
  45. package/dist/pi/mission-control/actions.js +61 -0
  46. package/dist/pi/mission-control/extension.d.ts +77 -0
  47. package/dist/pi/mission-control/extension.js +171 -0
  48. package/dist/pi/workflow-client.d.ts +14 -16
  49. package/dist/pi/workflow-client.js +18 -44
  50. package/dist/types.d.ts +15 -6
  51. package/dist/upgrade/boot-guard.d.ts +95 -0
  52. package/dist/upgrade/boot-guard.js +88 -0
  53. package/dist/upgrade/from-upgrade.d.ts +95 -0
  54. package/dist/upgrade/from-upgrade.js +330 -0
  55. package/dist/upgrade/phase-engine.js +4 -1
  56. package/dist/utils/duration.d.ts +3 -6
  57. package/dist/utils/duration.js +3 -6
  58. package/dist/utils/format-hosts.d.ts +1 -1
  59. package/dist/utils/format-hosts.js +0 -2
  60. package/dist/utils/grpc-shutdown-guard.js +0 -4
  61. package/dist/utils/hosts.d.ts +4 -10
  62. package/dist/utils/hosts.js +12 -28
  63. package/dist/utils/search-attributes.d.ts +27 -18
  64. package/dist/utils/search-attributes.js +39 -18
  65. package/dist/utils/visibility-deadline.d.ts +16 -0
  66. package/dist/utils/visibility-deadline.js +16 -0
  67. package/dist/workflows/maestro-signals.d.ts +0 -13
  68. package/dist/workflows/maestro-signals.js +6 -14
  69. package/dist/workflows/maestro.js +27 -32
  70. package/dist/workflows/scheduler.js +7 -1
  71. package/dist/workflows/session.js +44 -110
  72. package/dist/workflows/signals.d.ts +9 -1
  73. package/dist/workflows/signals.js +8 -6
  74. package/package.json +1 -6
  75. package/workflow-bundle.js +239 -182
  76. package/dist/cli/legacy-migration.d.ts +0 -35
  77. package/dist/cli/legacy-migration.js +0 -341
  78. package/dist/client/ensure-conductor-spawned.d.ts +0 -35
  79. package/dist/client/ensure-conductor-spawned.js +0 -48
  80. package/dist/tui/App.d.ts +0 -85
  81. package/dist/tui/App.js +0 -1791
  82. package/dist/tui/bootstrap-types.d.ts +0 -46
  83. package/dist/tui/bootstrap-types.js +0 -7
  84. package/dist/tui/commands.d.ts +0 -71
  85. package/dist/tui/commands.js +0 -1375
  86. package/dist/tui/components/ChatView.d.ts +0 -35
  87. package/dist/tui/components/ChatView.js +0 -54
  88. package/dist/tui/components/CommandPalette.d.ts +0 -21
  89. package/dist/tui/components/CommandPalette.js +0 -67
  90. package/dist/tui/components/ConversationStream.d.ts +0 -114
  91. package/dist/tui/components/ConversationStream.js +0 -307
  92. package/dist/tui/components/CreateEnsembleWizard.d.ts +0 -19
  93. package/dist/tui/components/CreateEnsembleWizard.js +0 -223
  94. package/dist/tui/components/DestroyConfirmModal.d.ts +0 -17
  95. package/dist/tui/components/DestroyConfirmModal.js +0 -62
  96. package/dist/tui/components/ErrorView.d.ts +0 -31
  97. package/dist/tui/components/ErrorView.js +0 -129
  98. package/dist/tui/components/HomeView.d.ts +0 -54
  99. package/dist/tui/components/HomeView.js +0 -306
  100. package/dist/tui/components/LoadLineupModal.d.ts +0 -18
  101. package/dist/tui/components/LoadLineupModal.js +0 -79
  102. package/dist/tui/components/MainView.d.ts +0 -21
  103. package/dist/tui/components/MainView.js +0 -107
  104. package/dist/tui/components/NewEnsembleModal.d.ts +0 -9
  105. package/dist/tui/components/NewEnsembleModal.js +0 -73
  106. package/dist/tui/components/Picker.d.ts +0 -23
  107. package/dist/tui/components/Picker.js +0 -70
  108. package/dist/tui/components/PlayerDetailView.d.ts +0 -26
  109. package/dist/tui/components/PlayerDetailView.js +0 -118
  110. package/dist/tui/components/PromptArea.d.ts +0 -50
  111. package/dist/tui/components/PromptArea.js +0 -303
  112. package/dist/tui/components/RecruitWizard.d.ts +0 -17
  113. package/dist/tui/components/RecruitWizard.js +0 -221
  114. package/dist/tui/components/RestoreConfirmModal.d.ts +0 -18
  115. package/dist/tui/components/RestoreConfirmModal.js +0 -71
  116. package/dist/tui/components/ScheduleWizard.d.ts +0 -19
  117. package/dist/tui/components/ScheduleWizard.js +0 -259
  118. package/dist/tui/components/Splash.d.ts +0 -23
  119. package/dist/tui/components/Splash.js +0 -221
  120. package/dist/tui/components/StatusBar.d.ts +0 -48
  121. package/dist/tui/components/StatusBar.js +0 -128
  122. package/dist/tui/components/StatusOverlay.d.ts +0 -15
  123. package/dist/tui/components/StatusOverlay.js +0 -76
  124. package/dist/tui/components/TitleBar.d.ts +0 -10
  125. package/dist/tui/components/TitleBar.js +0 -21
  126. package/dist/tui/index.d.ts +0 -15
  127. package/dist/tui/index.js +0 -162
  128. package/dist/tui/ink-context.d.ts +0 -18
  129. package/dist/tui/ink-context.js +0 -59
  130. package/dist/tui/ink-loader.d.ts +0 -26
  131. package/dist/tui/ink-loader.js +0 -42
  132. package/dist/tui/removed-commands.d.ts +0 -9
  133. package/dist/tui/removed-commands.js +0 -22
  134. package/dist/tui/sse-handler.d.ts +0 -52
  135. package/dist/tui/sse-handler.js +0 -157
  136. package/dist/tui/store.d.ts +0 -598
  137. package/dist/tui/store.js +0 -753
  138. package/dist/tui/utils/format.d.ts +0 -56
  139. package/dist/tui/utils/format.js +0 -155
  140. package/dist/tui/utils/fullscreen.d.ts +0 -23
  141. package/dist/tui/utils/fullscreen.js +0 -71
  142. package/dist/tui/utils/history.d.ts +0 -10
  143. package/dist/tui/utils/history.js +0 -85
  144. package/dist/tui/utils/platform.d.ts +0 -45
  145. package/dist/tui/utils/platform.js +0 -258
  146. package/dist/tui/utils/theme.d.ts +0 -21
  147. package/dist/tui/utils/theme.js +0 -24
@@ -0,0 +1,95 @@
1
+ import { Client } from '@temporalio/client';
2
+ import type { ScheduleEntry, SessionInput } from '../types';
3
+ import { type UpgradeSnapshot } from './snapshot-v1';
4
+ /** Args shape for the recreated per-ensemble scheduler workflow. Mirrors
5
+ * `SchedulerInput` (declared in `../workflows/scheduler`, which we deliberately
6
+ * do NOT import here — it pulls `@temporalio/workflow` into the Node reader). */
7
+ interface SchedulerStartArgs {
8
+ ensemble: string;
9
+ entries: ScheduleEntry[];
10
+ }
11
+ /** Filename the consumed snapshot is archived to on success. */
12
+ export declare const CONSUMED_SNAPSHOT_FILENAME = "upgrade-snapshot-v1.consumed.json";
13
+ /** A single workflow to recreate — pure plan output (no Temporal I/O). */
14
+ export interface RecreateSpec {
15
+ workflowId: string;
16
+ workflowType: 'agentSessionWorkflow' | 'agentMaestroWorkflow' | 'agentSchedulerWorkflow';
17
+ ensemble: string;
18
+ /** Present for session workflows. */
19
+ playerId?: string;
20
+ /** `args[0]` for the workflow start call. */
21
+ args: [SessionInput] | [{
22
+ ensemble: string;
23
+ }] | [SchedulerStartArgs];
24
+ searchAttributes: Record<string, string[]>;
25
+ memo: Record<string, unknown>;
26
+ }
27
+ /** An undelivered inbox cue, surfaced for operator review (never redelivered). */
28
+ export interface UndeliveredCueReview {
29
+ ensemble: string;
30
+ playerId: string;
31
+ from: string;
32
+ text: string;
33
+ timestamp: string;
34
+ }
35
+ /** Result of {@link runFromUpgrade}. */
36
+ export interface FromUpgradeResult {
37
+ /** `true` when a snapshot existed AND every workflow recreated cleanly. */
38
+ ok: boolean;
39
+ /** `false` when no `upgrade-snapshot-v1.json` was present (nothing to do). */
40
+ snapshotFound: boolean;
41
+ ensemblesRecreated: number;
42
+ workflowsCreated: number;
43
+ /** Per-workflow failures (empty on full success). */
44
+ failures: {
45
+ workflowId: string;
46
+ error: string;
47
+ }[];
48
+ /** Undelivered cues for operator review (surfaced, NOT redelivered). */
49
+ undeliveredCues: UndeliveredCueReview[];
50
+ /** `true` when the snapshot was archived to `.consumed.json` (success only). */
51
+ archived: boolean;
52
+ archivePath?: string;
53
+ }
54
+ /** Plan inputs — the Temporal coordinates the recreated workflows need. */
55
+ export interface PlanRecreationOpts {
56
+ hostname: string;
57
+ taskQueue: string;
58
+ temporalAddress: string;
59
+ temporalNamespace: string;
60
+ }
61
+ /**
62
+ * Pure planner: turn a snapshot into the list of protocol-2 workflows to
63
+ * recreate. One `agentMaestroWorkflow` per ensemble (so the ensemble is
64
+ * coordinated + discoverable) plus one `agentSessionWorkflow` per roster player
65
+ * (conductor via {@link conductorWorkflowId}, others via
66
+ * {@link sessionWorkflowId}). Structural essentials only — see the module
67
+ * header. Exported so the recreation set is unit-testable without Temporal.
68
+ */
69
+ export declare function planRecreation(snapshot: UpgradeSnapshot, opts: PlanRecreationOpts): RecreateSpec[];
70
+ /** Collect every undelivered cue across the snapshot (review only). */
71
+ export declare function collectUndeliveredCues(snapshot: UpgradeSnapshot): UndeliveredCueReview[];
72
+ /**
73
+ * Archive the consumed snapshot: `upgrade-snapshot-v1.json` →
74
+ * `upgrade-snapshot-v1.consumed.json`. Overwrites a prior `.consumed.json`
75
+ * from an earlier run (rename is atomic on the same volume). Returns the
76
+ * archive path.
77
+ */
78
+ export declare function archiveSnapshot(home: string): string;
79
+ /**
80
+ * Recreate protocol-2 workflows from the on-disk upgrade snapshot.
81
+ *
82
+ * Returns a structured result; never throws on a per-workflow failure (those
83
+ * are collected in {@link FromUpgradeResult.failures}). A malformed snapshot
84
+ * DOES throw (via `readSnapshot` → `SnapshotValidationError`) — a corrupt
85
+ * continuity file is a loud failure, never a silent skip.
86
+ */
87
+ export declare function runFromUpgrade(client: Client, opts: {
88
+ home: string;
89
+ taskQueue: string;
90
+ temporalAddress: string;
91
+ temporalNamespace: string;
92
+ hostname?: string;
93
+ log?: (...args: unknown[]) => void;
94
+ }): Promise<FromUpgradeResult>;
95
+ export {};
@@ -0,0 +1,330 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONSUMED_SNAPSHOT_FILENAME = void 0;
4
+ exports.planRecreation = planRecreation;
5
+ exports.collectUndeliveredCues = collectUndeliveredCues;
6
+ exports.archiveSnapshot = archiveSnapshot;
7
+ exports.runFromUpgrade = runFromUpgrade;
8
+ /**
9
+ * #786 — `agent-tempo up --from-upgrade` (the 2.0 side of the cutover).
10
+ *
11
+ * After `upgrade-to-2` (#785) has snapshotted continuity and destroyed every
12
+ * 1.x run, a fresh 2.0 daemon boots cleanly (the boot guard sees zero
13
+ * un-stamped workflows). This module is what `up --from-upgrade` then runs: it
14
+ * reads `upgrade-snapshot-v1.json` and recreates a fresh protocol-2 workflow
15
+ * for every player in the snapshot ROSTER.
16
+ *
17
+ * Each recreated session carries the structural identity needed to exist as a
18
+ * protocol-2 workflow (`ensemble`, `playerId`, `workDir`, `agentType`,
19
+ * `isConductor`, plus the cheap structural memo: gitRoot/playerType) AND the
20
+ * continuity the snapshot captured. The workflow self-stamps the
21
+ * `AgentTempoProtocol` memo on start (and we seed it here too, so the row is
22
+ * stamped even before the first workflow task runs).
23
+ *
24
+ * **Continuity seeding (the #786 fast-follow — a non-droppable beta.1 gate):**
25
+ * - **#334 state slots** — seeded onto each session's `playerState` so the
26
+ * player's own curated context survives the cutover.
27
+ * - **`sessionId`** — seeded onto `metadata.sessionId` so the first re-attach
28
+ * can `--resume` into the original adapter conversation.
29
+ * - **Non-default recruit `model`** — seeded onto `metadata.model` so an
30
+ * ad-hoc claude-api / opencode / pi player is recreated on the SAME model.
31
+ * - **Durable schedules + scheduler** — for any ensemble with captured
32
+ * schedules, the per-ensemble `agentSchedulerWorkflow` is recreated and
33
+ * seeded with the schedule entries (the #786 MVP correctly skipped it since
34
+ * it is lazily created; we recreate it here only when there is intent to
35
+ * restore). Empty-schedule ensembles get NO scheduler — it would just
36
+ * self-terminate after its 30s empty grace.
37
+ *
38
+ * **Re-attach is a SEPARATE operator step — `up --from-upgrade` does NOT
39
+ * re-spawn adapters.** Every recreated session lands in the `booting` phase
40
+ * (no `currentAttachment`, stale-detection suppressed) — a durable skeleton
41
+ * with continuity seeded, awaiting an adapter. Auto-spawning N adapters across
42
+ * hosts during a migration is surprising and risky (terminals popping,
43
+ * cross-host spawns); the snapshot is about durable continuity, not live
44
+ * process resurrection. The operator brings each session live on their own
45
+ * schedule via `restart` / `restore` (which honour the seeded `sessionId` for
46
+ * `--resume`). This is flagged for the migration note in the docs.
47
+ *
48
+ * **Undelivered cues are surfaced for REVIEW only — NEVER redelivered**
49
+ * (replaying stale cues into fresh sessions is worse than operator triage,
50
+ * #785 §4). The caller prints them; this module just collects them.
51
+ *
52
+ * **Archive-on-success:** the snapshot is renamed to
53
+ * `upgrade-snapshot-v1.consumed.json` only when every workflow was recreated
54
+ * without error. On any partial failure the snapshot is left PRISTINE so the
55
+ * operator can fix the cause and re-run (recreation is idempotent via
56
+ * `USE_EXISTING`).
57
+ */
58
+ const fs_1 = require("fs");
59
+ const path_1 = require("path");
60
+ const os_1 = require("os");
61
+ const client_1 = require("@temporalio/client");
62
+ const config_1 = require("../config");
63
+ const search_attributes_1 = require("../utils/search-attributes");
64
+ const constants_1 = require("../constants");
65
+ const default_part_1 = require("../utils/default-part");
66
+ const snapshot_v1_1 = require("./snapshot-v1");
67
+ /** Filename the consumed snapshot is archived to on success. */
68
+ exports.CONSUMED_SNAPSHOT_FILENAME = 'upgrade-snapshot-v1.consumed.json';
69
+ /**
70
+ * Pure planner: turn a snapshot into the list of protocol-2 workflows to
71
+ * recreate. One `agentMaestroWorkflow` per ensemble (so the ensemble is
72
+ * coordinated + discoverable) plus one `agentSessionWorkflow` per roster player
73
+ * (conductor via {@link conductorWorkflowId}, others via
74
+ * {@link sessionWorkflowId}). Structural essentials only — see the module
75
+ * header. Exported so the recreation set is unit-testable without Temporal.
76
+ */
77
+ function planRecreation(snapshot, opts) {
78
+ const specs = [];
79
+ for (const ensemble of snapshot.ensembles) {
80
+ // Per-ensemble maestro hub — structural coordination workflow. Recreated so
81
+ // the ensemble is observable; idempotent via USE_EXISTING at start time.
82
+ specs.push({
83
+ workflowId: (0, config_1.maestroWorkflowId)(ensemble.name),
84
+ workflowType: 'agentMaestroWorkflow',
85
+ ensemble: ensemble.name,
86
+ args: [{ ensemble: ensemble.name }],
87
+ searchAttributes: { AgentTempoEnsemble: [ensemble.name] },
88
+ memo: { [search_attributes_1.MEMO_KEYS.protocol]: constants_1.PROTOCOL_VERSION },
89
+ });
90
+ for (const player of ensemble.players) {
91
+ const workflowId = player.isConductor
92
+ ? (0, config_1.conductorWorkflowId)(ensemble.name)
93
+ : (0, config_1.sessionWorkflowId)(ensemble.name, player.playerId);
94
+ const playerHostname = player.hostname || opts.hostname;
95
+ const autoSummary = (0, default_part_1.defaultPart)({
96
+ playerType: player.playerType,
97
+ isConductor: player.isConductor,
98
+ workDir: player.workDir,
99
+ adapterType: player.agentType,
100
+ });
101
+ const playerState = seedPlayerState(player, snapshot.createdAt);
102
+ const input = {
103
+ metadata: {
104
+ playerId: player.playerId,
105
+ ensemble: ensemble.name,
106
+ hostname: playerHostname,
107
+ workDir: player.workDir,
108
+ isConductor: player.isConductor,
109
+ agentType: player.agentType,
110
+ ...(player.gitRoot ? { gitRoot: player.gitRoot } : {}),
111
+ ...(player.playerType ? { playerType: player.playerType } : {}),
112
+ // Resume pointer — lets the first re-attach `--resume` into the
113
+ // original adapter conversation (#785 continuity).
114
+ ...(player.sessionId ? { sessionId: player.sessionId } : {}),
115
+ // Non-default recruit model — ad-hoc claude-api/opencode/pi players
116
+ // are recreated on the SAME model (#131 / #449 / #734).
117
+ ...(player.model ? { model: player.model } : {}),
118
+ },
119
+ // Carried so the stamp is in history from run 1; the workflow also
120
+ // upserts it authoritatively from the constant.
121
+ protocol: constants_1.PROTOCOL_VERSION,
122
+ autoSummary,
123
+ // #334 saveable-state slots — the player's own curated continuity.
124
+ // Omitted entirely for the no-state case (mirrors the workflow's
125
+ // populated-only CAN carry idiom).
126
+ ...(playerState ? { playerState } : {}),
127
+ // Recreated skeletons have no live adapter yet — suppress stale
128
+ // detection until one attaches (mirrors recruit / lineup pre-create).
129
+ disableStaleDetection: true,
130
+ temporalConfig: {
131
+ temporalAddress: opts.temporalAddress,
132
+ temporalNamespace: opts.temporalNamespace,
133
+ taskQueue: opts.taskQueue,
134
+ },
135
+ };
136
+ specs.push({
137
+ workflowId,
138
+ workflowType: 'agentSessionWorkflow',
139
+ ensemble: ensemble.name,
140
+ playerId: player.playerId,
141
+ args: [input],
142
+ searchAttributes: {
143
+ AgentTempoHostname: [playerHostname],
144
+ AgentTempoEnsemble: [ensemble.name],
145
+ AgentTempoPlayerId: [player.playerId],
146
+ },
147
+ memo: {
148
+ ...(player.gitRoot ? { [search_attributes_1.MEMO_KEYS.gitRoot]: player.gitRoot } : {}),
149
+ ...(player.playerType ? { [search_attributes_1.MEMO_KEYS.playerType]: player.playerType } : {}),
150
+ [search_attributes_1.MEMO_KEYS.isConductor]: player.isConductor,
151
+ [search_attributes_1.MEMO_KEYS.part]: autoSummary,
152
+ [search_attributes_1.MEMO_KEYS.workDir]: player.workDir,
153
+ [search_attributes_1.MEMO_KEYS.agentType]: player.agentType,
154
+ // Seed the protocol stamp on the START memo so the boot guard sees a
155
+ // stamped row even before the workflow's first task runs.
156
+ [search_attributes_1.MEMO_KEYS.protocol]: constants_1.PROTOCOL_VERSION,
157
+ },
158
+ });
159
+ }
160
+ // Per-ensemble scheduler — recreated ONLY when there is captured intent to
161
+ // restore. The scheduler is lazily created (it self-terminates after a 30s
162
+ // empty grace), so seeding an empty one is pointless; we recreate it solely
163
+ // to re-home the durable schedule entries (#785 §4). The scheduler workflow
164
+ // self-stamps the protocol memo on start; we also seed it on the START memo
165
+ // so the boot guard sees a stamped row immediately.
166
+ if (ensemble.schedules.length > 0) {
167
+ specs.push({
168
+ workflowId: (0, config_1.schedulerWorkflowId)(ensemble.name),
169
+ workflowType: 'agentSchedulerWorkflow',
170
+ ensemble: ensemble.name,
171
+ args: [{ ensemble: ensemble.name, entries: ensemble.schedules.map(toScheduleEntry) }],
172
+ searchAttributes: { AgentTempoEnsemble: [ensemble.name] },
173
+ memo: { [search_attributes_1.MEMO_KEYS.protocol]: constants_1.PROTOCOL_VERSION },
174
+ });
175
+ }
176
+ }
177
+ return specs;
178
+ }
179
+ /**
180
+ * Convert a player's captured #334 state slots (`key → content`) into the
181
+ * `playerState` map the session workflow seeds at boot. `save_state` is
182
+ * owner-only, so `savedBy` is the player itself; `savedAt` uses the snapshot's
183
+ * freeze time (the best available timestamp — the original per-slot write time
184
+ * is not captured in the v1 contract). Returns `undefined` for the common
185
+ * no-state case so the field is omitted from the seed input.
186
+ */
187
+ function seedPlayerState(player, frozenAt) {
188
+ const keys = Object.keys(player.stateSlots);
189
+ if (keys.length === 0)
190
+ return undefined;
191
+ const out = {};
192
+ for (const key of keys) {
193
+ out[key] = { content: player.stateSlots[key], savedAt: frozenAt, savedBy: player.playerId };
194
+ }
195
+ return out;
196
+ }
197
+ /**
198
+ * Convert a captured {@link SnapshotSchedule} (a frozen, decoupled copy) back
199
+ * into a live {@link ScheduleEntry} for the recreated scheduler. `firedCount`
200
+ * is not part of the durable-intent contract (it is a runtime accumulator), so
201
+ * it resets to 0 — the recreated schedule starts a fresh fire history.
202
+ */
203
+ function toScheduleEntry(s) {
204
+ return {
205
+ name: s.name,
206
+ message: s.message,
207
+ target: s.target,
208
+ createdBy: s.createdBy,
209
+ nextFireAt: s.nextFireAt,
210
+ type: s.type,
211
+ firedCount: 0,
212
+ ...(s.interval !== undefined ? { interval: s.interval } : {}),
213
+ ...(s.cronExpression ? { cronExpression: s.cronExpression } : {}),
214
+ ...(s.timezone ? { timezone: s.timezone } : {}),
215
+ ...(s.until ? { until: s.until } : {}),
216
+ ...(s.remainingCount !== undefined ? { remainingCount: s.remainingCount } : {}),
217
+ };
218
+ }
219
+ /** Collect every undelivered cue across the snapshot (review only). */
220
+ function collectUndeliveredCues(snapshot) {
221
+ const out = [];
222
+ for (const ensemble of snapshot.ensembles) {
223
+ for (const player of ensemble.players) {
224
+ for (const msg of player.undeliveredMessages) {
225
+ out.push({
226
+ ensemble: ensemble.name,
227
+ playerId: player.playerId,
228
+ from: msg.from,
229
+ text: msg.text,
230
+ timestamp: msg.timestamp,
231
+ });
232
+ }
233
+ }
234
+ }
235
+ return out;
236
+ }
237
+ /**
238
+ * Archive the consumed snapshot: `upgrade-snapshot-v1.json` →
239
+ * `upgrade-snapshot-v1.consumed.json`. Overwrites a prior `.consumed.json`
240
+ * from an earlier run (rename is atomic on the same volume). Returns the
241
+ * archive path.
242
+ */
243
+ function archiveSnapshot(home) {
244
+ const from = (0, snapshot_v1_1.snapshotPath)(home);
245
+ const to = (0, path_1.join)(home, exports.CONSUMED_SNAPSHOT_FILENAME);
246
+ (0, fs_1.renameSync)(from, to);
247
+ return to;
248
+ }
249
+ /**
250
+ * Recreate protocol-2 workflows from the on-disk upgrade snapshot.
251
+ *
252
+ * Returns a structured result; never throws on a per-workflow failure (those
253
+ * are collected in {@link FromUpgradeResult.failures}). A malformed snapshot
254
+ * DOES throw (via `readSnapshot` → `SnapshotValidationError`) — a corrupt
255
+ * continuity file is a loud failure, never a silent skip.
256
+ */
257
+ async function runFromUpgrade(client, opts) {
258
+ const log = opts.log ?? (() => { });
259
+ const snapshot = (0, snapshot_v1_1.readSnapshot)(opts.home); // throws (loud) on a malformed file
260
+ if (!snapshot) {
261
+ return {
262
+ ok: false,
263
+ snapshotFound: false,
264
+ ensemblesRecreated: 0,
265
+ workflowsCreated: 0,
266
+ failures: [],
267
+ undeliveredCues: [],
268
+ archived: false,
269
+ };
270
+ }
271
+ const specs = planRecreation(snapshot, {
272
+ hostname: opts.hostname ?? (0, os_1.hostname)(),
273
+ taskQueue: opts.taskQueue,
274
+ temporalAddress: opts.temporalAddress,
275
+ temporalNamespace: opts.temporalNamespace,
276
+ });
277
+ const failures = [];
278
+ let created = 0;
279
+ for (const spec of specs) {
280
+ try {
281
+ await client.workflow.start(spec.workflowType, {
282
+ workflowId: spec.workflowId,
283
+ taskQueue: opts.taskQueue,
284
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
+ args: spec.args,
286
+ // Idempotent re-run: an already-recreated workflow is left as-is.
287
+ workflowIdConflictPolicy: client_1.WorkflowIdConflictPolicy.USE_EXISTING,
288
+ searchAttributes: spec.searchAttributes,
289
+ memo: spec.memo,
290
+ });
291
+ created++;
292
+ log(`from-upgrade: recreated ${spec.workflowType} ${spec.workflowId}`);
293
+ }
294
+ catch (err) {
295
+ const error = err instanceof Error ? err.message : String(err);
296
+ failures.push({ workflowId: spec.workflowId, error });
297
+ log(`from-upgrade: FAILED to recreate ${spec.workflowId}: ${error}`);
298
+ }
299
+ }
300
+ const undeliveredCues = collectUndeliveredCues(snapshot);
301
+ // Archive ONLY on full success — leave the snapshot pristine on partial fail
302
+ // so the operator can fix the cause and re-run.
303
+ let archived = false;
304
+ let archivePath;
305
+ if (failures.length === 0) {
306
+ try {
307
+ archivePath = archiveSnapshot(opts.home);
308
+ archived = true;
309
+ log(`from-upgrade: archived snapshot → ${exports.CONSUMED_SNAPSHOT_FILENAME}`);
310
+ }
311
+ catch (err) {
312
+ // Recreation succeeded but the archive rename failed (e.g. a lock on
313
+ // Windows). Don't fail the run — surface it so the operator can move the
314
+ // file manually (a re-run is a harmless idempotent no-op).
315
+ log(`from-upgrade: WARNING — recreation succeeded but archiving ${snapshot_v1_1.SNAPSHOT_FILENAME} ` +
316
+ `failed: ${err instanceof Error ? err.message : String(err)}. ` +
317
+ `Move it aside manually so a future boot doesn't re-read it.`);
318
+ }
319
+ }
320
+ return {
321
+ ok: failures.length === 0,
322
+ snapshotFound: true,
323
+ ensemblesRecreated: snapshot.ensembles.length,
324
+ workflowsCreated: created,
325
+ failures,
326
+ undeliveredCues,
327
+ archived,
328
+ ...(archivePath ? { archivePath } : {}),
329
+ };
330
+ }
@@ -245,7 +245,10 @@ async function enumerateEnsembleNames(deps) {
245
245
  }
246
246
  async function fetchHostProfiles(deps, queryTimeoutMs) {
247
247
  try {
248
- return (await (0, query_timeout_1.queryHandleWithTimeout)(deps.client.workflow.getHandle(config_1.GLOBAL_MAESTRO_WORKFLOW_ID), maestro_signals_1.hostProfilesQuery, { timeoutMs: queryTimeoutMs }));
248
+ // 2.0 (#788): legacy `hostProfilesQuery` removed use the combined
249
+ // existence+profiles query and take the profiles map.
250
+ const result = (await (0, query_timeout_1.queryHandleWithTimeout)(deps.client.workflow.getHandle(config_1.GLOBAL_MAESTRO_WORKFLOW_ID), maestro_signals_1.hostProfilesWithExistenceQuery, { timeoutMs: queryTimeoutMs }));
251
+ return result.profiles;
249
252
  }
250
253
  catch {
251
254
  return {};
@@ -20,11 +20,8 @@ export declare function formatDurationMs(ms: number): string;
20
20
  *
21
21
  * `now` is injectable for deterministic tests; defaults to `Date.now()`.
22
22
  *
23
- * **Why a second time helper exists** (the TUI has `formatRelativeTime`
24
- * in `src/tui/utils/format.ts` doing roughly the same thing):
25
- * `src/tui/` carries Ink/React transitive imports the headless tools layer
26
- * mustn't take on. This helper has zero dependencies and is freely
27
- * importable by any layer. Consolidation across the two homes is tracked
28
- * as future deduplication work, not in scope here.
23
+ * This helper has zero dependencies and is freely importable by any layer.
24
+ * (It once co-existed with the TUI's `formatRelativeTime`; the Ink TUI was
25
+ * deleted in #789, so this is now the single relative-time helper.)
29
26
  */
30
27
  export declare function formatTimeAgo(iso: string, now?: number): string;
@@ -45,12 +45,9 @@ function formatDurationMs(ms) {
45
45
  *
46
46
  * `now` is injectable for deterministic tests; defaults to `Date.now()`.
47
47
  *
48
- * **Why a second time helper exists** (the TUI has `formatRelativeTime`
49
- * in `src/tui/utils/format.ts` doing roughly the same thing):
50
- * `src/tui/` carries Ink/React transitive imports the headless tools layer
51
- * mustn't take on. This helper has zero dependencies and is freely
52
- * importable by any layer. Consolidation across the two homes is tracked
53
- * as future deduplication work, not in scope here.
48
+ * This helper has zero dependencies and is freely importable by any layer.
49
+ * (It once co-existed with the TUI's `formatRelativeTime`; the Ink TUI was
50
+ * deleted in #789, so this is now the single relative-time helper.)
54
51
  */
55
52
  function formatTimeAgo(iso, now = Date.now()) {
56
53
  const t = Date.parse(iso);
@@ -4,7 +4,7 @@
4
4
  * Single source of truth consumed by:
5
5
  * - `src/tools/hosts.ts` (MCP tool) — formatted text in `ok(...)` payload
6
6
  * - `src/cli/commands.ts` (CLI `hosts` command) — stdout table
7
- * - `src/tui/commands.ts` (TUI `/hosts` slash) — overlay content
7
+ * - `src/pi/mission-control/` (command-center `/hosts` board command) — board text
8
8
  *
9
9
  * Pure: no Temporal, no I/O, no ANSI colors (the CLI can wrap lines in
10
10
  * color downstream if desired; the formatter emits plain strings so
@@ -40,8 +40,6 @@ function formatSingleHost(host) {
40
40
  bits.push(`workers: ${workerBits.length > 0 ? workerBits.join('+') : 'none'}`);
41
41
  if (inst.lastAccessTime)
42
42
  bits.push(`last seen ${inst.lastAccessTime}`);
43
- if (inst.legacy)
44
- bits.push('legacy-identity');
45
43
  lines.push(` - ${bits.join(' · ')}`);
46
44
  }
47
45
  // Profile (capability).
@@ -54,10 +54,6 @@ const handler = (err) => {
54
54
  if (isBenignChannelShutdown(err)) {
55
55
  // A Temporal gRPC retry timer fired after we closed the connection. The
56
56
  // result we cared about was already captured (or degraded). Drop it.
57
- if (process.env.CLAUDE_TEMPO_DEBUG) {
58
- // eslint-disable-next-line no-console
59
- console.error('[agent-tempo] ignored post-shutdown gRPC channel error (benign Temporal retry-after-close race)');
60
- }
61
57
  return;
62
58
  }
63
59
  // Not ours — restore default crash behavior. Throwing from inside an
@@ -54,26 +54,20 @@ export interface ParsedIdentity {
54
54
  hostname: string;
55
55
  pid: number;
56
56
  version: string;
57
- /** `true` when the identity was in the legacy `<pid>@<hostname>` SDK-default shape. */
58
- legacy: boolean;
59
57
  }
60
58
  /**
61
59
  * Parse a Temporal poller identity back into the daemon that emitted it.
62
60
  *
63
61
  * Returns `null` for opaque / third-party identities (e.g. Temporal's own
64
- * system pollers or an unrelated worker sharing the namespace). Callers
65
- * skip those silently.
62
+ * system pollers, an unrelated worker sharing the namespace, or an SDK
63
+ * worker that left the default `<pid>@<hostname>` identity). Callers skip
64
+ * those silently.
66
65
  *
67
- * The v1.0 format (`agent-tempo:<hostname>:<pid>:<version>`) is
66
+ * The agent-tempo format (`agent-tempo:<hostname>:<pid>:<version>`) is
68
67
  * guaranteed to have exactly 4 colon-delimited segments because every
69
68
  * component has its own validation: hostname passes `PLAYER_NAME_REGEX`
70
69
  * (no colons possible), pid is numeric, and version is a semver-ish
71
70
  * string (no colons).
72
- *
73
- * Pre-v1.0 daemons emitted a `claude-tempo:` prefix. Under the v1.0 hard
74
- * break those daemons can't reach v1.x task queues anyway, but if one does
75
- * surface in a host listing we tag it `legacy: true` so operators can
76
- * distinguish a still-running old daemon from a fresh v1.x worker.
77
71
  */
78
72
  export declare function parseIdentity(identity: string): ParsedIdentity | null;
79
73
  interface RawPoller {
@@ -43,41 +43,26 @@ function __resetHostsCacheForTests() {
43
43
  * Parse a Temporal poller identity back into the daemon that emitted it.
44
44
  *
45
45
  * Returns `null` for opaque / third-party identities (e.g. Temporal's own
46
- * system pollers or an unrelated worker sharing the namespace). Callers
47
- * skip those silently.
46
+ * system pollers, an unrelated worker sharing the namespace, or an SDK
47
+ * worker that left the default `<pid>@<hostname>` identity). Callers skip
48
+ * those silently.
48
49
  *
49
- * The v1.0 format (`agent-tempo:<hostname>:<pid>:<version>`) is
50
+ * The agent-tempo format (`agent-tempo:<hostname>:<pid>:<version>`) is
50
51
  * guaranteed to have exactly 4 colon-delimited segments because every
51
52
  * component has its own validation: hostname passes `PLAYER_NAME_REGEX`
52
53
  * (no colons possible), pid is numeric, and version is a semver-ish
53
54
  * string (no colons).
54
- *
55
- * Pre-v1.0 daemons emitted a `claude-tempo:` prefix. Under the v1.0 hard
56
- * break those daemons can't reach v1.x task queues anyway, but if one does
57
- * surface in a host listing we tag it `legacy: true` so operators can
58
- * distinguish a still-running old daemon from a fresh v1.x worker.
59
55
  */
60
56
  function parseIdentity(identity) {
61
- if (identity.startsWith('agent-tempo:') || identity.startsWith('claude-tempo:')) {
62
- const legacy = identity.startsWith('claude-tempo:');
63
- const parts = identity.split(':');
64
- if (parts.length === 4) {
65
- const [, hostname, pidStr, version] = parts;
66
- const pid = Number(pidStr);
67
- if (hostname.length > 0 && Number.isFinite(pid) && pid > 0 && version.length > 0) {
68
- return { hostname, pid, version, legacy };
69
- }
70
- }
57
+ if (!identity.startsWith('agent-tempo:'))
71
58
  return null;
72
- }
73
- // Legacy SDK default format: `<pid>@<hostname>`
74
- const legacyMatch = identity.match(/^(\d+)@(.+)$/);
75
- if (legacyMatch) {
76
- const pid = Number(legacyMatch[1]);
77
- const hostname = legacyMatch[2];
78
- if (Number.isFinite(pid) && pid > 0 && hostname.length > 0) {
79
- return { hostname, pid, version: 'unknown', legacy: true };
80
- }
59
+ const parts = identity.split(':');
60
+ if (parts.length !== 4)
61
+ return null;
62
+ const [, hostname, pidStr, version] = parts;
63
+ const pid = Number(pidStr);
64
+ if (hostname.length > 0 && Number.isFinite(pid) && pid > 0 && version.length > 0) {
65
+ return { hostname, pid, version };
81
66
  }
82
67
  return null;
83
68
  }
@@ -192,7 +177,6 @@ async function listHosts(client, opts = {}) {
192
177
  hasWorkflowWorker: false,
193
178
  hasActivityWorker: false,
194
179
  hasHostQueueWorker: false,
195
- ...(ident.legacy ? { legacy: true } : {}),
196
180
  };
197
181
  byPid.set(pid, inst);
198
182
  }