agent-tempo 2.0.0-beta.1 → 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 (40) hide show
  1. package/dashboard/package.json +1 -1
  2. package/dist/activities/maestro.js +3 -0
  3. package/dist/activities/outbox.js +12 -0
  4. package/dist/activities/resolve.d.ts +18 -7
  5. package/dist/activities/resolve.js +58 -46
  6. package/dist/adapters/claude-code/adapter.js +7 -0
  7. package/dist/cli/command-center-command.js +15 -1
  8. package/dist/cli/help-text.js +2 -0
  9. package/dist/config.d.ts +14 -0
  10. package/dist/config.js +14 -0
  11. package/dist/constants.d.ts +28 -0
  12. package/dist/constants.js +38 -1
  13. package/dist/daemon.js +29 -0
  14. package/dist/http/event-types.d.ts +33 -0
  15. package/dist/http/server.js +10 -0
  16. package/dist/http/snapshot.js +3 -0
  17. package/dist/observability/nondeterminism-alarm.d.ts +113 -0
  18. package/dist/observability/nondeterminism-alarm.js +162 -0
  19. package/dist/server-tools.js +30 -29
  20. package/dist/server.js +42 -0
  21. package/dist/tools/action-guard.d.ts +23 -0
  22. package/dist/tools/action-guard.js +33 -0
  23. package/dist/tools/coat-check.d.ts +20 -0
  24. package/dist/tools/coat-check.js +129 -0
  25. package/dist/tools/gate.d.ts +13 -0
  26. package/dist/tools/gate.js +88 -0
  27. package/dist/tools/schedule.d.ts +18 -0
  28. package/dist/tools/schedule.js +93 -1
  29. package/dist/tools/stage.d.ts +17 -0
  30. package/dist/tools/stage.js +85 -1
  31. package/dist/tools/state.d.ts +14 -0
  32. package/dist/tools/state.js +95 -0
  33. package/dist/types.d.ts +39 -1
  34. package/dist/utils/orphan-guard.d.ts +37 -0
  35. package/dist/utils/orphan-guard.js +26 -0
  36. package/dist/utils/search-attributes.d.ts +1 -0
  37. package/dist/utils/search-attributes.js +9 -0
  38. package/dist/workflows/session.js +193 -43
  39. package/package.json +1 -1
  40. package/workflow-bundle.js +241 -45
@@ -0,0 +1,13 @@
1
+ import { WorkflowHandle } from '@temporalio/client';
2
+ import { type TempoToolDescriptor } from './descriptor';
3
+ /**
4
+ * Canonical `gate` tool. Dispatches on `action` (define | list); `evaluate_gate`
5
+ * is intentionally excluded (separate tool).
6
+ */
7
+ export declare function buildGateTool(handle: WorkflowHandle, getPlayerId: () => string): TempoToolDescriptor;
8
+ /**
9
+ * Legacy forwarding aliases — `quality_gate` → define, `gates` → list. Each
10
+ * keeps its exact original schema + handler; description gains a deprecation
11
+ * note. Explicit object literals (see §6 drift note).
12
+ */
13
+ export declare function buildGateAliasTools(handle: WorkflowHandle, getPlayerId: () => string): TempoToolDescriptor[];
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildGateTool = buildGateTool;
4
+ exports.buildGateAliasTools = buildGateAliasTools;
5
+ /**
6
+ * `gate` — canonical multi-action quality-gate tool (#793 merge, PARTIAL).
7
+ *
8
+ * Merges the two gate-DEFINITION tools (`quality_gate` → define, `gates` → list)
9
+ * into ONE canonical tool. The canonical name is **net-new**, so `action` is
10
+ * REQUIRED.
11
+ *
12
+ * PARTIAL merge: `evaluate_gate` STAYS A SEPARATE TOOL and is NOT folded in.
13
+ * Rationale (brief §3⑤): evaluate is a distinct runtime *operation* — record
14
+ * pass/fail + notes against criteria — not CRUD on the gate *definition*. The
15
+ * define/list-the-gate vs act-on-the-gate line is the partial boundary.
16
+ *
17
+ * Legacy tools stay registered as forwarding aliases ({@link buildGateAliasTools});
18
+ * both paths reuse the legacy handler bodies. Conductor-only (gated in
19
+ * server-tools.ts alongside the other gate/stage tools).
20
+ */
21
+ const zod_1 = require("zod");
22
+ const descriptor_1 = require("./descriptor");
23
+ const action_guard_1 = require("./action-guard");
24
+ const validation_1 = require("../utils/validation");
25
+ const quality_gate_1 = require("./quality-gate");
26
+ const gates_1 = require("./gates");
27
+ /**
28
+ * Canonical `gate` tool. Dispatches on `action` (define | list); `evaluate_gate`
29
+ * is intentionally excluded (separate tool).
30
+ */
31
+ function buildGateTool(handle, getPlayerId) {
32
+ const define = (0, quality_gate_1.buildQualityGateTool)(handle, getPlayerId);
33
+ const list = (0, gates_1.buildGatesTool)(handle);
34
+ return {
35
+ name: 'gate',
36
+ description: 'Define and inspect quality gates for a task (conductor only). ' +
37
+ 'action="define" sets/replaces a gate (task + criteria); ' +
38
+ 'action="list" shows gates and per-criterion status (optional task/status filters). ' +
39
+ 'To record pass/fail on criteria, use the separate `evaluate_gate` tool.',
40
+ params: {
41
+ action: zod_1.z.enum(['define', 'list']).describe('Which gate operation to perform'),
42
+ // define / list (filter):
43
+ task: zod_1.z.string().max(validation_1.GATE_TASK_MAX).optional().describe('define: the task this gate guards (required). list: optional task filter.'),
44
+ // define:
45
+ criteria: zod_1.z.array(zod_1.z.string().max(validation_1.GATE_CRITERION_TEXT_MAX)).min(1).max(validation_1.GATE_CRITERIA_MAX).optional().describe('define: the list of criteria that must pass (required)'),
46
+ // list:
47
+ status: zod_1.z.enum(['open', 'passed', 'failed']).optional().describe('list: optional status filter'),
48
+ },
49
+ handler: async (args) => {
50
+ const action = args.action;
51
+ switch (action) {
52
+ case 'define': {
53
+ const m = (0, action_guard_1.firstMissing)(args, ['task', 'criteria']);
54
+ if (m)
55
+ return (0, descriptor_1.fail)(`gate action="define" requires "${m}".`);
56
+ return define.handler(args);
57
+ }
58
+ case 'list':
59
+ return list.handler(args);
60
+ default:
61
+ return (0, descriptor_1.fail)(`Unknown gate action: ${String(action)}. Expected define | list.`);
62
+ }
63
+ },
64
+ };
65
+ }
66
+ /**
67
+ * Legacy forwarding aliases — `quality_gate` → define, `gates` → list. Each
68
+ * keeps its exact original schema + handler; description gains a deprecation
69
+ * note. Explicit object literals (see §6 drift note).
70
+ */
71
+ function buildGateAliasTools(handle, getPlayerId) {
72
+ const define = (0, quality_gate_1.buildQualityGateTool)(handle, getPlayerId);
73
+ const list = (0, gates_1.buildGatesTool)(handle);
74
+ return [
75
+ {
76
+ name: 'quality_gate',
77
+ description: 'DEPRECATED — use `gate` with action="define". ' + define.description,
78
+ params: define.params,
79
+ handler: define.handler,
80
+ },
81
+ {
82
+ name: 'gates',
83
+ description: 'DEPRECATED — use `gate` with action="list". ' + list.description,
84
+ params: list.params,
85
+ handler: list.handler,
86
+ },
87
+ ];
88
+ }
@@ -1,4 +1,22 @@
1
1
  import { Client } from '@temporalio/client';
2
2
  import { Config } from '../config';
3
3
  import { type TempoToolDescriptor } from './descriptor';
4
+ /**
5
+ * Canonical `schedule` tool (#793 merge) — **reused** name, so `action` defaults
6
+ * to `'create'` for backward-compat (existing callers omit `action` → the legacy
7
+ * bare-`schedule` create behaviour). Actions: `create | cancel | list`.
8
+ *
9
+ * The rich one-of timing validation lives in the create handler (reused
10
+ * verbatim); `cancel` reuses `unschedule`, `list` reuses `schedules`. The legacy
11
+ * `unschedule` / `schedules` names stay registered as forwarding aliases
12
+ * ({@link buildScheduleAliasTools}); there is no alias for `create` — the bare
13
+ * `schedule` IS create.
14
+ */
4
15
  export declare function buildScheduleTool(client: Client, config: Config, getPlayerId: () => string): TempoToolDescriptor;
16
+ /**
17
+ * Legacy forwarding aliases — `unschedule` → cancel, `schedules` → list. Each
18
+ * keeps its exact original schema + handler; description gains a deprecation
19
+ * note. No alias for `create` — the bare `schedule` IS create. Explicit object
20
+ * literals (see #793 brief §6 drift note).
21
+ */
22
+ export declare function buildScheduleAliasTools(client: Client, config: Config): TempoToolDescriptor[];
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildScheduleTool = buildScheduleTool;
4
+ exports.buildScheduleAliasTools = buildScheduleAliasTools;
4
5
  const zod_1 = require("zod");
5
6
  const croner_1 = require("croner");
6
7
  const client_1 = require("@temporalio/client");
@@ -8,9 +9,18 @@ const config_1 = require("../config");
8
9
  const duration_1 = require("../utils/duration");
9
10
  const resolve_1 = require("./resolve");
10
11
  const descriptor_1 = require("./descriptor");
12
+ const action_guard_1 = require("./action-guard");
13
+ const unschedule_1 = require("./unschedule");
14
+ const schedules_1 = require("./schedules");
11
15
  const validation_1 = require("../utils/validation");
12
16
  const log = (...args) => console.error('[agent-tempo:schedule]', ...args);
13
- function buildScheduleTool(client, config, getPlayerId) {
17
+ /**
18
+ * Internal create-only descriptor (the legacy bare-`schedule` behaviour). Its
19
+ * `.handler` is reused verbatim by the canonical {@link buildScheduleTool}
20
+ * dispatch under `action="create"`. Not exported — the canonical tool is the
21
+ * public surface.
22
+ */
23
+ function buildScheduleCreateTool(client, config, getPlayerId) {
14
24
  return {
15
25
  name: 'schedule',
16
26
  description: 'Schedule a message to be sent to a player at a specific time, after a delay, on a recurring interval, or via cron expression.',
@@ -155,3 +165,85 @@ function buildScheduleTool(client, config, getPlayerId) {
155
165
  },
156
166
  };
157
167
  }
168
+ /**
169
+ * Canonical `schedule` tool (#793 merge) — **reused** name, so `action` defaults
170
+ * to `'create'` for backward-compat (existing callers omit `action` → the legacy
171
+ * bare-`schedule` create behaviour). Actions: `create | cancel | list`.
172
+ *
173
+ * The rich one-of timing validation lives in the create handler (reused
174
+ * verbatim); `cancel` reuses `unschedule`, `list` reuses `schedules`. The legacy
175
+ * `unschedule` / `schedules` names stay registered as forwarding aliases
176
+ * ({@link buildScheduleAliasTools}); there is no alias for `create` — the bare
177
+ * `schedule` IS create.
178
+ */
179
+ function buildScheduleTool(client, config, getPlayerId) {
180
+ const create = buildScheduleCreateTool(client, config, getPlayerId);
181
+ const cancel = (0, unschedule_1.buildUnscheduleTool)(client, config);
182
+ const list = (0, schedules_1.buildSchedulesTool)(client, config);
183
+ return {
184
+ name: 'schedule',
185
+ description: 'Schedule messages to players (one-shot, delay, recurring, or cron). ' +
186
+ 'action="create" (default) schedules a message (name+message+target + one timing of at/delay/every/cron); ' +
187
+ 'action="cancel" removes a named schedule; ' +
188
+ 'action="list" shows all active schedules.',
189
+ params: {
190
+ action: zod_1.z.enum(['create', 'cancel', 'list']).optional().describe('Which schedule operation to perform (defaults to "create" when omitted)'),
191
+ // create:
192
+ name: zod_1.z.string().max(validation_1.SCHEDULE_NAME_MAX).optional().describe('create/cancel: unique name for this schedule'),
193
+ message: zod_1.z.string().max(validation_1.SCHEDULE_MESSAGE_MAX).optional().describe('create: the message to deliver'),
194
+ target: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional().describe('create: player to deliver to ("self" = this session)'),
195
+ at: zod_1.z.string().optional().describe('create: ISO datetime for one-shot delivery'),
196
+ delay: zod_1.z.string().optional().describe('create: duration until first delivery (e.g. "10m", "2h", "1d")'),
197
+ every: zod_1.z.string().optional().describe('create: recurring interval (e.g. "5m", "1h")'),
198
+ cron: zod_1.z.string().max(validation_1.CRON_EXPRESSION_MAX).optional().describe('create: cron expression. Mutually exclusive with at/delay/every.'),
199
+ timezone: zod_1.z.string().optional().describe('create: IANA timezone for cron evaluation (default UTC). Only with cron.'),
200
+ until: zod_1.z.string().optional().describe('create: ISO datetime — stop recurring after this time'),
201
+ count: zod_1.z.number().optional().describe('create: max number of deliveries for recurring schedules'),
202
+ },
203
+ handler: async (args) => {
204
+ const action = args.action ?? 'create';
205
+ switch (action) {
206
+ case 'create': {
207
+ const m = (0, action_guard_1.firstMissing)(args, ['name', 'message', 'target']);
208
+ if (m)
209
+ return (0, descriptor_1.fail)(`schedule action="create" requires "${m}".`);
210
+ return create.handler(args);
211
+ }
212
+ case 'cancel': {
213
+ const m = (0, action_guard_1.firstMissing)(args, ['name']);
214
+ if (m)
215
+ return (0, descriptor_1.fail)(`schedule action="cancel" requires "${m}".`);
216
+ return cancel.handler(args);
217
+ }
218
+ case 'list':
219
+ return list.handler(args);
220
+ default:
221
+ return (0, descriptor_1.fail)(`Unknown schedule action: ${String(action)}. Expected create | cancel | list.`);
222
+ }
223
+ },
224
+ };
225
+ }
226
+ /**
227
+ * Legacy forwarding aliases — `unschedule` → cancel, `schedules` → list. Each
228
+ * keeps its exact original schema + handler; description gains a deprecation
229
+ * note. No alias for `create` — the bare `schedule` IS create. Explicit object
230
+ * literals (see #793 brief §6 drift note).
231
+ */
232
+ function buildScheduleAliasTools(client, config) {
233
+ const cancel = (0, unschedule_1.buildUnscheduleTool)(client, config);
234
+ const list = (0, schedules_1.buildSchedulesTool)(client, config);
235
+ return [
236
+ {
237
+ name: 'unschedule',
238
+ description: 'DEPRECATED — use `schedule` with action="cancel". ' + cancel.description,
239
+ params: cancel.params,
240
+ handler: cancel.handler,
241
+ },
242
+ {
243
+ name: 'schedules',
244
+ description: 'DEPRECATED — use `schedule` with action="list". ' + list.description,
245
+ params: list.params,
246
+ handler: list.handler,
247
+ },
248
+ ];
249
+ }
@@ -1,3 +1,20 @@
1
1
  import { WorkflowHandle } from '@temporalio/client';
2
2
  import { type TempoToolDescriptor } from './descriptor';
3
+ /**
4
+ * Canonical `stage` tool (#793 merge) — **reused** name, so `action` defaults to
5
+ * `'create'` for backward-compat. Actions: `create | list | cancel` (all CRUD
6
+ * peers on the same StageEntry — see #793 brief §4). Conductor-only (gated in
7
+ * server-tools.ts).
8
+ *
9
+ * The legacy `stages` / `cancel_stage` names stay registered as forwarding
10
+ * aliases ({@link buildStageAliasTools}); there is no alias for `create` — the
11
+ * bare `stage` IS create.
12
+ */
3
13
  export declare function buildStageTool(handle: WorkflowHandle, getPlayerId: () => string): TempoToolDescriptor;
14
+ /**
15
+ * Legacy forwarding aliases — `stages` → list, `cancel_stage` → cancel. Each
16
+ * keeps its exact original schema + handler; description gains a deprecation
17
+ * note. No alias for `create` — the bare `stage` IS create. Explicit object
18
+ * literals (see #793 brief §6 drift note).
19
+ */
20
+ export declare function buildStageAliasTools(handle: WorkflowHandle): TempoToolDescriptor[];
@@ -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
  /**
@@ -874,6 +904,14 @@ export interface MaestroPlayerInfo {
874
904
  * surfaces that want to show "last active 12s ago" per player.
875
905
  */
876
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;
877
915
  }
878
916
  /** A message relayed through the global Maestro for dashboard visibility. */
879
917
  export interface MaestroRelayMessage {
@@ -0,0 +1,37 @@
1
+ /**
2
+ * #704 Item 1b — late-orphan self-tombstone discriminator (pure).
3
+ *
4
+ * A recruited process can launch LONG after its recruit was cancelled (a slow /
5
+ * wedged cold start). When it finally boots and reaches the workflow-start site
6
+ * (`server.ts`), this predicate decides whether it is an ORPHAN of a cancelled
7
+ * recruit (→ self-exit, do not re-register) or a legitimate (re-)start (→ proceed).
8
+ *
9
+ * The discriminator is the **running-run × close-reason PAIR**:
10
+ * - If a RUNNING run exists for the derived id, this process is NOT an orphan —
11
+ * every managed re-creation (recruit / restart / migrate / up) pre-creates a
12
+ * RUNNING run before its process boots, so a legit reuse is seen as RUNNING and
13
+ * simply attaches via `USE_EXISTING`. Never self-exit.
14
+ * - Otherwise, the most-recent run is CLOSED. Self-exit ONLY when it closed with a
15
+ * typed tombstone reason (`destroyed` | `boot-timeout`) within a generous TTL.
16
+ * The TTL bounds a stale tombstone so a much-later legit manual reuse of the
17
+ * same name isn't blocked forever (the observed orphan spawn delay was ~100min).
18
+ *
19
+ * Pure + dependency-free so it unit-tests without a live Temporal env; `server.ts`
20
+ * supplies the `describe()`-derived inputs.
21
+ */
22
+ export interface OrphanTombstoneInput {
23
+ /** `describe().status.name` — e.g. `'RUNNING'`, `'COMPLETED'`, `'TERMINATED'`. */
24
+ statusName: string;
25
+ /** `describe().memo?.[MEMO_KEYS.closeReason]` — the typed close reason, if any. */
26
+ closeReason: unknown;
27
+ /** `describe().closeTime?.getTime() ?? 0` — ms epoch of run close, `0` if open/unknown. */
28
+ closeTimeMs: number;
29
+ }
30
+ /**
31
+ * Returns true iff the booting process should self-exit as a late orphan.
32
+ *
33
+ * @param input the `describe()`-derived run state for the derived workflow id
34
+ * @param ttlMs how long a tombstone is honored (ms); `> 0`
35
+ * @param nowMs current wall-clock ms (`Date.now()`)
36
+ */
37
+ export declare function shouldSelfExitAsOrphan(input: OrphanTombstoneInput, ttlMs: number, nowMs: number): boolean;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shouldSelfExitAsOrphan = shouldSelfExitAsOrphan;
4
+ /** Close reasons that tombstone a derived id against orphan re-registration. */
5
+ const TOMBSTONE_REASONS = new Set(['destroyed', 'boot-timeout']);
6
+ /**
7
+ * Returns true iff the booting process should self-exit as a late orphan.
8
+ *
9
+ * @param input the `describe()`-derived run state for the derived workflow id
10
+ * @param ttlMs how long a tombstone is honored (ms); `> 0`
11
+ * @param nowMs current wall-clock ms (`Date.now()`)
12
+ */
13
+ function shouldSelfExitAsOrphan(input, ttlMs, nowMs) {
14
+ // A running run means a legit (re-)creation already exists — attach, don't exit.
15
+ if (input.statusName === 'RUNNING')
16
+ return false;
17
+ // Only a typed tombstone close-reason qualifies.
18
+ if (typeof input.closeReason !== 'string' || !TOMBSTONE_REASONS.has(input.closeReason)) {
19
+ return false;
20
+ }
21
+ // Bound staleness: a tombstone older than the TTL is released so a legit
22
+ // much-later reuse of the same derived id isn't blocked.
23
+ if (!(input.closeTimeMs > 0))
24
+ return false;
25
+ return nowMs - input.closeTimeMs <= ttlMs;
26
+ }
@@ -61,6 +61,7 @@ export declare const MEMO_KEYS: {
61
61
  readonly agentType: "AgentTempoAgentType";
62
62
  readonly gitBranch: "AgentTempoGitBranch";
63
63
  readonly protocol: "AgentTempoProtocol";
64
+ readonly closeReason: "AgentTempoCloseReason";
64
65
  };
65
66
  /**
66
67
  * Escape a value for use in Temporal visibility query strings — strips
@@ -43,6 +43,15 @@ exports.MEMO_KEYS = {
43
43
  // Memo, not a search attribute: avoids a 6th Keyword (the #747 SA-diet keeps
44
44
  // us at 5/10) and needs no operator registration. See constants.PROTOCOL_VERSION.
45
45
  protocol: 'AgentTempoProtocol',
46
+ // #704 — typed terminal close-reason stamped on a workflow's memo when it
47
+ // COMPLETEs via `destroy` (`'destroyed'`) or the booting attach-timeout
48
+ // watchdog (`'boot-timeout'`). Survives completion and is readable via
49
+ // `describe().memo`. The bootstrap orphan-guard (`server.ts`) reads it to
50
+ // self-tombstone a late-launching orphan process whose run was cancelled,
51
+ // discriminated by the running-run × close-reason PAIR. A string memo (not a
52
+ // search attribute): avoids a 6th Keyword (the #747 SA-diet keeps us at 5/10)
53
+ // and needs no operator registration.
54
+ closeReason: 'AgentTempoCloseReason',
46
55
  };
47
56
  /**
48
57
  * Escape a value for use in Temporal visibility query strings — strips