agent-tempo 1.1.0 → 1.3.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.
- package/CLAUDE.md +219 -219
- package/LICENSE +21 -21
- package/README.md +289 -289
- package/assets/icon-dark.svg +9 -9
- package/assets/icon.svg +9 -9
- package/assets/logo-dark.svg +11 -11
- package/assets/logo-light.svg +11 -11
- package/dashboard/README.md +91 -91
- package/dashboard/dist/assets/index-D6Xyje_n.js.map +1 -1
- package/dashboard/dist/index.html +19 -19
- package/dashboard/package.json +47 -47
- package/dist/adapters/copilot/adapter.js +12 -1
- package/dist/cli/commands.d.ts +39 -0
- package/dist/cli/commands.js +83 -2
- package/dist/cli/global-wrapper.d.ts +19 -0
- package/dist/cli/global-wrapper.js +169 -0
- package/dist/cli/help-text.js +97 -97
- package/dist/cli/sa-preflight.d.ts +27 -3
- package/dist/cli/sa-preflight.js +169 -9
- package/dist/cli/startup.js +45 -8
- package/dist/cli/upgrade-command.js +81 -81
- package/dist/cli.js +12 -0
- package/dist/daemon.js +6 -0
- package/dist/http/catalog.js +17 -3
- package/dist/scripts/verify-daemon-isolation-guard.js +24 -24
- package/dist/server.js +4 -0
- package/dist/spawn.js +12 -12
- package/dist/tools/coat-check-evict.js +2 -2
- package/dist/tools/coat-check-get.js +2 -2
- package/dist/tools/coat-check-put.js +4 -4
- package/dist/tools/fetch-state.js +2 -2
- package/dist/tools/save-state.js +13 -13
- package/dist/utils/grpc-shutdown-guard.d.ts +52 -0
- package/dist/utils/grpc-shutdown-guard.js +88 -0
- package/examples/agents/tempo-composer.md +56 -56
- package/examples/agents/tempo-conductor.md +117 -117
- package/examples/agents/tempo-critic.md +73 -73
- package/examples/agents/tempo-improv.md +74 -74
- package/examples/agents/tempo-liner.md +75 -75
- package/examples/agents/tempo-roadie.md +61 -61
- package/examples/agents/tempo-soloist.md +71 -71
- package/examples/agents/tempo-tuner.md +94 -94
- package/examples/ensembles/tempo-big-band.yaml +146 -146
- package/examples/ensembles/tempo-dev-team.yaml +58 -58
- package/examples/ensembles/tempo-headless-jam.yaml +77 -77
- package/examples/ensembles/tempo-jam-session.yaml +41 -41
- package/examples/ensembles/tempo-mock-jam.yaml +79 -79
- package/examples/ensembles/tempo-review-squad.yaml +32 -32
- package/package.json +173 -172
- package/packaging/launchd/com.agent.tempo.plist +46 -46
- package/packaging/systemd/agent-tempo.service +32 -32
- package/packaging/windows/install-task.ps1 +71 -71
- package/scenarios/conductor-recruit-mock.yaml +33 -33
- package/scenarios/echo-roundtrip.yaml +15 -15
- package/scenarios/multi-player-handoff.yaml +38 -38
- package/scenarios/recruit-cascade.yaml +38 -38
- package/scenarios/two-player-conversation.yaml +33 -33
- package/workflow-bundle.js +1 -1
- package/dist/activities/claude-stop.d.ts +0 -21
- package/dist/activities/claude-stop.js +0 -94
- package/dist/channel.d.ts +0 -3
- package/dist/channel.js +0 -48
- package/dist/copilot-bridge.d.ts +0 -22
- package/dist/copilot-bridge.js +0 -565
- package/dist/scripts/258-spotcheck.js +0 -303
- package/dist/tools/detach.d.ts +0 -4
- package/dist/tools/detach.js +0 -45
- package/dist/tools/encore.d.ts +0 -4
- package/dist/tools/encore.js +0 -31
- package/dist/tools/pause-ensemble.d.ts +0 -4
- package/dist/tools/pause-ensemble.js +0 -58
- package/dist/tools/resume-ensemble.d.ts +0 -4
- package/dist/tools/resume-ensemble.js +0 -79
- package/dist/tools/stop.d.ts +0 -4
- package/dist/tools/stop.js +0 -29
- package/dist/tui/client.d.ts +0 -6
- package/dist/tui/client.js +0 -9
- package/dist/tui/components/ActivityLog.d.ts +0 -16
- package/dist/tui/components/ActivityLog.js +0 -36
- package/dist/tui/components/CommandOverlay.d.ts +0 -15
- package/dist/tui/components/CommandOverlay.js +0 -34
- package/dist/tui/components/ConductorChat.d.ts +0 -16
- package/dist/tui/components/ConductorChat.js +0 -32
- package/dist/tui/components/EnsembleListView.d.ts +0 -14
- package/dist/tui/components/EnsembleListView.js +0 -32
- package/dist/tui/components/EnsemblePanel.d.ts +0 -12
- package/dist/tui/components/EnsemblePanel.js +0 -40
- package/dist/tui/components/InputBar.d.ts +0 -13
- package/dist/tui/components/InputBar.js +0 -58
- package/dist/tui/components/ScheduleOverlay.d.ts +0 -13
- package/dist/tui/components/ScheduleOverlay.js +0 -113
- package/dist/tui/components/TopBar.d.ts +0 -12
- package/dist/tui/components/TopBar.js +0 -15
- package/dist/tui/core-api.d.ts +0 -26
- package/dist/tui/core-api.js +0 -67
- package/dist/tui/hooks/useEnsembleDiscovery.d.ts +0 -3
- package/dist/tui/hooks/useEnsembleDiscovery.js +0 -30
- package/dist/tui/hooks/useMaestroPoller.d.ts +0 -3
- package/dist/tui/hooks/useMaestroPoller.js +0 -36
- package/dist/tui/hooks/useSendCommand.d.ts +0 -7
- package/dist/tui/hooks/useSendCommand.js +0 -29
- package/dist/utils/bg-preflight.d.ts +0 -25
- package/dist/utils/bg-preflight.js +0 -154
package/dist/cli/help-text.js
CHANGED
|
@@ -57,102 +57,102 @@ const types_1 = require("../types");
|
|
|
57
57
|
// adding a new adapter automatically updates this surface.
|
|
58
58
|
const AGENT_OPTIONS = types_1.AGENT_TYPES.join('|');
|
|
59
59
|
function printHelp() {
|
|
60
|
-
console.log(`
|
|
61
|
-
${out.bold('agent-tempo')} — Multi-session Claude Code coordination via Temporal
|
|
62
|
-
|
|
63
|
-
${out.bold('Getting started:')}
|
|
64
|
-
${out.cyan('agent-tempo up')} Start infrastructure, then launch the TUI with ${out.dim('agent-tempo')}
|
|
65
|
-
|
|
66
|
-
${out.bold('Usage:')}
|
|
67
|
-
agent-tempo Launch the TUI (auto-provisions + opens home view)
|
|
68
|
-
agent-tempo <ensemble> Launch the TUI directly into an ensemble view
|
|
69
|
-
agent-tempo <command> [options]
|
|
70
|
-
|
|
71
|
-
${out.bold('Commands:')}
|
|
72
|
-
${out.cyan('up')} Start infrastructure only — Temporal, daemon, MCP registration
|
|
73
|
-
${out.cyan('down')} Stop infrastructure; workflows stay parked for the next ${out.dim('up')}
|
|
74
|
-
${out.cyan('down --destroy [-y]')} Terminate every workflow across every ensemble, then stop infrastructure
|
|
75
|
-
${out.cyan('server')} Start the Temporal dev server and register search attributes
|
|
76
|
-
${out.cyan('status')} [ensemble] Show active sessions and Temporal health
|
|
77
|
-
${out.cyan('ensemble')} <sub> Manage saved ensemble lineups (save/list/show)
|
|
78
|
-
${out.cyan('broadcast')} <message> Send a message to all active players
|
|
79
|
-
${out.cyan('destroy')} <ensemble> [-y] Terminate every workflow in one ensemble (typed confirmation)
|
|
80
|
-
${out.cyan('attachment-info')} <name> Inspect the V2 attachment phase + current holder
|
|
81
|
-
${out.cyan('recall')} <name> Read a player's message history (--limit/--offset/--preview/--from/--since/--include-sent/--json)
|
|
82
|
-
${out.cyan('hosts')} List daemons polling this Temporal namespace with advertised capabilities (--all/--json)
|
|
83
|
-
${out.cyan('refresh-host-profile')} Re-advertise this daemon's capability profile to the global Maestro
|
|
84
|
-
${out.cyan('restore')} <ensemble> Restore orphaned sessions in one ensemble on this host (--all-hosts for cluster-view listing)
|
|
85
|
-
${out.cyan('release')} [ensemble] Release all held players (unlock outbox, deliver messages)
|
|
86
|
-
${out.cyan('agent-types')} <sub> Manage player type definitions (list/show/init)
|
|
87
|
-
${out.cyan('daemon')} <sub> Manage the worker daemon (start/stop/status/logs)
|
|
88
|
-
${out.cyan('dashboard')} Open the web dashboard (--no-open / --pair / --json)
|
|
89
|
-
${out.cyan('upgrade')} [version] Upgrade agent-tempo to latest (or specific version)
|
|
90
|
-
${out.cyan('config')} Configure Temporal connection settings
|
|
91
|
-
${out.cyan('init')} Register MCP server globally (or --project for .mcp.json)
|
|
92
|
-
${out.cyan('preflight')} Run preflight checks only
|
|
93
|
-
${out.cyan('help')} Show this help message
|
|
94
|
-
|
|
95
|
-
${out.bold('Removed — use the TUI:')}
|
|
96
|
-
${out.dim('stop / restart / detach / migrate')} → ${out.dim('/destroy · /restart · /shutdown')}
|
|
97
|
-
${out.dim('conduct / start / recruit / disband')} → ${out.dim('launch `agent-tempo` · /recruit · /destroy')}
|
|
98
|
-
${out.dim('resume')} → ${out.dim('/play')}
|
|
99
|
-
See https://github.com/vinceblank/agent-tempo/issues/285 for the full migration table.
|
|
100
|
-
|
|
101
|
-
${out.bold('Connection options (all commands):')}
|
|
102
|
-
--temporal-address <addr> Temporal server address (default: localhost:7233)
|
|
103
|
-
--temporal-namespace <ns> Temporal namespace (default: default)
|
|
104
|
-
--temporal-api-key <key> Temporal API key (for Temporal Cloud)
|
|
105
|
-
--temporal-tls-cert <path> Path to TLS client certificate
|
|
106
|
-
--temporal-tls-key <path> Path to TLS client key
|
|
107
|
-
|
|
108
|
-
${out.bold('Other options:')}
|
|
109
|
-
--name <name> Set session window name (up only)
|
|
110
|
-
--agent <name> Agent type to spawn — ${AGENT_OPTIONS} (default: from config; up)
|
|
111
|
-
--dev Use the dev profile (~/.agent-tempo-dev, port 8474, namespace agent-tempo-dev)
|
|
112
|
-
--skip-preflight Skip preflight checks
|
|
113
|
-
--background Run Temporal in background (server only)
|
|
114
|
-
--project Use per-project .mcp.json instead of global (init only)
|
|
115
|
-
--keep-mcp Don't remove MCP config (down only)
|
|
116
|
-
--keep-daemon Don't stop the worker daemon (down only)
|
|
117
|
-
--destroy Also terminate every workflow (down only)
|
|
118
|
-
--kill-shared-temporal Tear down the Temporal dev server even if the other profile is active (down only, #423)
|
|
119
|
-
-y, --yes Skip confirmation prompt (down --destroy, destroy)
|
|
120
|
-
--lineup <name|file> Load ensemble lineup by name or file path (up)
|
|
121
|
-
--scenario <name|path> Force every mock player in the lineup into mockMode:scripted with this scenario (dev-mode-only, up + --lineup)
|
|
122
|
-
--no-hold Skip startup hold (requires --lineup on up)
|
|
123
|
-
--ensemble <name> Target a specific ensemble (broadcast, destroy, restore)
|
|
124
|
-
--all-hosts List cross-host orphans across the whole namespace (restore — read-only, #151)
|
|
125
|
-
-d, --dir <path> Target directory (default: cwd)
|
|
126
|
-
|
|
127
|
-
${out.bold('Config command:')}
|
|
128
|
-
${out.dim('agent-tempo config')} Interactive connection setup
|
|
129
|
-
${out.dim('agent-tempo config show')} Show resolved config
|
|
130
|
-
${out.dim('agent-tempo config set <k> <v>')} Set a config value
|
|
131
|
-
|
|
132
|
-
Settings are saved to ~/.agent-tempo/config.json.
|
|
133
|
-
Also reads ~/.config/temporalio/temporal.yaml as a fallback.
|
|
134
|
-
|
|
135
|
-
${out.bold('Resolution order:')} CLI flag > env var > config file > temporal CLI config > default
|
|
136
|
-
|
|
137
|
-
${out.bold('First time? Run this:')}
|
|
138
|
-
${out.dim('cd your-project')}
|
|
139
|
-
${out.dim('agent-tempo up')}
|
|
140
|
-
${out.dim('agent-tempo')} # Launch the TUI
|
|
141
|
-
|
|
142
|
-
${out.bold('Typical workflow:')}
|
|
143
|
-
${out.dim('agent-tempo up')} Start infrastructure (once per host)
|
|
144
|
-
${out.dim('agent-tempo')} Launch the TUI
|
|
145
|
-
${out.dim('agent-tempo status myband')} Check who's active in an ensemble
|
|
146
|
-
|
|
147
|
-
${out.bold('Environment:')}
|
|
148
|
-
AGENT_TEMPO_ENSEMBLE Default ensemble name (fallback: "default")
|
|
149
|
-
TEMPORAL_ADDRESS Default Temporal address (fallback: localhost:7233)
|
|
150
|
-
TEMPORAL_NAMESPACE Default Temporal namespace (fallback: "default")
|
|
151
|
-
TEMPORAL_API_KEY Temporal API key
|
|
152
|
-
TEMPORAL_TLS_CERT_PATH Path to TLS client certificate
|
|
153
|
-
TEMPORAL_TLS_KEY_PATH Path to TLS client key
|
|
154
|
-
AGENT_TEMPO_DEFAULT_AGENT Default agent type: claude or copilot (fallback: claude)
|
|
155
|
-
AGENT_TEMPO_DEV_MODE Set to "1" or "true" to enable the dev profile (alternative to --dev)
|
|
156
|
-
AGENT_TEMPO_HOME_OVERRIDE Override the home dir entirely (escape hatch for triple-isolated envs)
|
|
60
|
+
console.log(`
|
|
61
|
+
${out.bold('agent-tempo')} — Multi-session Claude Code coordination via Temporal
|
|
62
|
+
|
|
63
|
+
${out.bold('Getting started:')}
|
|
64
|
+
${out.cyan('agent-tempo up')} Start infrastructure, then launch the TUI with ${out.dim('agent-tempo')}
|
|
65
|
+
|
|
66
|
+
${out.bold('Usage:')}
|
|
67
|
+
agent-tempo Launch the TUI (auto-provisions + opens home view)
|
|
68
|
+
agent-tempo <ensemble> Launch the TUI directly into an ensemble view
|
|
69
|
+
agent-tempo <command> [options]
|
|
70
|
+
|
|
71
|
+
${out.bold('Commands:')}
|
|
72
|
+
${out.cyan('up')} Start infrastructure only — Temporal, daemon, MCP registration
|
|
73
|
+
${out.cyan('down')} Stop infrastructure; workflows stay parked for the next ${out.dim('up')}
|
|
74
|
+
${out.cyan('down --destroy [-y]')} Terminate every workflow across every ensemble, then stop infrastructure
|
|
75
|
+
${out.cyan('server')} Start the Temporal dev server and register search attributes
|
|
76
|
+
${out.cyan('status')} [ensemble] Show active sessions and Temporal health
|
|
77
|
+
${out.cyan('ensemble')} <sub> Manage saved ensemble lineups (save/list/show)
|
|
78
|
+
${out.cyan('broadcast')} <message> Send a message to all active players
|
|
79
|
+
${out.cyan('destroy')} <ensemble> [-y] Terminate every workflow in one ensemble (typed confirmation)
|
|
80
|
+
${out.cyan('attachment-info')} <name> Inspect the V2 attachment phase + current holder
|
|
81
|
+
${out.cyan('recall')} <name> Read a player's message history (--limit/--offset/--preview/--from/--since/--include-sent/--json)
|
|
82
|
+
${out.cyan('hosts')} List daemons polling this Temporal namespace with advertised capabilities (--all/--json)
|
|
83
|
+
${out.cyan('refresh-host-profile')} Re-advertise this daemon's capability profile to the global Maestro
|
|
84
|
+
${out.cyan('restore')} <ensemble> Restore orphaned sessions in one ensemble on this host (--all-hosts for cluster-view listing)
|
|
85
|
+
${out.cyan('release')} [ensemble] Release all held players (unlock outbox, deliver messages)
|
|
86
|
+
${out.cyan('agent-types')} <sub> Manage player type definitions (list/show/init)
|
|
87
|
+
${out.cyan('daemon')} <sub> Manage the worker daemon (start/stop/status/logs)
|
|
88
|
+
${out.cyan('dashboard')} Open the web dashboard (--no-open / --pair / --json)
|
|
89
|
+
${out.cyan('upgrade')} [version] Upgrade agent-tempo to latest (or specific version)
|
|
90
|
+
${out.cyan('config')} Configure Temporal connection settings
|
|
91
|
+
${out.cyan('init')} Register MCP server globally (or --project for .mcp.json)
|
|
92
|
+
${out.cyan('preflight')} Run preflight checks only
|
|
93
|
+
${out.cyan('help')} Show this help message
|
|
94
|
+
|
|
95
|
+
${out.bold('Removed — use the TUI:')}
|
|
96
|
+
${out.dim('stop / restart / detach / migrate')} → ${out.dim('/destroy · /restart · /shutdown')}
|
|
97
|
+
${out.dim('conduct / start / recruit / disband')} → ${out.dim('launch `agent-tempo` · /recruit · /destroy')}
|
|
98
|
+
${out.dim('resume')} → ${out.dim('/play')}
|
|
99
|
+
See https://github.com/vinceblank/agent-tempo/issues/285 for the full migration table.
|
|
100
|
+
|
|
101
|
+
${out.bold('Connection options (all commands):')}
|
|
102
|
+
--temporal-address <addr> Temporal server address (default: localhost:7233)
|
|
103
|
+
--temporal-namespace <ns> Temporal namespace (default: default)
|
|
104
|
+
--temporal-api-key <key> Temporal API key (for Temporal Cloud)
|
|
105
|
+
--temporal-tls-cert <path> Path to TLS client certificate
|
|
106
|
+
--temporal-tls-key <path> Path to TLS client key
|
|
107
|
+
|
|
108
|
+
${out.bold('Other options:')}
|
|
109
|
+
--name <name> Set session window name (up only)
|
|
110
|
+
--agent <name> Agent type to spawn — ${AGENT_OPTIONS} (default: from config; up)
|
|
111
|
+
--dev Use the dev profile (~/.agent-tempo-dev, port 8474, namespace agent-tempo-dev)
|
|
112
|
+
--skip-preflight Skip preflight checks
|
|
113
|
+
--background Run Temporal in background (server only)
|
|
114
|
+
--project Use per-project .mcp.json instead of global (init only)
|
|
115
|
+
--keep-mcp Don't remove MCP config (down only)
|
|
116
|
+
--keep-daemon Don't stop the worker daemon (down only)
|
|
117
|
+
--destroy Also terminate every workflow (down only)
|
|
118
|
+
--kill-shared-temporal Tear down the Temporal dev server even if the other profile is active (down only, #423)
|
|
119
|
+
-y, --yes Skip confirmation prompt (down --destroy, destroy)
|
|
120
|
+
--lineup <name|file> Load ensemble lineup by name or file path (up)
|
|
121
|
+
--scenario <name|path> Force every mock player in the lineup into mockMode:scripted with this scenario (dev-mode-only, up + --lineup)
|
|
122
|
+
--no-hold Skip startup hold (requires --lineup on up)
|
|
123
|
+
--ensemble <name> Target a specific ensemble (broadcast, destroy, restore)
|
|
124
|
+
--all-hosts List cross-host orphans across the whole namespace (restore — read-only, #151)
|
|
125
|
+
-d, --dir <path> Target directory (default: cwd)
|
|
126
|
+
|
|
127
|
+
${out.bold('Config command:')}
|
|
128
|
+
${out.dim('agent-tempo config')} Interactive connection setup
|
|
129
|
+
${out.dim('agent-tempo config show')} Show resolved config
|
|
130
|
+
${out.dim('agent-tempo config set <k> <v>')} Set a config value
|
|
131
|
+
|
|
132
|
+
Settings are saved to ~/.agent-tempo/config.json.
|
|
133
|
+
Also reads ~/.config/temporalio/temporal.yaml as a fallback.
|
|
134
|
+
|
|
135
|
+
${out.bold('Resolution order:')} CLI flag > env var > config file > temporal CLI config > default
|
|
136
|
+
|
|
137
|
+
${out.bold('First time? Run this:')}
|
|
138
|
+
${out.dim('cd your-project')}
|
|
139
|
+
${out.dim('agent-tempo up')}
|
|
140
|
+
${out.dim('agent-tempo')} # Launch the TUI
|
|
141
|
+
|
|
142
|
+
${out.bold('Typical workflow:')}
|
|
143
|
+
${out.dim('agent-tempo up')} Start infrastructure (once per host)
|
|
144
|
+
${out.dim('agent-tempo')} Launch the TUI
|
|
145
|
+
${out.dim('agent-tempo status myband')} Check who's active in an ensemble
|
|
146
|
+
|
|
147
|
+
${out.bold('Environment:')}
|
|
148
|
+
AGENT_TEMPO_ENSEMBLE Default ensemble name (fallback: "default")
|
|
149
|
+
TEMPORAL_ADDRESS Default Temporal address (fallback: localhost:7233)
|
|
150
|
+
TEMPORAL_NAMESPACE Default Temporal namespace (fallback: "default")
|
|
151
|
+
TEMPORAL_API_KEY Temporal API key
|
|
152
|
+
TEMPORAL_TLS_CERT_PATH Path to TLS client certificate
|
|
153
|
+
TEMPORAL_TLS_KEY_PATH Path to TLS client key
|
|
154
|
+
AGENT_TEMPO_DEFAULT_AGENT Default agent type: claude or copilot (fallback: claude)
|
|
155
|
+
AGENT_TEMPO_DEV_MODE Set to "1" or "true" to enable the dev profile (alternative to --dev)
|
|
156
|
+
AGENT_TEMPO_HOME_OVERRIDE Override the home dir entirely (escape hatch for triple-isolated envs)
|
|
157
157
|
`);
|
|
158
158
|
}
|
|
@@ -6,10 +6,13 @@ export declare const REQUIRED_SEARCH_ATTRIBUTES: ReadonlyArray<{
|
|
|
6
6
|
export interface SearchAttributePreflightOpts {
|
|
7
7
|
temporalAddress: string;
|
|
8
8
|
temporalNamespace: string;
|
|
9
|
+
/** API key for Temporal Cloud — triggers SDK-based probe when set. */
|
|
10
|
+
temporalApiKey?: string;
|
|
9
11
|
/**
|
|
10
12
|
* Optional test seam — given a namespace, return the set of search
|
|
11
13
|
* attribute names that ARE currently registered. Defaults to
|
|
12
|
-
* {@link
|
|
14
|
+
* {@link sdkProbeRegisteredAttributes} when `temporalApiKey` is set,
|
|
15
|
+
* otherwise {@link defaultProbeRegisteredAttributes} which shells out to
|
|
13
16
|
* `temporal operator search-attribute list`.
|
|
14
17
|
*/
|
|
15
18
|
probe?: (opts: {
|
|
@@ -35,15 +38,36 @@ export declare function defaultProbeRegisteredAttributes(opts: {
|
|
|
35
38
|
temporalAddress: string;
|
|
36
39
|
temporalNamespace: string;
|
|
37
40
|
}): Promise<Set<string>>;
|
|
41
|
+
/** Returns true if the address looks like a Temporal Cloud endpoint. */
|
|
42
|
+
export declare function isTemporalCloud(address: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* SDK-based probe — uses the Temporal Client SDK to verify search attribute
|
|
45
|
+
* existence by issuing a visibility query. Works with Temporal Cloud API keys
|
|
46
|
+
* where the `temporal operator` CLI commands are unauthorized.
|
|
47
|
+
*
|
|
48
|
+
* Strategy: for each required attribute, issue `listWorkflowExecutions` with
|
|
49
|
+
* a query referencing that attribute. If the attribute is registered the query
|
|
50
|
+
* returns (possibly empty) results. If not registered, Temporal responds with
|
|
51
|
+
* INVALID_ARGUMENT containing "is not a valid search attribute".
|
|
52
|
+
*/
|
|
53
|
+
export declare function sdkProbeRegisteredAttributes(opts: {
|
|
54
|
+
temporalAddress: string;
|
|
55
|
+
temporalNamespace: string;
|
|
56
|
+
temporalApiKey?: string;
|
|
57
|
+
}): Promise<Set<string>>;
|
|
38
58
|
/**
|
|
39
59
|
* Format the missing-SA error message. Paste-friendly: operators copy the
|
|
40
|
-
*
|
|
60
|
+
* registration commands verbatim. Cloud-aware: shows `tcld` commands for
|
|
61
|
+
* Temporal Cloud namespaces.
|
|
41
62
|
*/
|
|
42
|
-
export declare function formatPreflightError(missing: ReadonlyArray<typeof REQUIRED_SEARCH_ATTRIBUTES[number]>, namespace: string, probeError?: string): string;
|
|
63
|
+
export declare function formatPreflightError(missing: ReadonlyArray<typeof REQUIRED_SEARCH_ATTRIBUTES[number]>, namespace: string, probeError?: string, cloud?: boolean): string;
|
|
43
64
|
/**
|
|
44
65
|
* Verify all {@link REQUIRED_SEARCH_ATTRIBUTES} are registered on the
|
|
45
66
|
* given namespace. Returns a structured result — callers decide whether
|
|
46
67
|
* to log+continue (boot bootstrap step) or exit non-zero (daemon start).
|
|
68
|
+
*
|
|
69
|
+
* When `temporalApiKey` is set (or address is a Cloud endpoint), uses the
|
|
70
|
+
* SDK-based probe instead of shelling out to `temporal operator`.
|
|
47
71
|
*/
|
|
48
72
|
export declare function verifySearchAttributes(opts: SearchAttributePreflightOpts): Promise<SearchAttributePreflightResult>;
|
|
49
73
|
/**
|
package/dist/cli/sa-preflight.js
CHANGED
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.REQUIRED_SEARCH_ATTRIBUTES = void 0;
|
|
4
37
|
exports.defaultProbeRegisteredAttributes = defaultProbeRegisteredAttributes;
|
|
38
|
+
exports.isTemporalCloud = isTemporalCloud;
|
|
39
|
+
exports.sdkProbeRegisteredAttributes = sdkProbeRegisteredAttributes;
|
|
5
40
|
exports.formatPreflightError = formatPreflightError;
|
|
6
41
|
exports.verifySearchAttributes = verifySearchAttributes;
|
|
7
42
|
exports.classifyRegistrationOutput = classifyRegistrationOutput;
|
|
@@ -29,6 +64,16 @@ exports.assertSearchAttributesOrExit = assertSearchAttributesOrExit;
|
|
|
29
64
|
* - `src/daemon.ts` boot path calls {@link verifySearchAttributes}
|
|
30
65
|
* directly to fail fast on `agent-tempo daemon start` before the worker
|
|
31
66
|
* tries to register workflows.
|
|
67
|
+
*
|
|
68
|
+
* Cloud support:
|
|
69
|
+
* Temporal Cloud's operator gRPC service is not accessible with namespace
|
|
70
|
+
* API keys, causing `temporal operator search-attribute list/create` to
|
|
71
|
+
* fail with "Request unauthorized". When a Cloud namespace is detected
|
|
72
|
+
* (address contains `.tmprl.cloud` or an API key is configured), the
|
|
73
|
+
* preflight uses an SDK-based probe: issue a visibility query referencing
|
|
74
|
+
* each required attribute — a registered attribute returns an empty result
|
|
75
|
+
* set; an unregistered one throws INVALID_ARGUMENT. Registration
|
|
76
|
+
* instructions surface `tcld` commands instead of `temporal operator`.
|
|
32
77
|
*/
|
|
33
78
|
const child_process_1 = require("child_process");
|
|
34
79
|
/** Single source of truth — must match `SEARCH_ATTRIBUTES` in `src/cli/startup.ts`. */
|
|
@@ -73,22 +118,123 @@ async function defaultProbeRegisteredAttributes(opts) {
|
|
|
73
118
|
}
|
|
74
119
|
return names;
|
|
75
120
|
}
|
|
121
|
+
/** Returns true if the address looks like a Temporal Cloud endpoint. */
|
|
122
|
+
function isTemporalCloud(address) {
|
|
123
|
+
return address.includes('.tmprl.cloud');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Substrings Temporal servers use to signal "this search attribute is not
|
|
127
|
+
* registered". The exact wording varies across server versions and storage
|
|
128
|
+
* backends (e.g. 'is not a valid search attribute', 'is not defined',
|
|
129
|
+
* 'no mapping defined for the field'), so we match a set rather than a
|
|
130
|
+
* single phrase — a wording change must not cause the probe to misclassify
|
|
131
|
+
* an unregistered SA as an unexpected error and re-throw.
|
|
132
|
+
*/
|
|
133
|
+
const UNREGISTERED_SA_MARKERS = [
|
|
134
|
+
'is not a valid search attribute',
|
|
135
|
+
'is not defined',
|
|
136
|
+
'no mapping defined for the field',
|
|
137
|
+
'unknown or unindexed search attribute',
|
|
138
|
+
];
|
|
139
|
+
/** gRPC status code for INVALID_ARGUMENT. */
|
|
140
|
+
const GRPC_INVALID_ARGUMENT = 3;
|
|
141
|
+
/**
|
|
142
|
+
* Classify a visibility-query error as "search attribute not registered".
|
|
143
|
+
*
|
|
144
|
+
* A registered-but-empty attribute returns results; an unregistered one
|
|
145
|
+
* fails. Temporal reports the failure as gRPC INVALID_ARGUMENT — we key on
|
|
146
|
+
* that status code first (wording-independent) and fall back to known
|
|
147
|
+
* message substrings for transports/versions that don't surface a code.
|
|
148
|
+
*/
|
|
149
|
+
function isUnregisteredAttributeError(err) {
|
|
150
|
+
const msg = (err?.message || '').toLowerCase();
|
|
151
|
+
if (UNREGISTERED_SA_MARKERS.some((m) => msg.includes(m)))
|
|
152
|
+
return true;
|
|
153
|
+
// gRPC ServiceError exposes a numeric `code`; INVALID_ARGUMENT means the
|
|
154
|
+
// query referenced an attribute the namespace doesn't know about.
|
|
155
|
+
if (err?.code === GRPC_INVALID_ARGUMENT)
|
|
156
|
+
return true;
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* SDK-based probe — uses the Temporal Client SDK to verify search attribute
|
|
161
|
+
* existence by issuing a visibility query. Works with Temporal Cloud API keys
|
|
162
|
+
* where the `temporal operator` CLI commands are unauthorized.
|
|
163
|
+
*
|
|
164
|
+
* Strategy: for each required attribute, issue `listWorkflowExecutions` with
|
|
165
|
+
* a query referencing that attribute. If the attribute is registered the query
|
|
166
|
+
* returns (possibly empty) results. If not registered, Temporal responds with
|
|
167
|
+
* INVALID_ARGUMENT containing "is not a valid search attribute".
|
|
168
|
+
*/
|
|
169
|
+
async function sdkProbeRegisteredAttributes(opts) {
|
|
170
|
+
const { Connection } = await Promise.resolve().then(() => __importStar(require('@temporalio/client')));
|
|
171
|
+
const tls = opts.temporalApiKey ? true : undefined;
|
|
172
|
+
const conn = await Connection.connect({
|
|
173
|
+
address: opts.temporalAddress,
|
|
174
|
+
tls: tls,
|
|
175
|
+
apiKey: opts.temporalApiKey,
|
|
176
|
+
});
|
|
177
|
+
const registered = new Set();
|
|
178
|
+
try {
|
|
179
|
+
for (const attr of exports.REQUIRED_SEARCH_ATTRIBUTES) {
|
|
180
|
+
// The probe value only has to be syntactically valid for the
|
|
181
|
+
// attribute's type so the visibility query parses. We support
|
|
182
|
+
// Keyword (quoted string literal) and Bool (`true`) here — the only
|
|
183
|
+
// two types in REQUIRED_SEARCH_ATTRIBUTES. A new SA type would need
|
|
184
|
+
// its own literal form added below.
|
|
185
|
+
const testValue = attr.type === 'Bool' ? 'true' : '"__probe__"';
|
|
186
|
+
try {
|
|
187
|
+
await conn.workflowService.listWorkflowExecutions({
|
|
188
|
+
namespace: opts.temporalNamespace,
|
|
189
|
+
query: `${attr.name} = ${testValue}`,
|
|
190
|
+
pageSize: 1,
|
|
191
|
+
});
|
|
192
|
+
registered.add(attr.name);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
if (isUnregisteredAttributeError(err)) {
|
|
196
|
+
// Attribute not registered — don't add to set
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// Unexpected error — re-throw
|
|
200
|
+
throw err;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
await conn.close();
|
|
207
|
+
}
|
|
208
|
+
return registered;
|
|
209
|
+
}
|
|
76
210
|
/**
|
|
77
211
|
* Format the missing-SA error message. Paste-friendly: operators copy the
|
|
78
|
-
*
|
|
212
|
+
* registration commands verbatim. Cloud-aware: shows `tcld` commands for
|
|
213
|
+
* Temporal Cloud namespaces.
|
|
79
214
|
*/
|
|
80
|
-
function formatPreflightError(missing, namespace, probeError) {
|
|
215
|
+
function formatPreflightError(missing, namespace, probeError, cloud) {
|
|
81
216
|
const lines = [];
|
|
82
217
|
lines.push(`Required search attributes not registered on namespace '${namespace}'.`);
|
|
83
218
|
if (probeError) {
|
|
84
219
|
lines.push(`(Could not probe namespace state: ${probeError})`);
|
|
85
220
|
}
|
|
86
221
|
lines.push('');
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
222
|
+
if (cloud) {
|
|
223
|
+
lines.push('Register via tcld (Temporal Cloud CLI) or the Cloud UI, then restart the daemon:');
|
|
224
|
+
lines.push('');
|
|
225
|
+
const saFlags = missing.map((attr) => `--sa "${attr.name}=${attr.type}"`).join(' \\\n ');
|
|
226
|
+
lines.push(` tcld namespace search-attributes add --namespace ${namespace} \\\n ${saFlags}`);
|
|
227
|
+
lines.push('');
|
|
228
|
+
lines.push('Or add them manually in the Temporal Cloud UI:');
|
|
229
|
+
lines.push(` https://cloud.temporal.io → Namespaces → ${namespace} → Search Attributes`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
lines.push('Run these commands once per Temporal namespace, then restart the daemon:');
|
|
233
|
+
lines.push('');
|
|
234
|
+
for (const attr of missing) {
|
|
235
|
+
lines.push(` temporal operator search-attribute create ` +
|
|
236
|
+
`--name ${attr.name} --type ${attr.type} --namespace ${namespace}`);
|
|
237
|
+
}
|
|
92
238
|
}
|
|
93
239
|
lines.push('');
|
|
94
240
|
lines.push('(See docs/ops/v1.0-migration.md for the full upgrade walkthrough.)');
|
|
@@ -98,9 +244,23 @@ function formatPreflightError(missing, namespace, probeError) {
|
|
|
98
244
|
* Verify all {@link REQUIRED_SEARCH_ATTRIBUTES} are registered on the
|
|
99
245
|
* given namespace. Returns a structured result — callers decide whether
|
|
100
246
|
* to log+continue (boot bootstrap step) or exit non-zero (daemon start).
|
|
247
|
+
*
|
|
248
|
+
* When `temporalApiKey` is set (or address is a Cloud endpoint), uses the
|
|
249
|
+
* SDK-based probe instead of shelling out to `temporal operator`.
|
|
101
250
|
*/
|
|
102
251
|
async function verifySearchAttributes(opts) {
|
|
103
|
-
const
|
|
252
|
+
const cloud = isTemporalCloud(opts.temporalAddress) || !!opts.temporalApiKey;
|
|
253
|
+
const defaultProbe = cloud
|
|
254
|
+
? () => sdkProbeRegisteredAttributes({
|
|
255
|
+
temporalAddress: opts.temporalAddress,
|
|
256
|
+
temporalNamespace: opts.temporalNamespace,
|
|
257
|
+
temporalApiKey: opts.temporalApiKey,
|
|
258
|
+
})
|
|
259
|
+
: () => defaultProbeRegisteredAttributes({
|
|
260
|
+
temporalAddress: opts.temporalAddress,
|
|
261
|
+
temporalNamespace: opts.temporalNamespace,
|
|
262
|
+
});
|
|
263
|
+
const probe = opts.probe ?? defaultProbe;
|
|
104
264
|
let registered;
|
|
105
265
|
let probeError;
|
|
106
266
|
try {
|
|
@@ -121,7 +281,7 @@ async function verifySearchAttributes(opts) {
|
|
|
121
281
|
ok: false,
|
|
122
282
|
missing,
|
|
123
283
|
probeError,
|
|
124
|
-
message: formatPreflightError(missing, opts.temporalNamespace, probeError),
|
|
284
|
+
message: formatPreflightError(missing, opts.temporalNamespace, probeError, cloud),
|
|
125
285
|
};
|
|
126
286
|
}
|
|
127
287
|
/**
|
package/dist/cli/startup.js
CHANGED
|
@@ -134,6 +134,7 @@ const TTL_60S = 60 * 1000;
|
|
|
134
134
|
// (#605 consolidated the two duplicated literals).
|
|
135
135
|
// ─────────────────────────────────────────────────────────────────────────
|
|
136
136
|
const sa_preflight_1 = require("./sa-preflight");
|
|
137
|
+
const global_wrapper_1 = require("./global-wrapper");
|
|
137
138
|
// ─────────────────────────────────────────────────────────────────────────
|
|
138
139
|
// Semver-aware outdated-version badge rendering (#289 pin item 4)
|
|
139
140
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -376,14 +377,40 @@ async function stepSearchAttrs(cache, config, now) {
|
|
|
376
377
|
if (isCacheFresh(cache.steps.searchAttrs, TTL_24H, now)) {
|
|
377
378
|
return { status: 'skipped', durationMs: 0 };
|
|
378
379
|
}
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
380
|
+
const cloud = (0, sa_preflight_1.isTemporalCloud)(config.temporalAddress) || !!config.temporalApiKey;
|
|
381
|
+
const { result: outcome, durationMs } = await timed(async () => {
|
|
382
|
+
if (cloud) {
|
|
383
|
+
// For Temporal Cloud, use SDK probe to verify SAs are present.
|
|
384
|
+
// Registration must be done via tcld or the Cloud UI — we cannot
|
|
385
|
+
// use `temporal operator search-attribute create`.
|
|
386
|
+
try {
|
|
387
|
+
const registered = await (0, sa_preflight_1.sdkProbeRegisteredAttributes)({
|
|
388
|
+
temporalAddress: config.temporalAddress,
|
|
389
|
+
temporalNamespace: config.temporalNamespace,
|
|
390
|
+
temporalApiKey: config.temporalApiKey,
|
|
391
|
+
});
|
|
392
|
+
const missing = sa_preflight_1.REQUIRED_SEARCH_ATTRIBUTES.filter((a) => !registered.has(a.name));
|
|
393
|
+
if (missing.length > 0) {
|
|
394
|
+
const saFlags = missing.map((a) => `--sa "${a.name}=${a.type}"`).join(' ');
|
|
395
|
+
return {
|
|
396
|
+
status: 'failed',
|
|
397
|
+
durationMs: 0,
|
|
398
|
+
detail: `${missing.length} search attribute(s) not registered on Temporal Cloud.\n` +
|
|
399
|
+
` Register via: tcld namespace search-attributes add --namespace ${config.temporalNamespace} ${saFlags}\n` +
|
|
400
|
+
` Or add them in the Cloud UI: https://cloud.temporal.io`,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
return { status: 'ok', durationMs: 0 };
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
return {
|
|
407
|
+
status: 'failed',
|
|
408
|
+
durationMs: 0,
|
|
409
|
+
detail: `SDK probe failed: ${err?.message || err}`,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// Self-hosted path: per-attr classification via `registerSearchAttribute` (#605)
|
|
387
414
|
const failures = [];
|
|
388
415
|
for (const attr of sa_preflight_1.REQUIRED_SEARCH_ATTRIBUTES) {
|
|
389
416
|
const r = (0, sa_preflight_1.registerSearchAttribute)(attr, config.temporalAddress, config.temporalNamespace);
|
|
@@ -598,6 +625,16 @@ async function bootstrap(args) {
|
|
|
598
625
|
// Steps 1–5: fail-fast on step 1 so we don't try to register MCP on an
|
|
599
626
|
// incompatible Node. Each step mutates the cache in-place.
|
|
600
627
|
const preflight = await stepPreflight(cache, now);
|
|
628
|
+
// Provision the global wrapper scripts (`~/.agent-tempo/bin/agent-tempo`)
|
|
629
|
+
// so the command is accessible without `npx`. Runs once per binary version
|
|
630
|
+
// (guarded by cache wipe on version change). Best-effort, non-blocking.
|
|
631
|
+
if (preflight.status !== 'failed') {
|
|
632
|
+
const { needsPathHint } = (0, global_wrapper_1.provisionWrapperScripts)();
|
|
633
|
+
if (needsPathHint) {
|
|
634
|
+
// Emit a one-time hint to stderr (won't pollute --json output).
|
|
635
|
+
process.stderr.write(`\n hint: ${(0, global_wrapper_1.getPathHint)()}\n\n`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
601
638
|
// Steps 2 + 3 are independent (MCP config is local fs/shell; Temporal
|
|
602
639
|
// reachability is network), so run them in parallel — up to ~300ms
|
|
603
640
|
// cold-path savings when the claude-mcp-list shell-out is slow.
|