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,10 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildStageTool = buildStageTool;
4
+ exports.buildStageAliasTools = buildStageAliasTools;
4
5
  const zod_1 = require("zod");
5
6
  const descriptor_1 = require("./descriptor");
7
+ const action_guard_1 = require("./action-guard");
8
+ const stages_1 = require("./stages");
9
+ const cancel_stage_1 = require("./cancel-stage");
6
10
  const validation_1 = require("../utils/validation");
7
- function buildStageTool(handle, getPlayerId) {
11
+ /**
12
+ * Internal create-only descriptor (the legacy bare-`stage` behaviour). Its
13
+ * `.handler` is reused verbatim by the canonical {@link buildStageTool} under
14
+ * `action="create"`. Not exported — the canonical tool is the public surface.
15
+ */
16
+ function buildStageCreateTool(handle, getPlayerId) {
8
17
  return {
9
18
  name: 'stage',
10
19
  description: 'Create a pipeline stage tracking N players. When all players report, a completion message is auto-injected. Conductor only.',
@@ -31,3 +40,78 @@ function buildStageTool(handle, getPlayerId) {
31
40
  },
32
41
  };
33
42
  }
43
+ /**
44
+ * Canonical `stage` tool (#793 merge) — **reused** name, so `action` defaults to
45
+ * `'create'` for backward-compat. Actions: `create | list | cancel` (all CRUD
46
+ * peers on the same StageEntry — see #793 brief §4). Conductor-only (gated in
47
+ * server-tools.ts).
48
+ *
49
+ * The legacy `stages` / `cancel_stage` names stay registered as forwarding
50
+ * aliases ({@link buildStageAliasTools}); there is no alias for `create` — the
51
+ * bare `stage` IS create.
52
+ */
53
+ function buildStageTool(handle, getPlayerId) {
54
+ const create = buildStageCreateTool(handle, getPlayerId);
55
+ const list = (0, stages_1.buildStagesTool)(handle);
56
+ const cancel = (0, cancel_stage_1.buildCancelStageTool)(handle);
57
+ return {
58
+ name: 'stage',
59
+ description: 'Track a fan-out/fan-in pipeline stage across N players (conductor only). ' +
60
+ 'action="create" (default) opens a stage (name + players[, failurePolicy]); ' +
61
+ 'action="list" shows all stages and per-player report status; ' +
62
+ 'action="cancel" cancels a named stage.',
63
+ params: {
64
+ action: zod_1.z.enum(['create', 'list', 'cancel']).optional().describe('Which stage operation to perform (defaults to "create" when omitted)'),
65
+ // create / cancel:
66
+ name: zod_1.z.string().max(validation_1.STAGE_NAME_MAX).optional().describe('create/cancel: the stage name'),
67
+ // create:
68
+ players: zod_1.z.array(zod_1.z.string().regex(validation_1.PLAYER_NAME_REGEX)).min(1).max(validation_1.STAGE_PLAYERS_MAX).optional().describe('create: player names to track in this stage'),
69
+ failurePolicy: zod_1.z.enum(['halt', 'continue']).optional().describe('create: "halt" (default) fails the stage on a blocker; "continue" waits for all players.'),
70
+ },
71
+ handler: async (args) => {
72
+ const action = args.action ?? 'create';
73
+ switch (action) {
74
+ case 'create': {
75
+ const m = (0, action_guard_1.firstMissing)(args, ['name', 'players']);
76
+ if (m)
77
+ return (0, descriptor_1.fail)(`stage action="create" requires "${m}".`);
78
+ return create.handler(args);
79
+ }
80
+ case 'list':
81
+ return list.handler(args);
82
+ case 'cancel': {
83
+ const m = (0, action_guard_1.firstMissing)(args, ['name']);
84
+ if (m)
85
+ return (0, descriptor_1.fail)(`stage action="cancel" requires "${m}".`);
86
+ return cancel.handler(args);
87
+ }
88
+ default:
89
+ return (0, descriptor_1.fail)(`Unknown stage action: ${String(action)}. Expected create | list | cancel.`);
90
+ }
91
+ },
92
+ };
93
+ }
94
+ /**
95
+ * Legacy forwarding aliases — `stages` → list, `cancel_stage` → cancel. Each
96
+ * keeps its exact original schema + handler; description gains a deprecation
97
+ * note. No alias for `create` — the bare `stage` IS create. Explicit object
98
+ * literals (see #793 brief §6 drift note).
99
+ */
100
+ function buildStageAliasTools(handle) {
101
+ const list = (0, stages_1.buildStagesTool)(handle);
102
+ const cancel = (0, cancel_stage_1.buildCancelStageTool)(handle);
103
+ return [
104
+ {
105
+ name: 'stages',
106
+ description: 'DEPRECATED — use `stage` with action="list". ' + list.description,
107
+ params: list.params,
108
+ handler: list.handler,
109
+ },
110
+ {
111
+ name: 'cancel_stage',
112
+ description: 'DEPRECATED — use `stage` with action="cancel". ' + cancel.description,
113
+ params: cancel.params,
114
+ handler: cancel.handler,
115
+ },
116
+ ];
117
+ }
@@ -0,0 +1,14 @@
1
+ import { Client, WorkflowHandle } from '@temporalio/client';
2
+ import { Config } from '../config';
3
+ import { type TempoToolDescriptor } from './descriptor';
4
+ /**
5
+ * Canonical `state` tool. Dispatches on `action`; per-action required fields are
6
+ * runtime-guarded.
7
+ */
8
+ export declare function buildStateTool(client: Client, config: Config, handle: WorkflowHandle, getPlayerId: () => string): TempoToolDescriptor;
9
+ /**
10
+ * Legacy forwarding aliases — `save_state` / `fetch_state` / `clear_state`.
11
+ * Each keeps its exact original schema + handler; description gains a
12
+ * deprecation note. Explicit object literals (see §6 drift note).
13
+ */
14
+ export declare function buildStateAliasTools(client: Client, config: Config, handle: WorkflowHandle, getPlayerId: () => string): TempoToolDescriptor[];
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildStateTool = buildStateTool;
4
+ exports.buildStateAliasTools = buildStateAliasTools;
5
+ /**
6
+ * `state` — canonical multi-action player saveable-state tool (#793 merge).
7
+ *
8
+ * Merges the three legacy tools (`save_state` / `fetch_state` / `clear_state`)
9
+ * into ONE canonical tool with a flat `{ action, ...optional fields }` shape
10
+ * (see docs/design/793-tool-family-merge-brief.md §2). The canonical name is
11
+ * **net-new**, so `action` is REQUIRED.
12
+ *
13
+ * Legacy tools stay registered as forwarding aliases
14
+ * ({@link buildStateAliasTools}); both paths reuse the legacy handler bodies, so
15
+ * behaviour (owner-only write, self-or-peer read, idempotent clear) is identical.
16
+ */
17
+ const zod_1 = require("zod");
18
+ const descriptor_1 = require("./descriptor");
19
+ const action_guard_1 = require("./action-guard");
20
+ const validation_1 = require("../utils/validation");
21
+ const save_state_1 = require("./save-state");
22
+ const fetch_state_1 = require("./fetch-state");
23
+ const clear_state_1 = require("./clear-state");
24
+ /**
25
+ * Canonical `state` tool. Dispatches on `action`; per-action required fields are
26
+ * runtime-guarded.
27
+ */
28
+ function buildStateTool(client, config, handle, getPlayerId) {
29
+ const save = (0, save_state_1.buildSaveStateTool)(handle, getPlayerId);
30
+ const fetch = (0, fetch_state_1.buildFetchStateTool)(client, config, handle, getPlayerId);
31
+ const clear = (0, clear_state_1.buildClearStateTool)(handle);
32
+ return {
33
+ name: 'state',
34
+ description: 'Curated per-player state slots that survive restart (you choose what context persists). ' +
35
+ 'action="save" writes your own slot (content[, key]); ' +
36
+ 'action="fetch" reads a slot for yourself or a peer (key/playerId, defaults to your own "main"); ' +
37
+ 'action="clear" empties one of your slots. save/clear are owner-only.',
38
+ params: {
39
+ action: zod_1.z.enum(['save', 'fetch', 'clear']).describe('Which state operation to perform'),
40
+ // save:
41
+ content: zod_1.z.string().min(1).max(validation_1.PLAYER_STATE_CONTENT_MAX).optional().describe('save: the state body to store (≤32 KiB)'),
42
+ // save / fetch / clear:
43
+ key: zod_1.z.string().regex(validation_1.PLAYER_STATE_KEY_REGEX).max(validation_1.PLAYER_STATE_KEY_MAX).optional().describe('Slot key (defaults to "main")'),
44
+ // fetch:
45
+ playerId: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional().describe('fetch: peer to read from (defaults to yourself)'),
46
+ },
47
+ handler: async (args) => {
48
+ const action = args.action;
49
+ switch (action) {
50
+ case 'save': {
51
+ const m = (0, action_guard_1.firstMissing)(args, ['content']);
52
+ if (m)
53
+ return (0, descriptor_1.fail)(`state action="save" requires "${m}".`);
54
+ return save.handler(args);
55
+ }
56
+ case 'fetch':
57
+ return fetch.handler(args);
58
+ case 'clear':
59
+ return clear.handler(args);
60
+ default:
61
+ return (0, descriptor_1.fail)(`Unknown state action: ${String(action)}. Expected save | fetch | clear.`);
62
+ }
63
+ },
64
+ };
65
+ }
66
+ /**
67
+ * Legacy forwarding aliases — `save_state` / `fetch_state` / `clear_state`.
68
+ * Each keeps its exact original schema + handler; description gains a
69
+ * deprecation note. Explicit object literals (see §6 drift note).
70
+ */
71
+ function buildStateAliasTools(client, config, handle, getPlayerId) {
72
+ const save = (0, save_state_1.buildSaveStateTool)(handle, getPlayerId);
73
+ const fetch = (0, fetch_state_1.buildFetchStateTool)(client, config, handle, getPlayerId);
74
+ const clear = (0, clear_state_1.buildClearStateTool)(handle);
75
+ return [
76
+ {
77
+ name: 'save_state',
78
+ description: 'DEPRECATED — use `state` with action="save". ' + save.description,
79
+ params: save.params,
80
+ handler: save.handler,
81
+ },
82
+ {
83
+ name: 'fetch_state',
84
+ description: 'DEPRECATED — use `state` with action="fetch". ' + fetch.description,
85
+ params: fetch.params,
86
+ handler: fetch.handler,
87
+ },
88
+ {
89
+ name: 'clear_state',
90
+ description: 'DEPRECATED — use `state` with action="clear". ' + clear.description,
91
+ params: clear.params,
92
+ handler: clear.handler,
93
+ },
94
+ ];
95
+ }
package/dist/types.d.ts CHANGED
@@ -57,6 +57,19 @@ export interface AdapterDescriptor {
57
57
  blocksOnLLMTurn: boolean;
58
58
  /** Heartbeat cadence in milliseconds. Interactive: 60_000; SDK: 30_000. */
59
59
  heartbeatMs: number;
60
+ /**
61
+ * #704 — True iff the adapter can park pre-attach on a blocking launch-time
62
+ * dialog (the interactive `claude-code` dev-channels acknowledgment, which
63
+ * requires a human click). When true, the booting attach-timeout watchdog is
64
+ * DISARMED for this adapter: a recruit waiting on a human is not a hang the
65
+ * watchdog should false-kill (an operator-away false-kill is worse than the
66
+ * hang — see docs/design/704-is-demo-companion-brief.md). Headless adapters
67
+ * (no dialog) omit this / set false → the watchdog arms. The property is the
68
+ * contract, not the adapter name: a future interactive adapter inherits the
69
+ * disarm by setting it. Interactive arming returns once #890 dissolves the
70
+ * dialog. Optional; absent ⇒ falsy ⇒ armed.
71
+ */
72
+ canBlockOnDialog?: boolean;
60
73
  }
61
74
  /**
62
75
  * Reason an attachment detached or was detached. Used for audit and UX messaging.
@@ -85,7 +98,7 @@ export interface AdapterDescriptor {
85
98
  * When not opted in, the base class fires the reason as terminal just like any
86
99
  * other non-recoverable lease loss.
87
100
  */
88
- export type DetachReason = 'user-stop' | 'restart' | 'heartbeat-timeout' | 'superseded' | 'agent-exited' | 'spawn-failed' | 'destroy' | 'force' | 'reconnect-exhausted' | 'continued-as-new';
101
+ export type DetachReason = 'user-stop' | 'restart' | 'heartbeat-timeout' | 'superseded' | 'agent-exited' | 'spawn-failed' | 'destroy' | 'force' | 'reconnect-exhausted' | 'continued-as-new' | 'boot-timeout';
89
102
  /**
90
103
  * Workflow-emitted directive to the attached adapter. Delivered via {@link AttachmentInfo}
91
104
  * polling (no reverse-RPC surface in Temporal).
@@ -178,6 +191,23 @@ export interface SessionMetadata {
178
191
  playerTypeDescription?: string;
179
192
  /** Player ID of who recruited this player. */
180
193
  recruitedBy?: string;
194
+ /**
195
+ * #704 — resolved at spawn time from the adapter descriptor's
196
+ * {@link AdapterDescriptor.canBlockOnDialog}. Threaded onto durable metadata
197
+ * (and carried across continueAsNew) so the session workflow can decide
198
+ * whether to ARM the booting attach-timeout watchdog WITHOUT importing the
199
+ * client-side adapter registry (workflows are sandboxed). True ⇒ interactive
200
+ * adapter that can park on a launch dialog ⇒ watchdog DISARMED. Absent/false
201
+ * ⇒ headless ⇒ ARMED.
202
+ */
203
+ canBlockOnDialog?: boolean;
204
+ /**
205
+ * #704 — optional override (ms) for the booting attach-timeout deadline,
206
+ * resolved from `AGENT_TEMPO_BOOTING_DEADLINE_MS` at spawn time. Workflows
207
+ * can't read process.env, so the override rides durable metadata. Absent ⇒
208
+ * the workflow's 180s default applies.
209
+ */
210
+ bootingDeadlineMs?: number;
181
211
  /** Worktree path if this session was spawned in an isolated worktree. */
182
212
  worktreePath?: string;
183
213
  /**
@@ -290,7 +320,7 @@ export interface HostProfile {
290
320
  */
291
321
  export interface InstanceInfo {
292
322
  pid: number;
293
- /** Parsed from worker identity. `'unknown'` for legacy-format identities (`<pid>@<hostname>`). */
323
+ /** Parsed from the `agent-tempo:<hostname>:<pid>:<version>` worker identity. */
294
324
  version: string;
295
325
  /** Raw `poller.identity` as Temporal returned it. */
296
326
  identity: string;
@@ -300,8 +330,6 @@ export interface InstanceInfo {
300
330
  hasActivityWorker: boolean;
301
331
  /** `true` when the per-host activity queue (`agent-tempo-<hostname>`) has a live poller. Controls `HostInfo.recruitReady`. */
302
332
  hasHostQueueWorker: boolean;
303
- /** Set only when identity was in the legacy `<pid>@<hostname>` form. */
304
- legacy?: true;
305
333
  }
306
334
  /**
307
335
  * Joined liveness + capability view returned by `src/utils/hosts.ts`.
@@ -346,6 +374,16 @@ export interface PlayerStateEntry {
346
374
  }
347
375
  export interface SessionInput {
348
376
  metadata: SessionMetadata;
377
+ /**
378
+ * #786 — 2.0 cutover protocol stamp. Set to `PROTOCOL_VERSION` (2) at every
379
+ * 2.0 start site so it's in history from run 1 and survives `continueAsNew`
380
+ * (carried by the `...input` spread at the CAN site). The workflow ALSO upserts
381
+ * the `AgentTempoProtocol` memo authoritatively from the constant — a 2.0
382
+ * bundle always stamps 2 regardless of input — so a 1.x run (which never sets
383
+ * this) is detectable by the boot guard. Optional only for type back-compat
384
+ * across the cutover; 2.0 always sets it.
385
+ */
386
+ protocol?: number;
349
387
  /** Restored from continue-as-new */
350
388
  part?: string;
351
389
  /** Restored from continue-as-new (undelivered only) */
@@ -728,9 +766,10 @@ export interface ResetOutboxEntry extends OutboxEntryBase {
728
766
  }
729
767
  export type OutboxEntry = CueOutboxEntry | RecruitOutboxEntry | ReportOutboxEntry | StopOutboxEntry | ReleaseOutboxEntry | SpawnOutboxEntry | DetachOutboxEntry | DestroyOutboxEntry | RestartOutboxEntry | ResetOutboxEntry;
730
768
  /**
731
- * Pending reset flag set on a session workflow by `deliverReset`, polled by the
732
- * Pi extension via `pendingResetQuery` and cleared via `ackResetSignal(resetId)`
733
- * after the extension performs the wipe. Single-slot, latest-wins.
769
+ * Pending reset flag set on a session workflow by `deliverReset`, read by the
770
+ * Pi extension off the combined `pendingIntake` query and cleared via
771
+ * `ackResetSignal(resetId)` after the extension performs the wipe. Single-slot,
772
+ * latest-wins.
734
773
  */
735
774
  export interface PendingReset {
736
775
  /** Correlation id (the originating outbox entry id). Ack clears only on match. */
@@ -865,6 +904,14 @@ export interface MaestroPlayerInfo {
865
904
  * surfaces that want to show "last active 12s ago" per player.
866
905
  */
867
906
  lastActivityAt?: string;
907
+ /**
908
+ * #886 slice 2 — `true` when this row came from a DEGRADED observation scan
909
+ * (the workflow was listed but its metadata extraction failed, so non-identity
910
+ * fields are best-effort blanks). Surfaces to the dashboard/board so the
911
+ * player renders as "uncertain" rather than vanishing — preventing roster
912
+ * flapping (contra #777). Absent / `false` on healthy rows.
913
+ */
914
+ degraded?: boolean;
868
915
  }
869
916
  /** A message relayed through the global Maestro for dashboard visibility. */
870
917
  export interface MaestroRelayMessage {
@@ -0,0 +1,95 @@
1
+ /**
2
+ * #786 — the 2.0 cutover BOOT GUARD (the keystone's centerpiece).
3
+ *
4
+ * A 2.0 Temporal worker can never deterministically replay a 1.x-recorded
5
+ * workflow history — the bundle's command stream has diverged, so a replay
6
+ * faults (or, worse, silently corrupts) deep inside a workflow task. The
7
+ * `upgrade-to-2` cutover (#785) exists to destroy every 1.x run before a 2.0
8
+ * daemon ever polls. This guard is the enforcement: a 2.0 daemon REFUSES to
9
+ * register workers if visibility still shows ANY Running agent-tempo workflow
10
+ * that lacks the protocol-2 stamp.
11
+ *
12
+ * **Stamp source of truth:** every 2.0 workflow upserts the
13
+ * `AgentTempoProtocol` memo = {@link PROTOCOL_VERSION} early + unconditionally
14
+ * on every run (incl. continueAsNew successors) — see `src/workflows/*` and
15
+ * `src/constants.ts`. The memo rides the visibility list result directly
16
+ * ({@link getWorkflowProtocol} reads it off the row), so the scan needs NO
17
+ * per-workflow query. A 1.x run never executes that upsert, so its memo is
18
+ * `undefined` → un-stamped → the guard refuses.
19
+ *
20
+ * **FAIL-CLOSED (the #845 partial-scan lesson):** the scan is bounded by the
21
+ * `protocolGuardBoot` visibility budget. A timeout or any scan error is NOT a
22
+ * best-effort partial — it means "couldn't prove the cutover is clean," and an
23
+ * unverified scan must NEVER be read as clean. Both refuse with
24
+ * {@link ProtocolGuardResult.reason} = `'scan-incomplete'`; the daemon retries
25
+ * on the next boot. The cost of a false-refuse (one retry) is trivial next to
26
+ * the cost of a false-proceed (deterministic history corruption).
27
+ *
28
+ * This module is daemon-side (it holds a Temporal client), distinct from the
29
+ * deliberately SDK-free `snapshot-v1.ts` cross-release contract.
30
+ */
31
+ import type { Client, WorkflowExecutionInfo } from '@temporalio/client';
32
+ /**
33
+ * The four agent-tempo workflow types. A Running workflow of any of these is
34
+ * an agent-tempo coordination workflow that a 2.0 worker would have to replay
35
+ * — so all four must be protocol-2 stamped before boot.
36
+ */
37
+ export declare const AGENT_TEMPO_WORKFLOW_TYPES: readonly ["agentSessionWorkflow", "agentMaestroWorkflow", "agentSchedulerWorkflow", "agentGlobalMaestroWorkflow"];
38
+ /** A Running workflow that failed the protocol-2 stamp check. */
39
+ export interface ProtocolOffender {
40
+ workflowId: string;
41
+ /** Workflow type name from the visibility row (best-effort). */
42
+ workflowType?: string;
43
+ /** The stamp read from the row's memo — `undefined` for an un-stamped 1.x run. */
44
+ protocol: number | undefined;
45
+ }
46
+ /** Outcome of {@link checkProtocolGuard}. */
47
+ export interface ProtocolGuardResult {
48
+ /** `true` → every Running agent-tempo workflow is protocol-2 stamped (or none exist). */
49
+ ok: boolean;
50
+ /**
51
+ * Why boot was refused. Only set when `ok` is `false`.
52
+ * - `'unstamped'` — at least one Running workflow lacks the protocol-2 stamp
53
+ * (a 1.x run survived the cutover). See {@link offenders}.
54
+ * - `'scan-incomplete'` — the visibility scan timed out or errored before
55
+ * completing, so cleanliness could not be PROVEN (fail-closed).
56
+ */
57
+ reason?: 'unstamped' | 'scan-incomplete';
58
+ /** Number of Running agent-tempo workflows examined before the result. */
59
+ scanned: number;
60
+ /** Un-stamped offenders (only when `reason === 'unstamped'`). */
61
+ offenders: ProtocolOffender[];
62
+ /** Operator-facing explanation (always set on refusal). */
63
+ message?: string;
64
+ }
65
+ /** Injectable seams for {@link checkProtocolGuard} — production callers omit. */
66
+ export interface ProtocolGuardDeps {
67
+ /**
68
+ * Returns the visibility iterable for the guard query. Defaults to
69
+ * `client.workflow.list({ query })`. Tests inject a crafted async iterable
70
+ * of `WorkflowExecutionInfo`-shaped rows (only `workflowId`, `type`, `memo`
71
+ * are read) without a live Temporal connection.
72
+ */
73
+ listWorkflows?: (query: string) => AsyncIterable<WorkflowExecutionInfo>;
74
+ /** Scan deadline. Default {@link VISIBILITY_DEADLINES_MS.protocolGuardBoot}. */
75
+ deadlineMs?: number;
76
+ /** Injectable clock — forwarded to {@link iterateWithDeadline}. Default `Date.now`. */
77
+ now?: () => number;
78
+ /** Log sink. Default no-op (the daemon caller logs the structured result). */
79
+ log?: (...args: unknown[]) => void;
80
+ }
81
+ /**
82
+ * Build the namespace-scoped visibility query for the guard. The client is
83
+ * already namespace-scoped, so no namespace clause is needed. Exposed for
84
+ * test introspection.
85
+ */
86
+ export declare function buildProtocolGuardQuery(): string;
87
+ /**
88
+ * Scan every Running agent-tempo workflow and verify each carries the
89
+ * protocol-2 stamp. See the module header for the fail-closed contract.
90
+ *
91
+ * Never throws — every failure (un-stamped run, scan timeout, scan error) is
92
+ * returned as a structured `ok: false` result so the daemon caller decides how
93
+ * to surface it (it prints + `process.exit(1)`).
94
+ */
95
+ export declare function checkProtocolGuard(client: Client, deps?: ProtocolGuardDeps): Promise<ProtocolGuardResult>;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AGENT_TEMPO_WORKFLOW_TYPES = void 0;
4
+ exports.buildProtocolGuardQuery = buildProtocolGuardQuery;
5
+ exports.checkProtocolGuard = checkProtocolGuard;
6
+ const constants_1 = require("../constants");
7
+ const search_attributes_1 = require("../utils/search-attributes");
8
+ const visibility_deadline_1 = require("../utils/visibility-deadline");
9
+ /**
10
+ * The four agent-tempo workflow types. A Running workflow of any of these is
11
+ * an agent-tempo coordination workflow that a 2.0 worker would have to replay
12
+ * — so all four must be protocol-2 stamped before boot.
13
+ */
14
+ exports.AGENT_TEMPO_WORKFLOW_TYPES = [
15
+ 'agentSessionWorkflow',
16
+ 'agentMaestroWorkflow',
17
+ 'agentSchedulerWorkflow',
18
+ 'agentGlobalMaestroWorkflow',
19
+ ];
20
+ /**
21
+ * Build the namespace-scoped visibility query for the guard. The client is
22
+ * already namespace-scoped, so no namespace clause is needed. Exposed for
23
+ * test introspection.
24
+ */
25
+ function buildProtocolGuardQuery() {
26
+ const types = exports.AGENT_TEMPO_WORKFLOW_TYPES.map((t) => `"${t}"`).join(', ');
27
+ return `WorkflowType IN (${types}) AND ExecutionStatus = "Running"`;
28
+ }
29
+ /**
30
+ * Scan every Running agent-tempo workflow and verify each carries the
31
+ * protocol-2 stamp. See the module header for the fail-closed contract.
32
+ *
33
+ * Never throws — every failure (un-stamped run, scan timeout, scan error) is
34
+ * returned as a structured `ok: false` result so the daemon caller decides how
35
+ * to surface it (it prints + `process.exit(1)`).
36
+ */
37
+ async function checkProtocolGuard(client, deps = {}) {
38
+ const log = deps.log ?? (() => { });
39
+ const deadlineMs = deps.deadlineMs ?? visibility_deadline_1.VISIBILITY_DEADLINES_MS.protocolGuardBoot;
40
+ const query = buildProtocolGuardQuery();
41
+ const listWorkflows = deps.listWorkflows ?? ((q) => client.workflow.list({ query: q }));
42
+ const offenders = [];
43
+ let scanned = 0;
44
+ try {
45
+ for await (const wf of (0, visibility_deadline_1.iterateWithDeadline)(listWorkflows(query), deadlineMs, 'protocolGuardBoot', deps.now)) {
46
+ scanned++;
47
+ const protocol = (0, search_attributes_1.getWorkflowProtocol)(wf);
48
+ if (protocol !== constants_1.PROTOCOL_VERSION) {
49
+ offenders.push({
50
+ workflowId: wf.workflowId,
51
+ // `WorkflowExecutionInfo.type` is the workflow type name string.
52
+ workflowType: wf.type,
53
+ protocol,
54
+ });
55
+ }
56
+ }
57
+ }
58
+ catch (err) {
59
+ // FAIL-CLOSED: a timeout OR any other scan error means we could not prove
60
+ // the namespace is clean. Refuse — never proceed on an unverified scan.
61
+ const kind = (0, visibility_deadline_1.isVisibilityTimeout)(err) ? 'timed out' : 'failed';
62
+ const detail = err instanceof Error ? err.message : String(err);
63
+ const message = `agent-tempo 2.0 boot guard could NOT verify a clean cutover — the ` +
64
+ `protocol scan ${kind} after ${scanned} workflow(s): ${detail}. ` +
65
+ `Refusing to boot (fail-closed): a 2.0 worker cannot safely replay a ` +
66
+ `1.x history. Retry once the Temporal namespace is responsive; if this ` +
67
+ `persists, run \`agent-tempo upgrade-to-2\` to complete the cutover.`;
68
+ log(`[boot-guard] scan-incomplete: ${message}`);
69
+ return { ok: false, reason: 'scan-incomplete', scanned, offenders, message };
70
+ }
71
+ if (offenders.length > 0) {
72
+ const sample = offenders
73
+ .slice(0, 5)
74
+ .map((o) => ` - ${o.workflowId} (protocol=${o.protocol ?? 'v1/unset'})`)
75
+ .join('\n');
76
+ const more = offenders.length > 5 ? `\n …and ${offenders.length - 5} more` : '';
77
+ const message = `agent-tempo 2.0 boot guard REFUSED to boot: ${offenders.length} of ` +
78
+ `${scanned} Running agent-tempo workflow(s) are not protocol-${constants_1.PROTOCOL_VERSION} ` +
79
+ `(2.0) stamped — these are pre-cutover 1.x runs that a 2.0 worker cannot ` +
80
+ `safely replay:\n${sample}${more}\n` +
81
+ `Complete the cutover with \`agent-tempo upgrade-to-2\` (it snapshots ` +
82
+ `continuity, then destroys the 1.x runs), then restart the daemon.`;
83
+ log(`[boot-guard] unstamped: refusing (${offenders.length}/${scanned} un-stamped)`);
84
+ return { ok: false, reason: 'unstamped', scanned, offenders, message };
85
+ }
86
+ log(`[boot-guard] clean: ${scanned} Running agent-tempo workflow(s), all protocol-${constants_1.PROTOCOL_VERSION} stamped`);
87
+ return { ok: true, scanned, offenders: [] };
88
+ }
@@ -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 {};