agent-tempo 1.7.0 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +10 -13
- package/README.md +21 -15
- package/dashboard/dist/assets/index-BbR7zIdK.js.map +1 -1
- package/dashboard/package.json +1 -1
- package/dist/activities/hard-terminate.d.ts +2 -2
- package/dist/activities/hard-terminate.js +3 -9
- package/dist/activities/maestro.d.ts +8 -13
- package/dist/activities/maestro.js +3 -10
- package/dist/activities/outbox.js +16 -2
- package/dist/activities/resolve.d.ts +18 -7
- package/dist/activities/resolve.js +58 -46
- package/dist/adapters/base.js +3 -0
- package/dist/adapters/claude-code/adapter.js +7 -0
- package/dist/cli/command-center-command.js +15 -1
- package/dist/cli/commands.d.ts +8 -0
- package/dist/cli/commands.js +79 -11
- package/dist/cli/daemon-command.js +19 -33
- package/dist/cli/help-text.js +9 -7
- package/dist/cli/home-command.d.ts +26 -0
- package/dist/cli/home-command.js +156 -0
- package/dist/cli/removed-verbs.d.ts +0 -4
- package/dist/cli/removed-verbs.js +23 -12
- package/dist/cli/sa-preflight.d.ts +0 -14
- package/dist/cli/sa-preflight.js +1 -17
- package/dist/cli/startup.d.ts +1 -10
- package/dist/cli/startup.js +0 -40
- package/dist/cli.js +34 -80
- package/dist/client/core.js +13 -15
- package/dist/client/index.d.ts +2 -1
- package/dist/client/interface.d.ts +5 -14
- package/dist/client/with-spawn.d.ts +9 -4
- package/dist/client/with-spawn.js +7 -10
- package/dist/config.d.ts +14 -13
- package/dist/config.js +14 -0
- package/dist/constants.d.ts +50 -0
- package/dist/constants.js +60 -1
- package/dist/daemon.js +81 -0
- package/dist/http/auth.d.ts +3 -21
- package/dist/http/auth.js +10 -31
- package/dist/http/event-types.d.ts +38 -4
- package/dist/http/orphans.d.ts +3 -3
- package/dist/http/orphans.js +3 -3
- package/dist/http/server.d.ts +0 -5
- package/dist/http/server.js +79 -29
- package/dist/http/snapshot.js +3 -0
- package/dist/http/writes.d.ts +1 -1
- package/dist/http/writes.js +25 -2
- package/dist/observability/nondeterminism-alarm.d.ts +113 -0
- package/dist/observability/nondeterminism-alarm.js +162 -0
- package/dist/palette/index.d.ts +6 -7
- package/dist/palette/index.js +6 -7
- package/dist/pi/mission-control/actions.d.ts +67 -1
- package/dist/pi/mission-control/actions.js +61 -0
- package/dist/pi/mission-control/extension.d.ts +77 -0
- package/dist/pi/mission-control/extension.js +171 -0
- package/dist/pi/workflow-client.d.ts +14 -16
- package/dist/pi/workflow-client.js +18 -44
- package/dist/server-tools.js +30 -29
- package/dist/server.js +42 -0
- package/dist/tools/action-guard.d.ts +23 -0
- package/dist/tools/action-guard.js +33 -0
- package/dist/tools/coat-check.d.ts +20 -0
- package/dist/tools/coat-check.js +129 -0
- package/dist/tools/gate.d.ts +13 -0
- package/dist/tools/gate.js +88 -0
- package/dist/tools/schedule.d.ts +18 -0
- package/dist/tools/schedule.js +93 -1
- package/dist/tools/stage.d.ts +17 -0
- package/dist/tools/stage.js +85 -1
- package/dist/tools/state.d.ts +14 -0
- package/dist/tools/state.js +95 -0
- package/dist/types.d.ts +54 -7
- package/dist/upgrade/boot-guard.d.ts +95 -0
- package/dist/upgrade/boot-guard.js +88 -0
- package/dist/upgrade/from-upgrade.d.ts +95 -0
- package/dist/upgrade/from-upgrade.js +330 -0
- package/dist/upgrade/phase-engine.js +4 -1
- package/dist/utils/duration.d.ts +3 -6
- package/dist/utils/duration.js +3 -6
- package/dist/utils/format-hosts.d.ts +1 -1
- package/dist/utils/format-hosts.js +0 -2
- package/dist/utils/grpc-shutdown-guard.js +0 -4
- package/dist/utils/hosts.d.ts +4 -10
- package/dist/utils/hosts.js +12 -28
- package/dist/utils/orphan-guard.d.ts +37 -0
- package/dist/utils/orphan-guard.js +26 -0
- package/dist/utils/search-attributes.d.ts +28 -18
- package/dist/utils/search-attributes.js +48 -18
- package/dist/utils/visibility-deadline.d.ts +16 -0
- package/dist/utils/visibility-deadline.js +16 -0
- package/dist/workflows/maestro-signals.d.ts +0 -13
- package/dist/workflows/maestro-signals.js +6 -14
- package/dist/workflows/maestro.js +27 -32
- package/dist/workflows/scheduler.js +7 -1
- package/dist/workflows/session.js +237 -153
- package/dist/workflows/signals.d.ts +9 -1
- package/dist/workflows/signals.js +8 -6
- package/package.json +1 -6
- package/workflow-bundle.js +478 -225
- package/dist/cli/legacy-migration.d.ts +0 -35
- package/dist/cli/legacy-migration.js +0 -341
- package/dist/client/ensure-conductor-spawned.d.ts +0 -35
- package/dist/client/ensure-conductor-spawned.js +0 -48
- package/dist/tui/App.d.ts +0 -85
- package/dist/tui/App.js +0 -1791
- package/dist/tui/bootstrap-types.d.ts +0 -46
- package/dist/tui/bootstrap-types.js +0 -7
- package/dist/tui/commands.d.ts +0 -71
- package/dist/tui/commands.js +0 -1375
- package/dist/tui/components/ChatView.d.ts +0 -35
- package/dist/tui/components/ChatView.js +0 -54
- package/dist/tui/components/CommandPalette.d.ts +0 -21
- package/dist/tui/components/CommandPalette.js +0 -67
- package/dist/tui/components/ConversationStream.d.ts +0 -114
- package/dist/tui/components/ConversationStream.js +0 -307
- package/dist/tui/components/CreateEnsembleWizard.d.ts +0 -19
- package/dist/tui/components/CreateEnsembleWizard.js +0 -223
- package/dist/tui/components/DestroyConfirmModal.d.ts +0 -17
- package/dist/tui/components/DestroyConfirmModal.js +0 -62
- package/dist/tui/components/ErrorView.d.ts +0 -31
- package/dist/tui/components/ErrorView.js +0 -129
- package/dist/tui/components/HomeView.d.ts +0 -54
- package/dist/tui/components/HomeView.js +0 -306
- package/dist/tui/components/LoadLineupModal.d.ts +0 -18
- package/dist/tui/components/LoadLineupModal.js +0 -79
- package/dist/tui/components/MainView.d.ts +0 -21
- package/dist/tui/components/MainView.js +0 -107
- package/dist/tui/components/NewEnsembleModal.d.ts +0 -9
- package/dist/tui/components/NewEnsembleModal.js +0 -73
- package/dist/tui/components/Picker.d.ts +0 -23
- package/dist/tui/components/Picker.js +0 -70
- package/dist/tui/components/PlayerDetailView.d.ts +0 -26
- package/dist/tui/components/PlayerDetailView.js +0 -118
- package/dist/tui/components/PromptArea.d.ts +0 -50
- package/dist/tui/components/PromptArea.js +0 -303
- package/dist/tui/components/RecruitWizard.d.ts +0 -17
- package/dist/tui/components/RecruitWizard.js +0 -221
- package/dist/tui/components/RestoreConfirmModal.d.ts +0 -18
- package/dist/tui/components/RestoreConfirmModal.js +0 -71
- package/dist/tui/components/ScheduleWizard.d.ts +0 -19
- package/dist/tui/components/ScheduleWizard.js +0 -259
- package/dist/tui/components/Splash.d.ts +0 -23
- package/dist/tui/components/Splash.js +0 -221
- package/dist/tui/components/StatusBar.d.ts +0 -48
- package/dist/tui/components/StatusBar.js +0 -128
- package/dist/tui/components/StatusOverlay.d.ts +0 -15
- package/dist/tui/components/StatusOverlay.js +0 -76
- package/dist/tui/components/TitleBar.d.ts +0 -10
- package/dist/tui/components/TitleBar.js +0 -21
- package/dist/tui/index.d.ts +0 -15
- package/dist/tui/index.js +0 -162
- package/dist/tui/ink-context.d.ts +0 -18
- package/dist/tui/ink-context.js +0 -59
- package/dist/tui/ink-loader.d.ts +0 -26
- package/dist/tui/ink-loader.js +0 -42
- package/dist/tui/removed-commands.d.ts +0 -9
- package/dist/tui/removed-commands.js +0 -22
- package/dist/tui/sse-handler.d.ts +0 -52
- package/dist/tui/sse-handler.js +0 -157
- package/dist/tui/store.d.ts +0 -598
- package/dist/tui/store.js +0 -753
- package/dist/tui/utils/format.d.ts +0 -56
- package/dist/tui/utils/format.js +0 -155
- package/dist/tui/utils/fullscreen.d.ts +0 -23
- package/dist/tui/utils/fullscreen.js +0 -71
- package/dist/tui/utils/history.d.ts +0 -10
- package/dist/tui/utils/history.js +0 -85
- package/dist/tui/utils/platform.d.ts +0 -45
- package/dist/tui/utils/platform.js +0 -258
- package/dist/tui/utils/theme.d.ts +0 -21
- package/dist/tui/utils/theme.js +0 -24
package/dist/client/core.js
CHANGED
|
@@ -42,9 +42,9 @@ exports.createTempoClientCore = createTempoClientCore;
|
|
|
42
42
|
* future SSE event source, and any external SDK consumer that wants a
|
|
43
43
|
* headless surface (no `child_process` dependency).
|
|
44
44
|
*
|
|
45
|
-
* The
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* The spawn method (`createEnsemble`) and the `runTempoCli` helper live in
|
|
46
|
+
* `./with-spawn.ts`, which composes this factory and adds the TTY-bound surface.
|
|
47
|
+
* (`spawnConductor` was removed in #789 with the Ink TUI.)
|
|
48
48
|
*
|
|
49
49
|
* See `docs/adr/0007-tempoclient-core-withspawn-split.md` and
|
|
50
50
|
* `docs/design/tempoclient-core-spawn-split.md`.
|
|
@@ -207,10 +207,10 @@ function createTempoClientCore(client, opts = {}) {
|
|
|
207
207
|
continue;
|
|
208
208
|
const entry = ensembleMap.get(name) || { count: 0, hasConductor: false };
|
|
209
209
|
entry.count++;
|
|
210
|
-
// Preferred: memo `AgentTempoIsConductor` (
|
|
211
|
-
// legacy-SA fallback
|
|
212
|
-
//
|
|
213
|
-
//
|
|
210
|
+
// Preferred: memo `AgentTempoIsConductor` (memo-only after the #788
|
|
211
|
+
// legacy-SA prune). Final fallback: workflow ID convention — covers
|
|
212
|
+
// the brief window after a conductor spawn before visibility
|
|
213
|
+
// propagates.
|
|
214
214
|
const isConductorFromSA = (0, search_attributes_1.getIsConductor)(wf) === true;
|
|
215
215
|
const isConductorFromId = wf.workflowId?.endsWith('-conductor') ?? false;
|
|
216
216
|
if (isConductorFromSA || isConductorFromId) {
|
|
@@ -257,10 +257,9 @@ function createTempoClientCore(client, opts = {}) {
|
|
|
257
257
|
// agent — counting it produced confusing "(2 players)" rows on
|
|
258
258
|
// a fresh ensemble with one real player. Mirrors the
|
|
259
259
|
// `filterRealPlayers` rule used in StatusBar (cf6becd). Detect
|
|
260
|
-
// via the `AgentTempoPlayerType` memo (
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
// propagates.
|
|
260
|
+
// via the `AgentTempoPlayerType` memo (memo-only after the #788
|
|
261
|
+
// legacy-SA prune), with a workflow-id-suffix fallback for the
|
|
262
|
+
// brief post-start window before visibility propagates.
|
|
264
263
|
const playerType = (0, search_attributes_1.getPlayerType)(wf);
|
|
265
264
|
const isMaestroSession = playerType === 'maestro'
|
|
266
265
|
|| (wf.workflowId?.endsWith('-maestro') ?? false);
|
|
@@ -379,10 +378,9 @@ function createTempoClientCore(client, opts = {}) {
|
|
|
379
378
|
for await (const wf of listWorkflows({ query })) {
|
|
380
379
|
const sa = wf.searchAttributes || {};
|
|
381
380
|
const playerId = Array.isArray(sa.AgentTempoPlayerId) ? String(sa.AgentTempoPlayerId[0]) : wf.workflowId;
|
|
382
|
-
// Preferred: memo (
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
// visibility propagates.
|
|
381
|
+
// Preferred: memo (memo-only after the #788 legacy-SA prune). Final
|
|
382
|
+
// fallback: workflow ID convention — covers the brief window after a
|
|
383
|
+
// conductor spawn before visibility propagates.
|
|
386
384
|
const isConductorFromSA = (0, search_attributes_1.getIsConductor)(wf) === true;
|
|
387
385
|
const isConductorFromId = wf.workflowId?.endsWith('-conductor') ?? false;
|
|
388
386
|
players.push({
|
package/dist/client/index.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* - {@link createTempoClientCore} — pure Temporal RPC, headless-safe
|
|
8
8
|
* (daemon, MCP server, SSE event source, future SDK consumers).
|
|
9
9
|
* - {@link createTempoClientWithSpawn} — the TTY-bound superset adding
|
|
10
|
-
* `createEnsemble`
|
|
10
|
+
* `createEnsemble` (the CLI `up` path + the command-center board's
|
|
11
|
+
* `/ensemble-up`). `spawnConductor` was removed in #789 with the Ink TUI.
|
|
11
12
|
*
|
|
12
13
|
* `createTempoClient` is preserved as a permanent alias for
|
|
13
14
|
* `createTempoClientWithSpawn` so every existing call site keeps the same
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TempoClient — public interface and related types.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* external integrations)
|
|
4
|
+
* Originally extracted from the former `src/tui/client.ts` so that headless
|
|
5
|
+
* consumers (CLI, tests, external integrations) could depend on the interface
|
|
6
|
+
* without pulling in Ink/React. (The Ink TUI was deleted in #789; the interface
|
|
7
|
+
* remains the shared contract for the CLI, daemon, command-center board, and
|
|
8
|
+
* external integrations.)
|
|
6
9
|
*/
|
|
7
10
|
import type { AgentType, MaestroPlayerInfo, MaestroRelayMessage, HistoryEntry, Message, SentMessage, SessionMetadata, ScheduleEntry, QualityGate, StageEntry, WorktreeEntry, EnsembleChatResult, AttachmentInfo, HostInfo, AnswerEntry, CoatCheckEntry } from '../types';
|
|
8
11
|
import type { CoatCheckPutInput, CoatCheckPutResult, CoatCheckGetInput } from '../workflows/maestro-signals';
|
|
@@ -515,18 +518,6 @@ export interface TempoClientWithSpawn extends TempoClientCore {
|
|
|
515
518
|
* daemon, or other headless processes.
|
|
516
519
|
*/
|
|
517
520
|
createEnsemble(opts: CreateEnsembleOpts): Promise<void>;
|
|
518
|
-
/**
|
|
519
|
-
* Spawn a conductor terminal for an existing ensemble — the restore-
|
|
520
|
-
* after-shutdown path. Shells out to `agent-tempo up <name>` which is
|
|
521
|
-
* idempotent at the workflow layer. Semantically distinct from
|
|
522
|
-
* {@link TempoClientWithSpawn.createEnsemble}: this fires on an ensemble
|
|
523
|
-
* that already exists; a "create" contradiction would mislead future
|
|
524
|
-
* readers. **Requires a TTY context.**
|
|
525
|
-
*/
|
|
526
|
-
spawnConductor(opts: {
|
|
527
|
-
ensemble: string;
|
|
528
|
-
workDir?: string;
|
|
529
|
-
}): Promise<void>;
|
|
530
521
|
}
|
|
531
522
|
/**
|
|
532
523
|
* Backwards-compatible alias preserved indefinitely (per ADR 0007's
|
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
* no TTY and a stray spawn would launch a terminal nothing can render.
|
|
8
8
|
*
|
|
9
9
|
* The boundary is enforced by the type system: headless callers depend on
|
|
10
|
-
* `TempoClientCore`;
|
|
11
|
-
*
|
|
10
|
+
* `TempoClientCore`; the spawn surface is opt-in.
|
|
11
|
+
*
|
|
12
|
+
* NOTE (#789): `spawnConductor` was removed with the Ink TUI (its only caller
|
|
13
|
+
* was the TUI's `ensure-conductor-spawned` helper). WithSpawn's only distinct
|
|
14
|
+
* method is now `createEnsemble` — still live (CLI `up`, command-center board).
|
|
15
|
+
* The Core-vs-WithSpawn split STAYS (WithSpawn is the universal client alias).
|
|
12
16
|
*
|
|
13
17
|
* See `docs/adr/0007-tempoclient-core-withspawn-split.md`.
|
|
14
18
|
*/
|
|
@@ -18,8 +22,9 @@ import type { TempoClientWithSpawn } from './interface';
|
|
|
18
22
|
export type { CreateTempoClientOpts } from './core';
|
|
19
23
|
/**
|
|
20
24
|
* Build a `TempoClientWithSpawn` over a configured Temporal `Client`.
|
|
21
|
-
* Composes {@link createTempoClientCore} and adds the
|
|
22
|
-
* spawn
|
|
25
|
+
* Composes {@link createTempoClientCore} and adds the TTY-bound
|
|
26
|
+
* `createEnsemble` spawn method. Use this for spawn-capable consumers (the CLI
|
|
27
|
+
* `up` path, the command-center board's `/ensemble-up`); pure-headless callers
|
|
23
28
|
* should use {@link createTempoClientCore} directly.
|
|
24
29
|
*
|
|
25
30
|
* `opts` (e.g. `subscribeDeps`) is forwarded to the Core factory.
|
|
@@ -59,8 +59,9 @@ async function runTempoCli(args, workDir) {
|
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* Build a `TempoClientWithSpawn` over a configured Temporal `Client`.
|
|
62
|
-
* Composes {@link createTempoClientCore} and adds the
|
|
63
|
-
* spawn
|
|
62
|
+
* Composes {@link createTempoClientCore} and adds the TTY-bound
|
|
63
|
+
* `createEnsemble` spawn method. Use this for spawn-capable consumers (the CLI
|
|
64
|
+
* `up` path, the command-center board's `/ensemble-up`); pure-headless callers
|
|
64
65
|
* should use {@link createTempoClientCore} directly.
|
|
65
66
|
*
|
|
66
67
|
* `opts` (e.g. `subscribeDeps`) is forwarded to the Core factory.
|
|
@@ -75,13 +76,9 @@ function createTempoClientWithSpawn(client, opts = {}) {
|
|
|
75
76
|
: ['up', opts.ensemble];
|
|
76
77
|
await runTempoCli(args, opts.workDir);
|
|
77
78
|
},
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// `createEnsemble` so call sites read as "make sure the conductor
|
|
83
|
-
// terminal is running", not "create a new ensemble".
|
|
84
|
-
await runTempoCli(['up', opts.ensemble], opts.workDir);
|
|
85
|
-
},
|
|
79
|
+
// #789: `spawnConductor` removed with the Ink TUI — its only caller was the
|
|
80
|
+
// TUI's `ensure-conductor-spawned` helper (also deleted). WithSpawn's only
|
|
81
|
+
// distinct method is now `createEnsemble` (still used by the command-center
|
|
82
|
+
// board's `/ensemble-up` + the CLI `up` path).
|
|
86
83
|
};
|
|
87
84
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -18,6 +18,20 @@ export declare const ENV: {
|
|
|
18
18
|
readonly DEFAULT_AGENT: "AGENT_TEMPO_DEFAULT_AGENT";
|
|
19
19
|
readonly PLAYER_TYPE: "AGENT_TEMPO_PLAYER_TYPE";
|
|
20
20
|
readonly CLAUDE_BIN: "AGENT_TEMPO_CLAUDE_BIN";
|
|
21
|
+
/**
|
|
22
|
+
* #704 — override (ms) for the booting attach-timeout watchdog deadline. Read
|
|
23
|
+
* at spawn time (`startRecruitedSession`) and threaded onto durable metadata
|
|
24
|
+
* (`SessionMetadata.bootingDeadlineMs`) because workflows can't read env.
|
|
25
|
+
* Default 180s. For tests/tuning.
|
|
26
|
+
*/
|
|
27
|
+
readonly BOOTING_DEADLINE_MS: "AGENT_TEMPO_BOOTING_DEADLINE_MS";
|
|
28
|
+
/**
|
|
29
|
+
* #704 — override (ms) for how long a destroyed / boot-timed-out run's
|
|
30
|
+
* close-reason tombstone is honored by the bootstrap orphan-guard
|
|
31
|
+
* (`server.ts`). A late-launching orphan within this window self-exits.
|
|
32
|
+
* Default 6h (the observed orphan spawn delay was ~100min). For tuning.
|
|
33
|
+
*/
|
|
34
|
+
readonly ORPHAN_TOMBSTONE_TTL_MS: "AGENT_TEMPO_ORPHAN_TOMBSTONE_TTL_MS";
|
|
21
35
|
/**
|
|
22
36
|
* #753 — cadence (ms) of the periodic `[agent-tempo:action-counters]` log
|
|
23
37
|
* line each instrumented process emits. Default 5 minutes; `0` disables.
|
|
@@ -257,19 +271,6 @@ export interface PersistedConfig {
|
|
|
257
271
|
temporalTlsKeyPath?: string;
|
|
258
272
|
defaultAgent?: AgentType;
|
|
259
273
|
claudeBin?: string;
|
|
260
|
-
/**
|
|
261
|
-
* Bearer token for the daemon's HTTP/SSE event source (#94, #95,
|
|
262
|
-
* SSE-PROTOCOL.md §3.1). Auto-generated on first daemon boot when
|
|
263
|
-
* bearer mode is required (`AGENT_TEMPO_HTTP_BIND` non-loopback OR
|
|
264
|
-
* a request with a non-loopback `Origin`) and no token is set:
|
|
265
|
-
* `crypto.randomBytes(32).toString('base64url')`, 0600 on POSIX.
|
|
266
|
-
* Rotation = delete this field; next daemon boot regenerates.
|
|
267
|
-
*
|
|
268
|
-
* 3e: this LEGACY single token is migrated to the READ tier (T1) — a daemon
|
|
269
|
-
* with only `httpToken` set keeps read access and emits a one-time startup
|
|
270
|
-
* warning to set an admin token for writes/gate/inner. Prefer `readToken`.
|
|
271
|
-
*/
|
|
272
|
-
httpToken?: string;
|
|
273
274
|
/**
|
|
274
275
|
* 3e RBAC — the READ-tier (T1) bearer token. Env `AGENT_TEMPO_HTTP_READ_TOKEN`
|
|
275
276
|
* takes precedence over this; auto-generated here on first bearer-mode boot if
|
package/dist/config.js
CHANGED
|
@@ -47,6 +47,20 @@ exports.ENV = {
|
|
|
47
47
|
DEFAULT_AGENT: 'AGENT_TEMPO_DEFAULT_AGENT',
|
|
48
48
|
PLAYER_TYPE: 'AGENT_TEMPO_PLAYER_TYPE',
|
|
49
49
|
CLAUDE_BIN: 'AGENT_TEMPO_CLAUDE_BIN',
|
|
50
|
+
/**
|
|
51
|
+
* #704 — override (ms) for the booting attach-timeout watchdog deadline. Read
|
|
52
|
+
* at spawn time (`startRecruitedSession`) and threaded onto durable metadata
|
|
53
|
+
* (`SessionMetadata.bootingDeadlineMs`) because workflows can't read env.
|
|
54
|
+
* Default 180s. For tests/tuning.
|
|
55
|
+
*/
|
|
56
|
+
BOOTING_DEADLINE_MS: 'AGENT_TEMPO_BOOTING_DEADLINE_MS',
|
|
57
|
+
/**
|
|
58
|
+
* #704 — override (ms) for how long a destroyed / boot-timed-out run's
|
|
59
|
+
* close-reason tombstone is honored by the bootstrap orphan-guard
|
|
60
|
+
* (`server.ts`). A late-launching orphan within this window self-exits.
|
|
61
|
+
* Default 6h (the observed orphan spawn delay was ~100min). For tuning.
|
|
62
|
+
*/
|
|
63
|
+
ORPHAN_TOMBSTONE_TTL_MS: 'AGENT_TEMPO_ORPHAN_TOMBSTONE_TTL_MS',
|
|
50
64
|
/**
|
|
51
65
|
* #753 — cadence (ms) of the periodic `[agent-tempo:action-counters]` log
|
|
52
66
|
* line each instrumented process emits. Default 5 minutes; `0` disables.
|
package/dist/constants.d.ts
CHANGED
|
@@ -16,6 +16,28 @@
|
|
|
16
16
|
* for the kill regex to match.
|
|
17
17
|
*/
|
|
18
18
|
export declare const ENSEMBLE_SENTINEL_FLAG = "--remote-control-session-name-prefix";
|
|
19
|
+
/**
|
|
20
|
+
* 2.0 wire-protocol version (#786 — the cutover keystone). Single source of truth
|
|
21
|
+
* for the protocol stamp, the boot guard, and the `claimAttachment` adapter
|
|
22
|
+
* handshake.
|
|
23
|
+
*
|
|
24
|
+
* - **Stamp:** every 2.0-created workflow records `protocol: PROTOCOL_VERSION` on
|
|
25
|
+
* its START input (so it's in history from run 1 and survives `continueAsNew`
|
|
26
|
+
* via the input spread) and upserts the memo key {@link
|
|
27
|
+
* MEMO_KEYS.protocol | `AgentTempoProtocol`} = PROTOCOL_VERSION.
|
|
28
|
+
* - **Boot guard:** a 2.0 daemon refuses to register workers if visibility shows
|
|
29
|
+
* any Running agent-tempo workflow lacking the stamp — a 2.0 worker can never
|
|
30
|
+
* deterministically replay a 1.x-recorded history, so we fail LOUD at boot
|
|
31
|
+
* (printing `agent-tempo upgrade-to-2`) instead of non-determinism-faulting
|
|
32
|
+
* deep in a workflow task later.
|
|
33
|
+
* - **claimAttachment:** the adapter sends `protocolVersion`; a 2.0 workflow
|
|
34
|
+
* rejects any value `!== PROTOCOL_VERSION` (incl. a v1 adapter that omits it).
|
|
35
|
+
*
|
|
36
|
+
* This module is runtime-free, so the constant is safe to import from BOTH the
|
|
37
|
+
* sandboxed workflow bundle (the stamp + claim validator) AND the daemon guard /
|
|
38
|
+
* CLI (the check). Bumping it is a hard cutover — see `docs/design/v2-scoping.md`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const PROTOCOL_VERSION: 2;
|
|
19
41
|
/**
|
|
20
42
|
* Escape the two regex metacharacters that can appear in validated player/ensemble
|
|
21
43
|
* names (which are constrained to `[A-Za-z0-9._-]+`). Safe to interpolate the
|
|
@@ -48,3 +70,31 @@ export declare function ensembleReadyBanner(name: string, playerCount: number):
|
|
|
48
70
|
* actually stops other players from acting while the conductor waits.
|
|
49
71
|
*/
|
|
50
72
|
export declare function ensembleReadyDirective(name: string, playerCount: number): string;
|
|
73
|
+
/** One command-center access tier — its short label + the source-of-truth wording. */
|
|
74
|
+
export interface CommandCenterAccessTier {
|
|
75
|
+
/** Short label shown at the head of the line (e.g. `No auth`, `/login`, `API key`). */
|
|
76
|
+
label: string;
|
|
77
|
+
/** Tier wording — mirrored VERBATIM into docs/cli.md (drift-guarded). */
|
|
78
|
+
description: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* #791 — command-center access tiers. THE SINGLE SOURCE OF TRUTH for the three
|
|
82
|
+
* access levels the `command-center` board offers, shared between the launch
|
|
83
|
+
* banner (`src/cli/command-center-command.ts`) and `docs/cli.md`.
|
|
84
|
+
*
|
|
85
|
+
* The tiers are independent, NOT a strict ladder: tier 1 (board + operator
|
|
86
|
+
* controls) needs NO auth on a local loopback daemon; tiers 2/3 only add the
|
|
87
|
+
* LLM PLANNER (ask / handoff / recruit), which needs either a Claude
|
|
88
|
+
* subscription via in-session `/login` (zero API key) or an `ANTHROPIC_API_KEY`.
|
|
89
|
+
*
|
|
90
|
+
* `tests/cli/command-center-access-tiers.test.ts` asserts the banner renders
|
|
91
|
+
* every tier AND that `docs/cli.md` carries each tier's label + description
|
|
92
|
+
* verbatim — so the launch text and the docs can never drift.
|
|
93
|
+
*/
|
|
94
|
+
export declare const COMMAND_CENTER_ACCESS_TIERS: readonly CommandCenterAccessTier[];
|
|
95
|
+
/**
|
|
96
|
+
* Render {@link COMMAND_CENTER_ACCESS_TIERS} as plain indented ` • label — description`
|
|
97
|
+
* lines for the `command-center` launch banner. One string per tier; the caller
|
|
98
|
+
* styles them and prints a heading. Runtime-free, so it stays importable anywhere.
|
|
99
|
+
*/
|
|
100
|
+
export declare function commandCenterAccessTierLines(): string[];
|
package/dist/constants.js
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* code without dragging Node-only modules into the sandboxed bundle.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.ENSEMBLE_SENTINEL_FLAG = void 0;
|
|
9
|
+
exports.COMMAND_CENTER_ACCESS_TIERS = exports.PROTOCOL_VERSION = exports.ENSEMBLE_SENTINEL_FLAG = void 0;
|
|
10
10
|
exports.escapeNameForRegex = escapeNameForRegex;
|
|
11
11
|
exports.ensembleReadyBanner = ensembleReadyBanner;
|
|
12
12
|
exports.ensembleReadyDirective = ensembleReadyDirective;
|
|
13
|
+
exports.commandCenterAccessTierLines = commandCenterAccessTierLines;
|
|
13
14
|
/**
|
|
14
15
|
* CLI flag injected into every Claude Code spawn to carry the ensemble name as a
|
|
15
16
|
* sentinel in the process's CommandLine. `src/activities/hard-terminate.ts`
|
|
@@ -22,6 +23,28 @@ exports.ensembleReadyDirective = ensembleReadyDirective;
|
|
|
22
23
|
* for the kill regex to match.
|
|
23
24
|
*/
|
|
24
25
|
exports.ENSEMBLE_SENTINEL_FLAG = '--remote-control-session-name-prefix';
|
|
26
|
+
/**
|
|
27
|
+
* 2.0 wire-protocol version (#786 — the cutover keystone). Single source of truth
|
|
28
|
+
* for the protocol stamp, the boot guard, and the `claimAttachment` adapter
|
|
29
|
+
* handshake.
|
|
30
|
+
*
|
|
31
|
+
* - **Stamp:** every 2.0-created workflow records `protocol: PROTOCOL_VERSION` on
|
|
32
|
+
* its START input (so it's in history from run 1 and survives `continueAsNew`
|
|
33
|
+
* via the input spread) and upserts the memo key {@link
|
|
34
|
+
* MEMO_KEYS.protocol | `AgentTempoProtocol`} = PROTOCOL_VERSION.
|
|
35
|
+
* - **Boot guard:** a 2.0 daemon refuses to register workers if visibility shows
|
|
36
|
+
* any Running agent-tempo workflow lacking the stamp — a 2.0 worker can never
|
|
37
|
+
* deterministically replay a 1.x-recorded history, so we fail LOUD at boot
|
|
38
|
+
* (printing `agent-tempo upgrade-to-2`) instead of non-determinism-faulting
|
|
39
|
+
* deep in a workflow task later.
|
|
40
|
+
* - **claimAttachment:** the adapter sends `protocolVersion`; a 2.0 workflow
|
|
41
|
+
* rejects any value `!== PROTOCOL_VERSION` (incl. a v1 adapter that omits it).
|
|
42
|
+
*
|
|
43
|
+
* This module is runtime-free, so the constant is safe to import from BOTH the
|
|
44
|
+
* sandboxed workflow bundle (the stamp + claim validator) AND the daemon guard /
|
|
45
|
+
* CLI (the check). Bumping it is a hard cutover — see `docs/design/v2-scoping.md`.
|
|
46
|
+
*/
|
|
47
|
+
exports.PROTOCOL_VERSION = 2;
|
|
25
48
|
/**
|
|
26
49
|
* Escape the two regex metacharacters that can appear in validated player/ensemble
|
|
27
50
|
* names (which are constrained to `[A-Za-z0-9._-]+`). Safe to interpolate the
|
|
@@ -72,3 +95,39 @@ function ensembleReadyDirective(name, playerCount) {
|
|
|
72
95
|
'If the user has not spoken yet, wait silently.',
|
|
73
96
|
].join('\n');
|
|
74
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* #791 — command-center access tiers. THE SINGLE SOURCE OF TRUTH for the three
|
|
100
|
+
* access levels the `command-center` board offers, shared between the launch
|
|
101
|
+
* banner (`src/cli/command-center-command.ts`) and `docs/cli.md`.
|
|
102
|
+
*
|
|
103
|
+
* The tiers are independent, NOT a strict ladder: tier 1 (board + operator
|
|
104
|
+
* controls) needs NO auth on a local loopback daemon; tiers 2/3 only add the
|
|
105
|
+
* LLM PLANNER (ask / handoff / recruit), which needs either a Claude
|
|
106
|
+
* subscription via in-session `/login` (zero API key) or an `ANTHROPIC_API_KEY`.
|
|
107
|
+
*
|
|
108
|
+
* `tests/cli/command-center-access-tiers.test.ts` asserts the banner renders
|
|
109
|
+
* every tier AND that `docs/cli.md` carries each tier's label + description
|
|
110
|
+
* verbatim — so the launch text and the docs can never drift.
|
|
111
|
+
*/
|
|
112
|
+
exports.COMMAND_CENTER_ACCESS_TIERS = [
|
|
113
|
+
{
|
|
114
|
+
label: 'No auth',
|
|
115
|
+
description: 'board + operator controls (cue, pause, play, restart, destroy) — works out of the box on a local (loopback) daemon; no token or login required.',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
label: '/login',
|
|
119
|
+
description: 'LLM planner on your Claude Pro/Max subscription — run /login inside the board to enable the planner (ask, handoff, recruit) with zero API key.',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
label: 'API key',
|
|
123
|
+
description: 'LLM planner on an API key — set ANTHROPIC_API_KEY before launch to run the planner against the Anthropic API.',
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
/**
|
|
127
|
+
* Render {@link COMMAND_CENTER_ACCESS_TIERS} as plain indented ` • label — description`
|
|
128
|
+
* lines for the `command-center` launch banner. One string per tier; the caller
|
|
129
|
+
* styles them and prints a heading. Runtime-free, so it stays importable anywhere.
|
|
130
|
+
*/
|
|
131
|
+
function commandCenterAccessTierLines() {
|
|
132
|
+
return exports.COMMAND_CENTER_ACCESS_TIERS.map((t) => ` • ${t.label} — ${t.description}`);
|
|
133
|
+
}
|
package/dist/daemon.js
CHANGED
|
@@ -75,6 +75,8 @@ const inner_loop_1 = require("./http/inner-loop");
|
|
|
75
75
|
const doorbell_1 = require("./http/doorbell");
|
|
76
76
|
const ingest_registry_1 = require("./http/ingest-registry");
|
|
77
77
|
const grpc_shutdown_guard_1 = require("./utils/grpc-shutdown-guard");
|
|
78
|
+
const worker_2 = require("@temporalio/worker");
|
|
79
|
+
const nondeterminism_alarm_1 = require("./observability/nondeterminism-alarm");
|
|
78
80
|
const action_counters_1 = require("./utils/action-counters");
|
|
79
81
|
const daemon_1 = require("./cli/daemon");
|
|
80
82
|
const client_3 = require("./client");
|
|
@@ -847,6 +849,33 @@ async function main() {
|
|
|
847
849
|
// close race so a stray retry timer can't kill the long-lived daemon. See
|
|
848
850
|
// src/utils/grpc-shutdown-guard.ts.
|
|
849
851
|
(0, grpc_shutdown_guard_1.installGrpcShutdownGuard)();
|
|
852
|
+
// #886 slice 1 — install the nondeterminism alarm BEFORE any Worker.create.
|
|
853
|
+
// Wraps Temporal's Runtime logger so a nondeterminism / determinism-violation
|
|
854
|
+
// flap (the #801 incident: 57 workflow-task failures in 3min with ZERO
|
|
855
|
+
// operator signal) is NAMED instantly — a prominent `[agent-tempo:ALARM]`
|
|
856
|
+
// line with a running count — and surfaced on `GET /v1/health`
|
|
857
|
+
// (`HealthV1.nondeterminism`). Runtime.install must precede worker creation
|
|
858
|
+
// and can only run once per process; both hold here (daemon entry point).
|
|
859
|
+
{
|
|
860
|
+
const alarm = new nondeterminism_alarm_1.NondeterminismAlarm({
|
|
861
|
+
onHit: (count, sample) => {
|
|
862
|
+
// Prominent + greppable (`ALARM`), distinct from normal daemon chatter.
|
|
863
|
+
log(`[agent-tempo:ALARM] nondeterminism #${count} — ${sample.detail}`);
|
|
864
|
+
},
|
|
865
|
+
});
|
|
866
|
+
(0, nondeterminism_alarm_1.setGlobalNondeterminismAlarm)(alarm);
|
|
867
|
+
try {
|
|
868
|
+
// Match the SDK default logger level ('INFO') so verbosity is unchanged;
|
|
869
|
+
// the wrapper only ADDS alarm detection on WARN/ERROR records.
|
|
870
|
+
worker_2.Runtime.install({ logger: (0, nondeterminism_alarm_1.wrapLoggerWithAlarm)(new worker_2.DefaultLogger('INFO'), alarm) });
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
// Runtime.install throws if a Runtime already exists this process. Non-
|
|
874
|
+
// fatal: the alarm singleton still backs /v1/health; we just couldn't
|
|
875
|
+
// intercept Core logs. (Shouldn't happen — this is the daemon entry.)
|
|
876
|
+
log('nondeterminism alarm: Runtime.install skipped (runtime already initialized):', err instanceof Error ? err.message : err);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
850
879
|
// ADR 0014 §5.4 / gate 4 — dev daemon log self-identifies. Banner fires
|
|
851
880
|
// first so it lands at the top of `~/.agent-tempo-dev/daemon.log` for
|
|
852
881
|
// grep-friendly identification regardless of subsequent log volume.
|
|
@@ -952,6 +981,58 @@ async function main() {
|
|
|
952
981
|
log('search-attribute preflight probe failed (non-fatal — createWorkers will surface the real error):', result.probeError);
|
|
953
982
|
}
|
|
954
983
|
}
|
|
984
|
+
// #786 — 2.0 cutover BOOT GUARD. Runs AFTER the search-attribute preflight
|
|
985
|
+
// and BEFORE createWorkers(): a 2.0 worker cannot deterministically replay a
|
|
986
|
+
// 1.x-recorded history, so refuse to register workers if visibility still
|
|
987
|
+
// shows ANY Running agent-tempo workflow lacking the protocol-2 stamp (a
|
|
988
|
+
// pre-cutover 1.x run). FAIL-CLOSED — a timed-out/errored scan, or a failure
|
|
989
|
+
// to even run the scan, refuses too (we couldn't PROVE the cutover is clean;
|
|
990
|
+
// see the #845 partial-scan lesson). Namespace-scoped via the client below.
|
|
991
|
+
// See src/upgrade/boot-guard.ts.
|
|
992
|
+
{
|
|
993
|
+
const { checkProtocolGuard } = await Promise.resolve().then(() => __importStar(require('./upgrade/boot-guard')));
|
|
994
|
+
let result;
|
|
995
|
+
let guardConn = null;
|
|
996
|
+
try {
|
|
997
|
+
guardConn = await (0, connection_1.createTemporalConnection)(config);
|
|
998
|
+
const guardClient = new client_1.Client({
|
|
999
|
+
connection: guardConn,
|
|
1000
|
+
namespace: config.temporalNamespace,
|
|
1001
|
+
interceptors: (0, action_counters_1.actionCountingInterceptors)(),
|
|
1002
|
+
});
|
|
1003
|
+
result = await checkProtocolGuard(guardClient, { log });
|
|
1004
|
+
}
|
|
1005
|
+
catch (err) {
|
|
1006
|
+
// FAIL-CLOSED — couldn't even run the scan (e.g. connection failure).
|
|
1007
|
+
// Without a completed scan we can't prove a clean cutover, so refuse
|
|
1008
|
+
// rather than risk a 2.0 worker replaying a 1.x history.
|
|
1009
|
+
result = {
|
|
1010
|
+
ok: false,
|
|
1011
|
+
reason: 'scan-incomplete',
|
|
1012
|
+
message: `agent-tempo 2.0 boot guard could not run the protocol scan ` +
|
|
1013
|
+
`(${err instanceof Error ? err.message : String(err)}). Refusing to boot ` +
|
|
1014
|
+
`(fail-closed). Verify Temporal at ${config.temporalAddress} ` +
|
|
1015
|
+
`(namespace ${config.temporalNamespace}) is reachable, then retry.`,
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
finally {
|
|
1019
|
+
if (guardConn)
|
|
1020
|
+
await guardConn.close().catch(() => { });
|
|
1021
|
+
}
|
|
1022
|
+
if (!result.ok) {
|
|
1023
|
+
process.stderr.write('ERROR: ' + result.message + '\n');
|
|
1024
|
+
log(`Daemon refused to boot — ${result.reason} (#786 protocol guard)`);
|
|
1025
|
+
try {
|
|
1026
|
+
fs.unlinkSync(daemon_1.DAEMON_PID_PATH);
|
|
1027
|
+
}
|
|
1028
|
+
catch { /* ignore */ }
|
|
1029
|
+
try {
|
|
1030
|
+
fs.unlinkSync(daemon_1.DAEMON_HEARTBEAT_PATH);
|
|
1031
|
+
}
|
|
1032
|
+
catch { /* ignore */ }
|
|
1033
|
+
process.exit(1);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
955
1036
|
// Use mutable refs so signal handlers can be registered before workers
|
|
956
1037
|
// are created — closes the narrow window where a SIGTERM during
|
|
957
1038
|
// createWorkers() would be missed.
|
package/dist/http/auth.d.ts
CHANGED
|
@@ -50,44 +50,26 @@ export declare function extractBearerToken(authHeader: string | undefined): stri
|
|
|
50
50
|
* reading "constant-time" doesn't assume length is also blinded.
|
|
51
51
|
*/
|
|
52
52
|
export declare function tokensMatch(received: string, expected: string): boolean;
|
|
53
|
-
/**
|
|
54
|
-
* @deprecated 3e — superseded by {@link loadRbacTokens} (read + admin split).
|
|
55
|
-
* Kept only until every caller migrates; do NOT add new callers. Resolves the
|
|
56
|
-
* legacy single `httpToken` (env-less) for back-compat shims.
|
|
57
|
-
*/
|
|
58
|
-
export declare function loadOrGenerateHttpToken(opts: {
|
|
59
|
-
bearerRequired: boolean;
|
|
60
|
-
load?: () => PersistedConfig;
|
|
61
|
-
save?: (cfg: PersistedConfig) => void;
|
|
62
|
-
}): string | null;
|
|
63
53
|
/** Resolved RBAC tokens for the daemon HTTP surface (3e). */
|
|
64
54
|
export interface RbacTokens {
|
|
65
55
|
/** T1 read token (env > config.json > auto-gen), or `null` when none + not required. */
|
|
66
56
|
readToken: string | null;
|
|
67
57
|
/** T1+T2+T3 admin token — ENV-VAR-ONLY, or `null` when unset (→ 503 on T≥2). */
|
|
68
58
|
adminToken: string | null;
|
|
69
|
-
/**
|
|
70
|
-
* True when a LEGACY `httpToken` (no `readToken`) was adopted as the read token.
|
|
71
|
-
* The daemon emits a one-time startup warning so the operator sets an admin token.
|
|
72
|
-
*/
|
|
73
|
-
legacyMigrated: boolean;
|
|
74
59
|
}
|
|
75
60
|
/** Admin token — ENV-VAR-ONLY (never config.json/disk, never auto-generated). */
|
|
76
61
|
export declare function loadAdminToken(env?: NodeJS.ProcessEnv): string | null;
|
|
77
62
|
/**
|
|
78
63
|
* Read token (T1). Priority: env `AGENT_TEMPO_HTTP_READ_TOKEN` > config.json
|
|
79
|
-
* `readToken` >
|
|
80
|
-
*
|
|
64
|
+
* `readToken` > auto-generate when bearer mode is required (persisted as
|
|
65
|
+
* `readToken`).
|
|
81
66
|
*/
|
|
82
67
|
export declare function loadReadToken(opts: {
|
|
83
68
|
bearerRequired: boolean;
|
|
84
69
|
env?: NodeJS.ProcessEnv;
|
|
85
70
|
load?: () => PersistedConfig;
|
|
86
71
|
save?: (cfg: PersistedConfig) => void;
|
|
87
|
-
}):
|
|
88
|
-
token: string | null;
|
|
89
|
-
legacy: boolean;
|
|
90
|
-
};
|
|
72
|
+
}): string | null;
|
|
91
73
|
/** Load both RBAC tokens. The daemon calls this once at startup. */
|
|
92
74
|
export declare function loadRbacTokens(opts: {
|
|
93
75
|
bearerRequired: boolean;
|
package/dist/http/auth.js
CHANGED
|
@@ -38,7 +38,6 @@ exports.originHost = originHost;
|
|
|
38
38
|
exports.bearerRequired = bearerRequired;
|
|
39
39
|
exports.extractBearerToken = extractBearerToken;
|
|
40
40
|
exports.tokensMatch = tokensMatch;
|
|
41
|
-
exports.loadOrGenerateHttpToken = loadOrGenerateHttpToken;
|
|
42
41
|
exports.loadAdminToken = loadAdminToken;
|
|
43
42
|
exports.loadReadToken = loadReadToken;
|
|
44
43
|
exports.loadRbacTokens = loadRbacTokens;
|
|
@@ -63,9 +62,10 @@ exports.requireTier = requireTier;
|
|
|
63
62
|
* by reverse proxies, supervisord, and the TUI bootstrap state machine.
|
|
64
63
|
*
|
|
65
64
|
* **Token storage** (§3.1) — `~/.agent-tempo/config.json` field
|
|
66
|
-
* `
|
|
65
|
+
* `readToken` (T1). Auto-generated on first daemon boot when bearer mode is
|
|
67
66
|
* required and no token is set: `crypto.randomBytes(32).toString('base64url')`,
|
|
68
67
|
* 0600 on POSIX. Rotation = delete the field; next daemon boot regenerates.
|
|
68
|
+
* The admin token (T2/T3) is env-var-only (`AGENT_TEMPO_HTTP_ADMIN_TOKEN`).
|
|
69
69
|
*/
|
|
70
70
|
const crypto = __importStar(require("crypto"));
|
|
71
71
|
const config_1 = require("../config");
|
|
@@ -156,24 +156,6 @@ function tokensMatch(received, expected) {
|
|
|
156
156
|
const b = Buffer.from(expected, 'utf8');
|
|
157
157
|
return crypto.timingSafeEqual(a, b);
|
|
158
158
|
}
|
|
159
|
-
/**
|
|
160
|
-
* @deprecated 3e — superseded by {@link loadRbacTokens} (read + admin split).
|
|
161
|
-
* Kept only until every caller migrates; do NOT add new callers. Resolves the
|
|
162
|
-
* legacy single `httpToken` (env-less) for back-compat shims.
|
|
163
|
-
*/
|
|
164
|
-
function loadOrGenerateHttpToken(opts) {
|
|
165
|
-
const load = opts.load ?? config_1.loadConfigFile;
|
|
166
|
-
const save = opts.save ?? config_1.saveConfigFile;
|
|
167
|
-
const cfg = load();
|
|
168
|
-
if (cfg.httpToken && typeof cfg.httpToken === 'string' && cfg.httpToken.length > 0) {
|
|
169
|
-
return cfg.httpToken;
|
|
170
|
-
}
|
|
171
|
-
if (!opts.bearerRequired)
|
|
172
|
-
return null;
|
|
173
|
-
const token = crypto.randomBytes(32).toString('base64url');
|
|
174
|
-
save({ ...cfg, httpToken: token });
|
|
175
|
-
return token;
|
|
176
|
-
}
|
|
177
159
|
/** Admin token — ENV-VAR-ONLY (never config.json/disk, never auto-generated). */
|
|
178
160
|
function loadAdminToken(env = process.env) {
|
|
179
161
|
const t = env[config_1.ENV.HTTP_ADMIN_TOKEN];
|
|
@@ -181,8 +163,8 @@ function loadAdminToken(env = process.env) {
|
|
|
181
163
|
}
|
|
182
164
|
/**
|
|
183
165
|
* Read token (T1). Priority: env `AGENT_TEMPO_HTTP_READ_TOKEN` > config.json
|
|
184
|
-
* `readToken` >
|
|
185
|
-
*
|
|
166
|
+
* `readToken` > auto-generate when bearer mode is required (persisted as
|
|
167
|
+
* `readToken`).
|
|
186
168
|
*/
|
|
187
169
|
function loadReadToken(opts) {
|
|
188
170
|
const env = opts.env ?? process.env;
|
|
@@ -190,24 +172,21 @@ function loadReadToken(opts) {
|
|
|
190
172
|
const save = opts.save ?? config_1.saveConfigFile;
|
|
191
173
|
const envTok = env[config_1.ENV.HTTP_READ_TOKEN];
|
|
192
174
|
if (envTok && envTok.length > 0)
|
|
193
|
-
return
|
|
175
|
+
return envTok;
|
|
194
176
|
const cfg = load();
|
|
195
177
|
if (cfg.readToken && cfg.readToken.length > 0)
|
|
196
|
-
return
|
|
197
|
-
// LEGACY: a pre-3e single `httpToken` becomes the READ token (T1) — NOT admin.
|
|
198
|
-
if (cfg.httpToken && cfg.httpToken.length > 0)
|
|
199
|
-
return { token: cfg.httpToken, legacy: true };
|
|
178
|
+
return cfg.readToken;
|
|
200
179
|
if (!opts.bearerRequired)
|
|
201
|
-
return
|
|
180
|
+
return null;
|
|
202
181
|
const token = crypto.randomBytes(32).toString('base64url');
|
|
203
182
|
save({ ...cfg, readToken: token });
|
|
204
|
-
return
|
|
183
|
+
return token;
|
|
205
184
|
}
|
|
206
185
|
/** Load both RBAC tokens. The daemon calls this once at startup. */
|
|
207
186
|
function loadRbacTokens(opts) {
|
|
208
|
-
const
|
|
187
|
+
const readToken = loadReadToken(opts);
|
|
209
188
|
const adminToken = loadAdminToken(opts.env);
|
|
210
|
-
return { readToken, adminToken
|
|
189
|
+
return { readToken, adminToken };
|
|
211
190
|
}
|
|
212
191
|
/**
|
|
213
192
|
* Granted tier for a presented bearer, given the RBAC tokens (timing-safe).
|