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
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en" data-theme="dark" data-density="6" data-accent="terracotta">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<meta name="color-scheme" content="dark light" />
|
|
7
|
-
<meta name="description" content="agent-tempo Maestro Dashboard" />
|
|
8
|
-
<title>agent-tempo · Maestro</title>
|
|
9
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
|
-
<link
|
|
12
|
-
rel="stylesheet"
|
|
13
|
-
href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
14
|
-
/>
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-theme="dark" data-density="6" data-accent="terracotta">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="color-scheme" content="dark light" />
|
|
7
|
+
<meta name="description" content="agent-tempo Maestro Dashboard" />
|
|
8
|
+
<title>agent-tempo · Maestro</title>
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
|
+
<link
|
|
12
|
+
rel="stylesheet"
|
|
13
|
+
href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
14
|
+
/>
|
|
15
15
|
<script type="module" crossorigin src="/dashboard/assets/index-D6Xyje_n.js"></script>
|
|
16
16
|
<link rel="stylesheet" crossorigin href="/dashboard/assets/index-CB78ToNE.css">
|
|
17
|
-
</head>
|
|
18
|
-
<body>
|
|
19
|
-
<div id="root"></div>
|
|
20
|
-
</body>
|
|
21
|
-
</html>
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<div id="root"></div>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
package/dashboard/package.json
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "agent-tempo-dashboard",
|
|
3
|
-
"private": true,
|
|
4
|
-
"version": "1.1
|
|
5
|
-
"type": "module",
|
|
6
|
-
"description": "Web dashboard for agent-tempo. Bundled into the npm package; served by the daemon at /dashboard/*.",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"dev": "vite",
|
|
9
|
-
"build": "tsc -b && vite build",
|
|
10
|
-
"build:overflow": "tsc -b && vite build --mode overflow",
|
|
11
|
-
"preview": "vite preview",
|
|
12
|
-
"lint": "eslint src/ tests/",
|
|
13
|
-
"test": "vitest run",
|
|
14
|
-
"test:watch": "vitest",
|
|
15
|
-
"test:e2e": "playwright test",
|
|
16
|
-
"test:e2e:install": "playwright install chromium",
|
|
17
|
-
"test:overflow": "playwright test --config tests-overflow/playwright.config.ts"
|
|
18
|
-
},
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"@radix-ui/react-dialog": "~1.1.15",
|
|
21
|
-
"@tanstack/react-query": "5.100.5",
|
|
22
|
-
"react": "19.2.5",
|
|
23
|
-
"react-dom": "19.2.5",
|
|
24
|
-
"react-router-dom": "7.14.2",
|
|
25
|
-
"zustand": "^5.0.0"
|
|
26
|
-
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@eslint/js": "^9.0.0",
|
|
29
|
-
"@playwright/test": "^1.50.0",
|
|
30
|
-
"@tailwindcss/vite": "4.2.4",
|
|
31
|
-
"@testing-library/dom": "^10.0.0",
|
|
32
|
-
"@testing-library/jest-dom": "^6.6.0",
|
|
33
|
-
"@testing-library/react": "^16.1.0",
|
|
34
|
-
"@types/node": "^22.0.0",
|
|
35
|
-
"@types/react": "^19.2.0",
|
|
36
|
-
"@types/react-dom": "^19.2.0",
|
|
37
|
-
"@typescript-eslint/parser": "^8.0.0",
|
|
38
|
-
"@vitejs/plugin-react": "^5.0.0",
|
|
39
|
-
"eslint": "^9.0.0",
|
|
40
|
-
"eslint-plugin-react-hooks": "^5.1.0",
|
|
41
|
-
"jsdom": "^25.0.0",
|
|
42
|
-
"tailwindcss": "4.2.4",
|
|
43
|
-
"typescript": "^5.7.0",
|
|
44
|
-
"vite": "8.0.10",
|
|
45
|
-
"vitest": "^2.1.9"
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-tempo-dashboard",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.3.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Web dashboard for agent-tempo. Bundled into the npm package; served by the daemon at /dashboard/*.",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"build": "tsc -b && vite build",
|
|
10
|
+
"build:overflow": "tsc -b && vite build --mode overflow",
|
|
11
|
+
"preview": "vite preview",
|
|
12
|
+
"lint": "eslint src/ tests/",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:e2e": "playwright test",
|
|
16
|
+
"test:e2e:install": "playwright install chromium",
|
|
17
|
+
"test:overflow": "playwright test --config tests-overflow/playwright.config.ts"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@radix-ui/react-dialog": "~1.1.15",
|
|
21
|
+
"@tanstack/react-query": "5.100.5",
|
|
22
|
+
"react": "19.2.5",
|
|
23
|
+
"react-dom": "19.2.5",
|
|
24
|
+
"react-router-dom": "7.14.2",
|
|
25
|
+
"zustand": "^5.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@eslint/js": "^9.0.0",
|
|
29
|
+
"@playwright/test": "^1.50.0",
|
|
30
|
+
"@tailwindcss/vite": "4.2.4",
|
|
31
|
+
"@testing-library/dom": "^10.0.0",
|
|
32
|
+
"@testing-library/jest-dom": "^6.6.0",
|
|
33
|
+
"@testing-library/react": "^16.1.0",
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
"@types/react": "^19.2.0",
|
|
36
|
+
"@types/react-dom": "^19.2.0",
|
|
37
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
38
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
39
|
+
"eslint": "^9.0.0",
|
|
40
|
+
"eslint-plugin-react-hooks": "^5.1.0",
|
|
41
|
+
"jsdom": "^25.0.0",
|
|
42
|
+
"tailwindcss": "4.2.4",
|
|
43
|
+
"typescript": "^5.7.0",
|
|
44
|
+
"vite": "8.0.10",
|
|
45
|
+
"vitest": "^2.1.9"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -274,6 +274,7 @@ class CopilotSdkAttachment extends base_1.SdkAttachment {
|
|
|
274
274
|
// Spawn Copilot SDK client and session
|
|
275
275
|
const copilotClient = new CopilotClient({
|
|
276
276
|
logLevel: 'debug',
|
|
277
|
+
workingDirectory: workDir,
|
|
277
278
|
env: {
|
|
278
279
|
...cleanEnv(),
|
|
279
280
|
...(process.env.GITHUB_TOKEN ? { GITHUB_TOKEN: process.env.GITHUB_TOKEN } : {}),
|
|
@@ -286,7 +287,6 @@ class CopilotSdkAttachment extends base_1.SdkAttachment {
|
|
|
286
287
|
// All tool calls are auto-approved by design — the bridge operator accepts
|
|
287
288
|
// this when launching the bridge process.
|
|
288
289
|
onPermissionRequest: approveAll,
|
|
289
|
-
workingDirectory: workDir,
|
|
290
290
|
mcpServers: {
|
|
291
291
|
'agent-tempo': {
|
|
292
292
|
command: serverCommand,
|
|
@@ -302,6 +302,17 @@ class CopilotSdkAttachment extends base_1.SdkAttachment {
|
|
|
302
302
|
// `--append-system-prompt` argv. Behavior here is unchanged.
|
|
303
303
|
content: (0, system_prompt_1.buildSdkSystemPrompt)({ ensemble: config.ensemble }),
|
|
304
304
|
},
|
|
305
|
+
hooks: {
|
|
306
|
+
// Auto-allow agent-tempo MCP tools to skip the permission prompt round-trip.
|
|
307
|
+
// This eliminates the permission.requested → handler → approval cycle for
|
|
308
|
+
// every MCP tool call, reducing latency.
|
|
309
|
+
onPreToolUse: async (input) => {
|
|
310
|
+
if (input.toolName?.startsWith('mcp__agent-tempo__')) {
|
|
311
|
+
return { permissionDecision: 'allow' };
|
|
312
|
+
}
|
|
313
|
+
return undefined;
|
|
314
|
+
},
|
|
315
|
+
},
|
|
305
316
|
excludedTools: ['write_powershell', 'read_powershell', 'list_powershell'],
|
|
306
317
|
...(model ? { model } : {}),
|
|
307
318
|
};
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -132,6 +132,45 @@ export type StopTemporalResult = {
|
|
|
132
132
|
* profile collateral damage.
|
|
133
133
|
*/
|
|
134
134
|
export declare function stopTemporalServer(opts: StopTemporalServerOpts): StopTemporalResult;
|
|
135
|
+
/**
|
|
136
|
+
* Minimal child handle {@link startTemporalForDestroy} needs — `ChildProcess`
|
|
137
|
+
* satisfies it. Kept narrow so unit tests can inject a fake without spawning.
|
|
138
|
+
*
|
|
139
|
+
* @internal
|
|
140
|
+
*/
|
|
141
|
+
export interface SpawnedTemporalChild {
|
|
142
|
+
kill(): void;
|
|
143
|
+
unref(): void;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Dependency seam for {@link startTemporalForDestroy} — production callers
|
|
147
|
+
* pass nothing and get the real spawn + reachability probe. Tests inject
|
|
148
|
+
* stubs plus a tiny `pollDelayMs` so the readiness loop runs instantly.
|
|
149
|
+
*
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
export interface StartTemporalForDestroyDeps {
|
|
153
|
+
/** Readiness probe — defaults to {@link isTemporalReachable} for `config`. */
|
|
154
|
+
isReachable?: () => Promise<boolean>;
|
|
155
|
+
/** Spawn hook — defaults to a detached `temporal server start-dev`. */
|
|
156
|
+
spawn?: () => SpawnedTemporalChild;
|
|
157
|
+
/** Readiness poll attempts. Default 20. */
|
|
158
|
+
attempts?: number;
|
|
159
|
+
/** Delay between readiness polls, ms. Default 500 (→ 20×500ms = 10s). */
|
|
160
|
+
pollDelayMs?: number;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Start a temporary Temporal dev server just long enough for `down --destroy`
|
|
164
|
+
* to terminate workflows when Temporal happened to be down. Polls for
|
|
165
|
+
* readiness; on timeout it kills the child it spawned so `down` never leaves
|
|
166
|
+
* a stray Temporal process booting in the background. Exported for unit
|
|
167
|
+
* tests — production callers pass only `config`.
|
|
168
|
+
*
|
|
169
|
+
* @internal
|
|
170
|
+
*/
|
|
171
|
+
export declare function startTemporalForDestroy(config: Config, deps?: StartTemporalForDestroyDeps): Promise<{
|
|
172
|
+
started: boolean;
|
|
173
|
+
}>;
|
|
135
174
|
export declare function down(opts: DownOpts): Promise<void>;
|
|
136
175
|
interface AgentTypesCommandOpts {
|
|
137
176
|
subcommand?: string;
|
package/dist/cli/commands.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.up = up;
|
|
|
40
40
|
exports.formatScheduleRecurrence = formatScheduleRecurrence;
|
|
41
41
|
exports.lineupScheduleToEntry = lineupScheduleToEntry;
|
|
42
42
|
exports.stopTemporalServer = stopTemporalServer;
|
|
43
|
+
exports.startTemporalForDestroy = startTemporalForDestroy;
|
|
43
44
|
exports.down = down;
|
|
44
45
|
exports.agentTypesCommand = agentTypesCommand;
|
|
45
46
|
exports.broadcast = broadcast;
|
|
@@ -1547,6 +1548,44 @@ function stopTemporalServer(opts) {
|
|
|
1547
1548
|
return { action: 'failed', error: err };
|
|
1548
1549
|
}
|
|
1549
1550
|
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Start a temporary Temporal dev server just long enough for `down --destroy`
|
|
1553
|
+
* to terminate workflows when Temporal happened to be down. Polls for
|
|
1554
|
+
* readiness; on timeout it kills the child it spawned so `down` never leaves
|
|
1555
|
+
* a stray Temporal process booting in the background. Exported for unit
|
|
1556
|
+
* tests — production callers pass only `config`.
|
|
1557
|
+
*
|
|
1558
|
+
* @internal
|
|
1559
|
+
*/
|
|
1560
|
+
async function startTemporalForDestroy(config, deps = {}) {
|
|
1561
|
+
const attempts = deps.attempts ?? 20;
|
|
1562
|
+
const pollDelayMs = deps.pollDelayMs ?? 500;
|
|
1563
|
+
const isReachable = deps.isReachable ?? (() => isTemporalReachable(config));
|
|
1564
|
+
const spawn = deps.spawn ?? (() => {
|
|
1565
|
+
(0, fs_1.mkdirSync)(config_1.AGENT_TEMPO_HOME, { recursive: true });
|
|
1566
|
+
const port = config.temporalAddress.split(':')[1] || '7233';
|
|
1567
|
+
return (0, child_process_1.spawn)('temporal', [
|
|
1568
|
+
'server', 'start-dev',
|
|
1569
|
+
'--port', port,
|
|
1570
|
+
'--db-filename', DEFAULT_DB_PATH,
|
|
1571
|
+
], { detached: true, stdio: 'ignore' });
|
|
1572
|
+
});
|
|
1573
|
+
const child = spawn();
|
|
1574
|
+
child.unref();
|
|
1575
|
+
for (let i = 0; i < attempts; i++) {
|
|
1576
|
+
await new Promise(r => setTimeout(r, pollDelayMs));
|
|
1577
|
+
if (await isReachable())
|
|
1578
|
+
return { started: true };
|
|
1579
|
+
}
|
|
1580
|
+
// Timed out. The detached child may still be booting and would come up
|
|
1581
|
+
// orphaned moments after we give up — kill the process we spawned so
|
|
1582
|
+
// `down` doesn't leave a stray Temporal server behind.
|
|
1583
|
+
try {
|
|
1584
|
+
child.kill();
|
|
1585
|
+
}
|
|
1586
|
+
catch { /* already exited */ }
|
|
1587
|
+
return { started: false };
|
|
1588
|
+
}
|
|
1550
1589
|
async function down(opts) {
|
|
1551
1590
|
const config = (0, config_1.getConfig)(opts);
|
|
1552
1591
|
out.heading('agent-tempo teardown');
|
|
@@ -1555,7 +1594,35 @@ async function down(opts) {
|
|
|
1555
1594
|
: ` Stopping daemon + Temporal. Workflows stay parked for the next ${out.dim('agent-tempo up')}.`);
|
|
1556
1595
|
// Step 1 (destroy mode only): enumerate + terminate workflows across every
|
|
1557
1596
|
// ensemble, after a typed confirmation showing the user what's at stake.
|
|
1558
|
-
|
|
1597
|
+
let temporalUp = await isTemporalReachable(config);
|
|
1598
|
+
// `--destroy` can only terminate workflows while Temporal is reachable.
|
|
1599
|
+
// Workflow state lives durably on disk in ~/.agent-tempo/, so if Temporal
|
|
1600
|
+
// happens to be down when the user runs `down --destroy`, skipping the
|
|
1601
|
+
// destroy step here silently leaves every workflow to be resurrected the
|
|
1602
|
+
// next time anything starts the daemon (an `up`, a `status`, or the TUI).
|
|
1603
|
+
// To make `--destroy` actually mean it, start Temporal temporarily just
|
|
1604
|
+
// long enough to run the terminations — Step 4 below stops it again.
|
|
1605
|
+
let startedTemporalForDestroy = false;
|
|
1606
|
+
if (opts.destroy && !temporalUp) {
|
|
1607
|
+
if (!temporalCliExists()) {
|
|
1608
|
+
out.warn('temporal CLI not found — cannot destroy workflows; they will persist on disk.');
|
|
1609
|
+
}
|
|
1610
|
+
else {
|
|
1611
|
+
out.log(` ${out.dim('...')} Temporal is down — starting it temporarily to destroy workflows...`);
|
|
1612
|
+
const { started } = await startTemporalForDestroy(config);
|
|
1613
|
+
if (started) {
|
|
1614
|
+
temporalUp = true;
|
|
1615
|
+
startedTemporalForDestroy = true;
|
|
1616
|
+
out.success('Temporal started for cleanup');
|
|
1617
|
+
}
|
|
1618
|
+
else {
|
|
1619
|
+
out.warn('Could not start Temporal within 10s — workflows may survive teardown. ' +
|
|
1620
|
+
'Re-run `agent-tempo down --destroy` once Temporal is up. ' +
|
|
1621
|
+
'A stray Temporal process may have been left starting — check with ' +
|
|
1622
|
+
'`agent-tempo status` and stop it manually if one is still running.');
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1559
1626
|
if (opts.destroy && temporalUp) {
|
|
1560
1627
|
try {
|
|
1561
1628
|
const connection = await (0, connection_1.createTemporalConnection)(config);
|
|
@@ -1625,6 +1692,15 @@ async function down(opts) {
|
|
|
1625
1692
|
const confirmed = await typedConfirmPrompt(` This terminates every workflow (${totalTargets}) and cannot be undone.`, 'destroy');
|
|
1626
1693
|
if (!confirmed) {
|
|
1627
1694
|
out.log('Aborted.');
|
|
1695
|
+
// We may have started Temporal solely to run this destroy.
|
|
1696
|
+
// Aborting at the confirmation prompt must not leave that
|
|
1697
|
+
// server orphaned — stop it before the hard exit. We own it
|
|
1698
|
+
// outright, so force past the cross-profile guard.
|
|
1699
|
+
if (startedTemporalForDestroy) {
|
|
1700
|
+
if (stopTemporalServer({ killSharedTemporal: true }).action === 'killed') {
|
|
1701
|
+
out.log(` ${out.dim('Temporal server stopped')}`);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1628
1704
|
process.exit(0);
|
|
1629
1705
|
}
|
|
1630
1706
|
}
|
|
@@ -1676,7 +1752,12 @@ async function down(opts) {
|
|
|
1676
1752
|
// skips the kill when the OPPOSITE profile is likely active;
|
|
1677
1753
|
// `--kill-shared-temporal` is the explicit opt-in to override.
|
|
1678
1754
|
if (temporalUp) {
|
|
1679
|
-
|
|
1755
|
+
// When we started Temporal ourselves just for the destroy step, always
|
|
1756
|
+
// stop it again — the cross-profile guard is about not killing a server
|
|
1757
|
+
// the *other* profile owns, but this one we own outright.
|
|
1758
|
+
const result = stopTemporalServer({
|
|
1759
|
+
killSharedTemporal: opts.killSharedTemporal || startedTemporalForDestroy,
|
|
1760
|
+
});
|
|
1680
1761
|
switch (result.action) {
|
|
1681
1762
|
case 'killed':
|
|
1682
1763
|
out.success('Temporal server stopped');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refresh the entrypoint pointer so the global wrapper resolves to the
|
|
3
|
+
* currently-running binary. Called on every successful CLI boot — cheap
|
|
4
|
+
* (one `writeFileSync`) and idempotent.
|
|
5
|
+
*/
|
|
6
|
+
export declare function refreshEntrypoint(): void;
|
|
7
|
+
/**
|
|
8
|
+
* Ensure the wrapper scripts exist. Called once per binary version (guarded
|
|
9
|
+
* by the bootstrap cache). Returns `true` if a PATH hint should be shown
|
|
10
|
+
* to the user (i.e., the bin dir is not yet on PATH).
|
|
11
|
+
*/
|
|
12
|
+
export declare function provisionWrapperScripts(): {
|
|
13
|
+
created: boolean;
|
|
14
|
+
needsPathHint: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Returns a one-liner PATH hint appropriate for the current platform/shell.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getPathHint(): string;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.refreshEntrypoint = refreshEntrypoint;
|
|
4
|
+
exports.provisionWrapperScripts = provisionWrapperScripts;
|
|
5
|
+
exports.getPathHint = getPathHint;
|
|
6
|
+
/**
|
|
7
|
+
* Global wrapper provisioning — ensures `agent-tempo` is runnable from any
|
|
8
|
+
* shell without requiring `npx` or manual PATH surgery.
|
|
9
|
+
*
|
|
10
|
+
* **Strategy**: Write a thin wrapper script into `~/.agent-tempo/bin/` that
|
|
11
|
+
* reads an entrypoint pointer file (`.entrypoint`) to locate the real
|
|
12
|
+
* `dist/cli.js`. Every successful CLI boot refreshes the pointer so
|
|
13
|
+
* reinstalls (npm, pnpm, yarn — any package manager) auto-heal without
|
|
14
|
+
* user intervention.
|
|
15
|
+
*
|
|
16
|
+
* Cross-platform: emits a POSIX shell script + a `.cmd` for Windows.
|
|
17
|
+
*
|
|
18
|
+
* **Non-breaking**: If `agent-tempo` already resolves on PATH to a location
|
|
19
|
+
* outside `~/.agent-tempo/bin/` (e.g. npm global install), the wrapper is
|
|
20
|
+
* still written but the PATH hint is suppressed.
|
|
21
|
+
*/
|
|
22
|
+
const fs_1 = require("fs");
|
|
23
|
+
const path_1 = require("path");
|
|
24
|
+
const os_1 = require("os");
|
|
25
|
+
/** Resolved CLI entrypoint — the `dist/cli.js` of the running binary. */
|
|
26
|
+
const THIS_CLI_JS = (0, path_1.resolve)(__dirname, '..', 'cli.js');
|
|
27
|
+
/**
|
|
28
|
+
* Wrapper bin directory. Lives inside the agent-tempo home so it follows
|
|
29
|
+
* the same dev-mode / home-override semantics, but we hardcode `~/.agent-tempo`
|
|
30
|
+
* here because the wrapper must be stable across dev/prod modes — it's a
|
|
31
|
+
* user-facing PATH entry that shouldn't move.
|
|
32
|
+
*/
|
|
33
|
+
function getWrapperBinDir() {
|
|
34
|
+
return (0, path_1.join)((0, os_1.homedir)(), '.agent-tempo', 'bin');
|
|
35
|
+
}
|
|
36
|
+
const ENTRYPOINT_FILENAME = '.entrypoint';
|
|
37
|
+
// ─── Unix wrapper ────────────────────────────────────────────────────────
|
|
38
|
+
/* eslint-disable no-useless-escape */
|
|
39
|
+
const UNIX_WRAPPER = [
|
|
40
|
+
'#!/bin/sh',
|
|
41
|
+
'# Auto-generated by agent-tempo. Do not edit manually.',
|
|
42
|
+
'# This wrapper resolves the agent-tempo CLI entrypoint dynamically so',
|
|
43
|
+
'# reinstalls (npm/pnpm/yarn) auto-heal without re-linking.',
|
|
44
|
+
'set -e',
|
|
45
|
+
'SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"',
|
|
46
|
+
`ENTRYPOINT_FILE="\${SCRIPT_DIR}/${ENTRYPOINT_FILENAME}"`,
|
|
47
|
+
'if [ ! -f "$ENTRYPOINT_FILE" ]; then',
|
|
48
|
+
' echo "agent-tempo: entrypoint not configured. Run \'npx agent-tempo\' once to repair." >&2',
|
|
49
|
+
' exit 1',
|
|
50
|
+
'fi',
|
|
51
|
+
'ENTRYPOINT="$(cat "$ENTRYPOINT_FILE")"',
|
|
52
|
+
'if [ ! -f "$ENTRYPOINT" ]; then',
|
|
53
|
+
' echo "agent-tempo: entrypoint stale ($ENTRYPOINT). Run \'npx agent-tempo\' once to repair." >&2',
|
|
54
|
+
' exit 1',
|
|
55
|
+
'fi',
|
|
56
|
+
'exec node "$ENTRYPOINT" "$@"',
|
|
57
|
+
'',
|
|
58
|
+
].join('\n');
|
|
59
|
+
// ─── Windows wrapper ─────────────────────────────────────────────────────
|
|
60
|
+
const WIN_WRAPPER = [
|
|
61
|
+
'@echo off',
|
|
62
|
+
'rem Auto-generated by agent-tempo. Do not edit manually.',
|
|
63
|
+
'setlocal enabledelayedexpansion',
|
|
64
|
+
'set "SCRIPT_DIR=%~dp0"',
|
|
65
|
+
`set "ENTRYPOINT_FILE=%SCRIPT_DIR%${ENTRYPOINT_FILENAME}"`,
|
|
66
|
+
'if not exist "%ENTRYPOINT_FILE%" (',
|
|
67
|
+
' echo agent-tempo: entrypoint not configured. Run "npx agent-tempo" once to repair. >&2',
|
|
68
|
+
' exit /b 1',
|
|
69
|
+
')',
|
|
70
|
+
'set /p ENTRYPOINT=<"%ENTRYPOINT_FILE%"',
|
|
71
|
+
'if not exist "%ENTRYPOINT%" (',
|
|
72
|
+
' echo agent-tempo: entrypoint stale. Run "npx agent-tempo" once to repair. >&2',
|
|
73
|
+
' exit /b 1',
|
|
74
|
+
')',
|
|
75
|
+
'node "%ENTRYPOINT%" %*',
|
|
76
|
+
'',
|
|
77
|
+
].join('\r\n');
|
|
78
|
+
// ─── Public API ──────────────────────────────────────────────────────────
|
|
79
|
+
/**
|
|
80
|
+
* Refresh the entrypoint pointer so the global wrapper resolves to the
|
|
81
|
+
* currently-running binary. Called on every successful CLI boot — cheap
|
|
82
|
+
* (one `writeFileSync`) and idempotent.
|
|
83
|
+
*/
|
|
84
|
+
function refreshEntrypoint() {
|
|
85
|
+
try {
|
|
86
|
+
const binDir = getWrapperBinDir();
|
|
87
|
+
(0, fs_1.mkdirSync)(binDir, { recursive: true });
|
|
88
|
+
const pointerPath = (0, path_1.join)(binDir, ENTRYPOINT_FILENAME);
|
|
89
|
+
const current = safeRead(pointerPath);
|
|
90
|
+
// Skip write if already correct — avoids unnecessary disk churn.
|
|
91
|
+
if (current === THIS_CLI_JS)
|
|
92
|
+
return;
|
|
93
|
+
// Atomic write-then-rename: a torn `.entrypoint` would break the wrapper
|
|
94
|
+
// until the next CLI boot, so stage into a tmp file and rename into place.
|
|
95
|
+
// `renameSync` is atomic on POSIX and at least better-than-torn on Windows.
|
|
96
|
+
const tmp = pointerPath + '.tmp';
|
|
97
|
+
(0, fs_1.writeFileSync)(tmp, THIS_CLI_JS, 'utf8');
|
|
98
|
+
(0, fs_1.renameSync)(tmp, pointerPath);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Best-effort — never throw from a convenience provisioning step.
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Ensure the wrapper scripts exist. Called once per binary version (guarded
|
|
106
|
+
* by the bootstrap cache). Returns `true` if a PATH hint should be shown
|
|
107
|
+
* to the user (i.e., the bin dir is not yet on PATH).
|
|
108
|
+
*/
|
|
109
|
+
function provisionWrapperScripts() {
|
|
110
|
+
try {
|
|
111
|
+
const binDir = getWrapperBinDir();
|
|
112
|
+
(0, fs_1.mkdirSync)(binDir, { recursive: true });
|
|
113
|
+
const unixPath = (0, path_1.join)(binDir, 'agent-tempo');
|
|
114
|
+
const cmdPath = (0, path_1.join)(binDir, 'agent-tempo.cmd');
|
|
115
|
+
let created = false;
|
|
116
|
+
// Write Unix wrapper if missing or outdated.
|
|
117
|
+
if (!(0, fs_1.existsSync)(unixPath) || safeRead(unixPath) !== UNIX_WRAPPER) {
|
|
118
|
+
(0, fs_1.writeFileSync)(unixPath, UNIX_WRAPPER, { mode: 0o755 });
|
|
119
|
+
created = true;
|
|
120
|
+
}
|
|
121
|
+
// Ensure executable even if content matches (chmod may have been lost).
|
|
122
|
+
try {
|
|
123
|
+
(0, fs_1.chmodSync)(unixPath, 0o755);
|
|
124
|
+
}
|
|
125
|
+
catch { /* Windows — no-op */ }
|
|
126
|
+
// Write Windows wrapper if missing or outdated.
|
|
127
|
+
if (!(0, fs_1.existsSync)(cmdPath) || safeRead(cmdPath) !== WIN_WRAPPER) {
|
|
128
|
+
(0, fs_1.writeFileSync)(cmdPath, WIN_WRAPPER);
|
|
129
|
+
created = true;
|
|
130
|
+
}
|
|
131
|
+
// Write the entrypoint pointer.
|
|
132
|
+
refreshEntrypoint();
|
|
133
|
+
// Determine if PATH hint is needed.
|
|
134
|
+
const needsPathHint = !isBinDirOnPath(binDir);
|
|
135
|
+
return { created, needsPathHint };
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return { created: false, needsPathHint: false };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Returns a one-liner PATH hint appropriate for the current platform/shell.
|
|
143
|
+
*/
|
|
144
|
+
function getPathHint() {
|
|
145
|
+
const binDir = getWrapperBinDir();
|
|
146
|
+
if (process.platform === 'win32') {
|
|
147
|
+
return `Add to PATH: setx PATH "%PATH%;${binDir}"`;
|
|
148
|
+
}
|
|
149
|
+
// Unix — suggest the export for both bash and zsh.
|
|
150
|
+
return `Add to PATH: export PATH="${binDir}:$PATH" (add to ~/.zshrc or ~/.bashrc)`;
|
|
151
|
+
}
|
|
152
|
+
// ─── Internals ───────────────────────────────────────────────────────────
|
|
153
|
+
function safeRead(filePath) {
|
|
154
|
+
try {
|
|
155
|
+
return (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function isBinDirOnPath(binDir) {
|
|
162
|
+
const pathEnv = process.env.PATH || process.env.Path || '';
|
|
163
|
+
const sep = process.platform === 'win32' ? ';' : ':';
|
|
164
|
+
const dirs = pathEnv.split(sep);
|
|
165
|
+
// Windows filesystem paths are case-insensitive; normalize before comparing
|
|
166
|
+
// so `C:\Users\X\...` and `c:\users\x\...` don't produce a spurious PATH hint.
|
|
167
|
+
const norm = (p) => process.platform === 'win32' ? (0, path_1.resolve)(p).toLowerCase() : (0, path_1.resolve)(p);
|
|
168
|
+
return dirs.some((d) => norm(d) === norm(binDir));
|
|
169
|
+
}
|