agent-tempo 1.7.0 → 2.0.0-beta.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 +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 +0 -10
- package/dist/activities/outbox.js +4 -2
- package/dist/adapters/base.js +3 -0
- 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 +7 -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 +0 -13
- package/dist/constants.d.ts +22 -0
- package/dist/constants.js +23 -1
- package/dist/daemon.js +52 -0
- package/dist/http/auth.d.ts +3 -21
- package/dist/http/auth.js +10 -31
- package/dist/http/event-types.d.ts +5 -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 +69 -29
- package/dist/http/writes.d.ts +1 -1
- package/dist/http/writes.js +25 -2
- 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/types.d.ts +15 -6
- 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/search-attributes.d.ts +27 -18
- package/dist/utils/search-attributes.js +39 -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 +44 -110
- package/dist/workflows/signals.d.ts +9 -1
- package/dist/workflows/signals.js +8 -6
- package/package.json +1 -6
- package/workflow-bundle.js +239 -182
- 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/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).
|
|
@@ -242,10 +242,11 @@ export type EventIdToken = string;
|
|
|
242
242
|
* - `'stale'` — matches a host with `freshness === 'stale'`
|
|
243
243
|
* - `'missing'` — `preferredHost` is null OR no matching host record
|
|
244
244
|
*
|
|
245
|
-
* `migrateCommand` is the
|
|
246
|
-
* own session on the migrate target
|
|
247
|
-
*
|
|
248
|
-
* `
|
|
245
|
+
* `migrateCommand` is the `/migrate` slash-command the operator pastes into
|
|
246
|
+
* their own session on the migrate target (the command-center board; the Ink
|
|
247
|
+
* TUI that originally handled it was removed in #789). `--yes-steal=` (NOT
|
|
248
|
+
* `--confirm-steal-from-host`) is the actual flag the `/migrate` handler
|
|
249
|
+
* accepts. When `preferredHost` is null the
|
|
249
250
|
* command targets the local host and includes the steal guard pre-filled
|
|
250
251
|
* with the last-known host (or a literal `'(unknown)'` when even that is
|
|
251
252
|
* missing — the operator must edit it before submit).
|
package/dist/http/orphans.d.ts
CHANGED
|
@@ -30,9 +30,9 @@ import type { TempoClient } from '../client/interface';
|
|
|
30
30
|
import type { HostInfo } from '../types';
|
|
31
31
|
import type { OrphanCandidate } from '../reconcile/orphans';
|
|
32
32
|
/**
|
|
33
|
-
* Render the
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* Render the `/migrate` slash command the operator pastes into their own
|
|
34
|
+
* session to recover an orphan (the command-center board; the Ink TUI that
|
|
35
|
+
* originally handled it was removed in #789). Wording:
|
|
36
36
|
* - positional `<playerId> <host>`
|
|
37
37
|
* - flag form `--yes-steal=<currentHost>` (NOT `--confirm-steal-from-host`)
|
|
38
38
|
*
|
package/dist/http/orphans.js
CHANGED
|
@@ -6,9 +6,9 @@ exports.buildOrphansResponse = buildOrphansResponse;
|
|
|
6
6
|
exports.handleOrphans = handleOrphans;
|
|
7
7
|
const responses_1 = require("./responses");
|
|
8
8
|
/**
|
|
9
|
-
* Render the
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Render the `/migrate` slash command the operator pastes into their own
|
|
10
|
+
* session to recover an orphan (the command-center board; the Ink TUI that
|
|
11
|
+
* originally handled it was removed in #789). Wording:
|
|
12
12
|
* - positional `<playerId> <host>`
|
|
13
13
|
* - flag form `--yes-steal=<currentHost>` (NOT `--confirm-steal-from-host`)
|
|
14
14
|
*
|
package/dist/http/server.d.ts
CHANGED
|
@@ -61,11 +61,6 @@ export interface HttpServerOptions {
|
|
|
61
61
|
* don't fight production daemons running on the same machine.
|
|
62
62
|
*/
|
|
63
63
|
portFilePath?: string;
|
|
64
|
-
/**
|
|
65
|
-
* @deprecated 3e — back-compat alias for {@link readToken}. A single injected
|
|
66
|
-
* bearer is treated as the READ token (T1). Prefer `readToken`/`adminToken`.
|
|
67
|
-
*/
|
|
68
|
-
httpToken?: string;
|
|
69
64
|
/** 3e — inject the read-tier (T1) token directly (tests). Overrides config/env. */
|
|
70
65
|
readToken?: string;
|
|
71
66
|
/** 3e — inject the admin (T1+T2+T3) token directly (tests). Overrides env. */
|
package/dist/http/server.js
CHANGED
|
@@ -103,42 +103,22 @@ async function startHttpServer(opts) {
|
|
|
103
103
|
// generation now so the daemon doesn't crash mid-request when the first
|
|
104
104
|
// bearer-required call shows up.
|
|
105
105
|
const bindIsLoopback = (0, auth_1.isLoopbackBindAddr)(bindAddr);
|
|
106
|
-
// 3e RBAC token resolution.
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (opts.readToken !== undefined) {
|
|
113
|
-
readToken = opts.readToken;
|
|
114
|
-
}
|
|
115
|
-
else if (opts.httpToken !== undefined) {
|
|
116
|
-
// Back-compat: a single injected bearer is treated as the READ token (T1).
|
|
117
|
-
readToken = opts.httpToken;
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
const loaded = (0, auth_1.loadReadToken)({ bearerRequired: !bindIsLoopback });
|
|
121
|
-
readToken = loaded.token;
|
|
122
|
-
legacyMigrated = loaded.legacy;
|
|
123
|
-
}
|
|
106
|
+
// 3e RBAC token resolution. The READ token (T1) comes from the explicit
|
|
107
|
+
// `readToken` option (tests) or env/config/auto-gen; the ADMIN token is
|
|
108
|
+
// env-var-only. loopback bind ⇒ no bearer required ⇒ tokens may be null.
|
|
109
|
+
const readToken = opts.readToken !== undefined
|
|
110
|
+
? opts.readToken
|
|
111
|
+
: (0, auth_1.loadReadToken)({ bearerRequired: !bindIsLoopback });
|
|
124
112
|
const adminToken = opts.adminToken ?? (0, auth_1.loadAdminToken)();
|
|
125
113
|
if (!bindIsLoopback && !readToken) {
|
|
126
114
|
throw new Error('Bearer token required for non-loopback bind but none configured. ' +
|
|
127
115
|
'Set AGENT_TEMPO_HTTP_READ_TOKEN (or readToken in ~/.agent-tempo/config.json), ' +
|
|
128
116
|
'or unset AGENT_TEMPO_HTTP_BIND.');
|
|
129
117
|
}
|
|
130
|
-
// 3e MD-E — one-time startup
|
|
118
|
+
// 3e MD-E — one-time startup warning (non-blocking).
|
|
131
119
|
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
// (all Tier ≥ 2) will 503 until an admin token is set.
|
|
135
|
-
if (legacyMigrated && adminToken === null) {
|
|
136
|
-
log('NOTICE: adopted legacy config.json `httpToken` as the read-tier token. ' +
|
|
137
|
-
'Writes and the inner-tail are admin-only and will return ' +
|
|
138
|
-
'503 until you set AGENT_TEMPO_HTTP_ADMIN_TOKEN (env-var only).');
|
|
139
|
-
}
|
|
140
|
-
// (2) Plaintext-bearer exposure: binding to a non-loopback address serves the
|
|
141
|
-
// bearer token over cleartext HTTP. Suppressible, never blocking.
|
|
120
|
+
// Plaintext-bearer exposure: binding to a non-loopback address serves the
|
|
121
|
+
// bearer token over cleartext HTTP. Suppressible, never blocking.
|
|
142
122
|
if (!bindIsLoopback && process.env[config_1.ENV.TLS_ACKNOWLEDGED] !== '1') {
|
|
143
123
|
log(`WARNING: binding to non-loopback ${bindAddr} serves the bearer token over ` +
|
|
144
124
|
'plaintext HTTP. Terminate TLS at a reverse proxy, or tunnel via SSH/Tailscale. ' +
|
|
@@ -433,6 +413,37 @@ async function handle(req, res, ctx) {
|
|
|
433
413
|
}
|
|
434
414
|
return (0, coat_check_1.handleCoatCheckGet)(res, ctx.client, ensemble, ticket);
|
|
435
415
|
}
|
|
416
|
+
// #742 — per-player attachment-info read (`/attachment-info` board command).
|
|
417
|
+
// 2-segment GET; MUST be matched BEFORE the generic writeMatch below, which
|
|
418
|
+
// would otherwise 404 `attachment-info` as an unknown write action. The
|
|
419
|
+
// player is a `?playerId=` query param (the path stays ensemble-scoped).
|
|
420
|
+
const attachmentInfoMatch = pathname.match(/^\/v1\/ensembles\/([^/]+)\/attachment-info$/);
|
|
421
|
+
if (attachmentInfoMatch) {
|
|
422
|
+
const ensemble = decodeURIComponent(attachmentInfoMatch[1]);
|
|
423
|
+
if (!gateTier(1))
|
|
424
|
+
return; // L3 — read (Tier 1).
|
|
425
|
+
if (method !== 'GET') {
|
|
426
|
+
return (0, responses_1.errorResponse)(res, 405, { error: 'method-not-allowed' }, { Allow: 'GET, OPTIONS' });
|
|
427
|
+
}
|
|
428
|
+
const playerId = url.searchParams.get('playerId');
|
|
429
|
+
if (!playerId)
|
|
430
|
+
return (0, responses_1.errorResponse)(res, 400, { error: 'missing-field', field: 'playerId' });
|
|
431
|
+
return handleAttachmentInfo(res, ctx, ensemble, playerId);
|
|
432
|
+
}
|
|
433
|
+
// #742 — active-schedule listing (`/schedule list` board command). 2-segment
|
|
434
|
+
// GET; same ordering rationale as attachment-info (precede writeMatch). The
|
|
435
|
+
// schedule WRITE path is the `unschedule` write action below + the MCP
|
|
436
|
+
// `schedule` tool for create (no thin-shim create method on the client).
|
|
437
|
+
const schedulesMatch = pathname.match(/^\/v1\/ensembles\/([^/]+)\/schedules$/);
|
|
438
|
+
if (schedulesMatch) {
|
|
439
|
+
const ensemble = decodeURIComponent(schedulesMatch[1]);
|
|
440
|
+
if (!gateTier(1))
|
|
441
|
+
return; // L3 — read (Tier 1).
|
|
442
|
+
if (method !== 'GET') {
|
|
443
|
+
return (0, responses_1.errorResponse)(res, 405, { error: 'method-not-allowed' }, { Allow: 'GET, OPTIONS' });
|
|
444
|
+
}
|
|
445
|
+
return handleSchedules(res, ctx, ensemble);
|
|
446
|
+
}
|
|
436
447
|
// Write surface (PR-7a of #340) — POST `/v1/ensembles/:ensemble/<action>`
|
|
437
448
|
// Match BEFORE the GET-only method gate; everything else (POST to a
|
|
438
449
|
// read endpoint, GET to a write endpoint) flows into the 405 fallback
|
|
@@ -718,6 +729,35 @@ async function handleHosts(res, ctx) {
|
|
|
718
729
|
const hosts = await ctx.client.listHosts();
|
|
719
730
|
(0, responses_1.jsonResponse)(res, 200, hosts);
|
|
720
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* #742 — a player's V2 attachment-lifecycle snapshot. Thin shim over
|
|
734
|
+
* `client.attachmentInfo`; the command-center `/attachment-info` board command
|
|
735
|
+
* renders the payload via the shared `formatAttachmentInfoForDisplay`. A 404 is
|
|
736
|
+
* surfaced when the player's session can't be resolved (it throws "No session
|
|
737
|
+
* found …", mirroring the recall not-found contract).
|
|
738
|
+
*/
|
|
739
|
+
async function handleAttachmentInfo(res, ctx, ensemble, playerId) {
|
|
740
|
+
try {
|
|
741
|
+
const info = await ctx.client.attachmentInfo(ensemble, playerId);
|
|
742
|
+
(0, responses_1.jsonResponse)(res, 200, info);
|
|
743
|
+
}
|
|
744
|
+
catch (err) {
|
|
745
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
746
|
+
if (/no session found|not found/i.test(message)) {
|
|
747
|
+
return (0, responses_1.errorResponse)(res, 404, { error: 'session-not-found', ensemble, playerId, detail: message });
|
|
748
|
+
}
|
|
749
|
+
throw err;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* #742 — active-schedule listing. Thin shim over `client.getSchedules` (which
|
|
754
|
+
* already degrades to `[]` when no scheduler is running). The command-center
|
|
755
|
+
* `/schedule [list]` board command renders the entries.
|
|
756
|
+
*/
|
|
757
|
+
async function handleSchedules(res, ctx, ensemble) {
|
|
758
|
+
const schedules = await ctx.client.getSchedules(ensemble);
|
|
759
|
+
(0, responses_1.jsonResponse)(res, 200, schedules);
|
|
760
|
+
}
|
|
721
761
|
/**
|
|
722
762
|
* #753 — daemon-process per-source Temporal action counters. Pure in-memory
|
|
723
763
|
* read (zero Temporal calls); adapter and Pi processes self-report via their
|
package/dist/http/writes.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export { WRITE_BODY_MAX };
|
|
|
42
42
|
* mutations. Bodies are uniform `{ playerId, reason? }` (plus per-action
|
|
43
43
|
* extras); the ensemble lives in the URL.
|
|
44
44
|
*/
|
|
45
|
-
export declare const WRITE_ACTIONS: readonly ["cue", "pause", "play", "release", "recruit", "restart", "reset", "destroy", "detach", "recall", "shutdown"];
|
|
45
|
+
export declare const WRITE_ACTIONS: readonly ["cue", "pause", "play", "release", "recruit", "restart", "reset", "destroy", "detach", "recall", "unschedule", "shutdown"];
|
|
46
46
|
export type WriteAction = (typeof WRITE_ACTIONS)[number];
|
|
47
47
|
/** Type guard — narrows an arbitrary string to a known `WriteAction`. */
|
|
48
48
|
export declare function isWriteAction(s: string): s is WriteAction;
|
package/dist/http/writes.js
CHANGED
|
@@ -31,6 +31,7 @@ exports.WRITE_ACTIONS = [
|
|
|
31
31
|
'destroy',
|
|
32
32
|
'detach',
|
|
33
33
|
'recall',
|
|
34
|
+
'unschedule',
|
|
34
35
|
'shutdown',
|
|
35
36
|
];
|
|
36
37
|
/** Type guard — narrows an arbitrary string to a known `WriteAction`. */
|
|
@@ -69,6 +70,7 @@ async function handleWriteRoute(req, res, client, ensemble, action) {
|
|
|
69
70
|
case 'destroy': return await handleDestroy(res, client, ensemble, body);
|
|
70
71
|
case 'detach': return await handleDetach(res, client, ensemble, body);
|
|
71
72
|
case 'recall': return await handleRecall(res, client, ensemble, body);
|
|
73
|
+
case 'unschedule': return await handleUnschedule(res, client, ensemble, body);
|
|
72
74
|
case 'shutdown': return await handleShutdown(res, client, ensemble, body);
|
|
73
75
|
}
|
|
74
76
|
}
|
|
@@ -242,12 +244,33 @@ async function handleRecall(res, client, ensemble, body) {
|
|
|
242
244
|
// The dashboard's `RecallResult` consumer expects a count, not the raw
|
|
243
245
|
// arrays — projecting `received` + `sent` lengths here keeps the wire
|
|
244
246
|
// payload tight and avoids leaking inbox + sent-history shape into the
|
|
245
|
-
// browser-side type. Callers wanting the full timeline
|
|
246
|
-
//
|
|
247
|
+
// browser-side type. Callers wanting the full timeline pass `{ full: true }`
|
|
248
|
+
// (the command-center `/recall` board command, #742) or use the MCP `recall`
|
|
249
|
+
// tool / TempoClient method directly.
|
|
247
250
|
const result = await client.recall(ensemble, playerId);
|
|
251
|
+
if (body.full === true) {
|
|
252
|
+
// Full timeline — the operator board feeds this through the shared
|
|
253
|
+
// `buildTimeline`/`formatRecall` helpers, same as the MCP tool. Kept behind
|
|
254
|
+
// an opt-in flag so the count-only default stays the dashboard's shape.
|
|
255
|
+
return (0, responses_1.jsonResponse)(res, 200, { ok: true, ensemble, playerId, received: result.received, sent: result.sent });
|
|
256
|
+
}
|
|
248
257
|
const messages = result.received.length + result.sent.length;
|
|
249
258
|
(0, responses_1.jsonResponse)(res, 200, { ok: true, ensemble, playerId, messages });
|
|
250
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Cancel a named schedule (#742 — the command-center `/unschedule` &
|
|
262
|
+
* `/schedule delete`). Thin shim over `client.cancelSchedule`. Body is
|
|
263
|
+
* `{ name }` (the schedule key), distinct from the per-player `{ playerId }`
|
|
264
|
+
* shape — schedules are ensemble-scoped, keyed by name. 200 (the cancel IS
|
|
265
|
+
* the operation, not a queued effect).
|
|
266
|
+
*/
|
|
267
|
+
async function handleUnschedule(res, client, ensemble, body) {
|
|
268
|
+
const name = (0, body_1.stringField)(body, 'name');
|
|
269
|
+
if (!name)
|
|
270
|
+
return (0, responses_1.errorResponse)(res, 400, { error: 'missing-field', field: 'name' });
|
|
271
|
+
await client.cancelSchedule(ensemble, name);
|
|
272
|
+
(0, responses_1.jsonResponse)(res, 200, { ok: true, ensemble, name });
|
|
273
|
+
}
|
|
251
274
|
/**
|
|
252
275
|
* Ensemble teardown (#700 P1) — the HTTP verb the command-center `/ensemble-down`
|
|
253
276
|
* needs (the CLI `down` + the `shutdown` MCP tool cover this, but neither was
|
package/dist/palette/index.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure palette-input classification + filtering helpers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* same filtering semantics, same quote-aware tokenizer.
|
|
4
|
+
* Used by the dashboard (`dashboard/src/components/chat/`) chat-input
|
|
5
|
+
* autocomplete machinery — prefix detection, filtering semantics, and the
|
|
6
|
+
* quote-aware tokenizer.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
* TUI parity bundle)
|
|
11
|
-
*
|
|
8
|
+
* Originally extracted from the former `src/tui/commands.ts` in #471/#472
|
|
9
|
+
* (dashboard ↔ TUI parity bundle); the Ink TUI was deleted in #789, so the
|
|
10
|
+
* dashboard is now the sole consumer. The dashboard imports directly from
|
|
12
11
|
* `agent-tempo/palette` (Vite alias `agent-tempo` → `../src`).
|
|
13
12
|
*
|
|
14
13
|
* **No imports allowed.** Keep this module dependency-free so it
|
package/dist/palette/index.js
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Pure palette-input classification + filtering helpers.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* same filtering semantics, same quote-aware tokenizer.
|
|
5
|
+
* Used by the dashboard (`dashboard/src/components/chat/`) chat-input
|
|
6
|
+
* autocomplete machinery — prefix detection, filtering semantics, and the
|
|
7
|
+
* quote-aware tokenizer.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
* TUI parity bundle)
|
|
12
|
-
*
|
|
9
|
+
* Originally extracted from the former `src/tui/commands.ts` in #471/#472
|
|
10
|
+
* (dashboard ↔ TUI parity bundle); the Ink TUI was deleted in #789, so the
|
|
11
|
+
* dashboard is now the sole consumer. The dashboard imports directly from
|
|
13
12
|
* `agent-tempo/palette` (Vite alias `agent-tempo` → `../src`).
|
|
14
13
|
*
|
|
15
14
|
* **No imports allowed.** Keep this module dependency-free so it
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnswerEntry } from '../../types';
|
|
1
|
+
import type { AnswerEntry, HostInfo, AttachmentInfo, ScheduleEntry, Message, SentMessage } from '../../types';
|
|
2
2
|
import type { EnsembleSummary } from '../../client/interface';
|
|
3
3
|
/** Env var holding the daemon admin (T3) token (writes + inner tail). */
|
|
4
4
|
export declare const ADMIN_TOKEN_ENV = "AGENT_TEMPO_HTTP_ADMIN_TOKEN";
|
|
@@ -104,9 +104,75 @@ export declare class MissionControlActions {
|
|
|
104
104
|
ok: false;
|
|
105
105
|
error: string;
|
|
106
106
|
}>;
|
|
107
|
+
/**
|
|
108
|
+
* List connected hosts (`GET /v1/hosts`, Tier-1 read) — the cluster host
|
|
109
|
+
* registry + freshness + capability profile, the same payload the dashboard
|
|
110
|
+
* `/hosts` view and the CLI `agent-tempo hosts` consume. NOT ensemble-scoped
|
|
111
|
+
* (the host registry is cluster-global), so it ignores the bound ensemble.
|
|
112
|
+
*/
|
|
113
|
+
listHosts(): Promise<{
|
|
114
|
+
ok: true;
|
|
115
|
+
hosts: HostInfo[];
|
|
116
|
+
} | {
|
|
117
|
+
ok: false;
|
|
118
|
+
error: string;
|
|
119
|
+
}>;
|
|
120
|
+
/**
|
|
121
|
+
* A player's V2 attachment-lifecycle snapshot (`GET /v1/ensembles/:e/attachment-info`,
|
|
122
|
+
* Tier-1 read). Thin shim over `client.attachmentInfo` — the same payload the
|
|
123
|
+
* MCP `attachment_info` tool and CLI `attachment-info` render via the shared
|
|
124
|
+
* `formatAttachmentInfoForDisplay`. The player is a `?playerId=` query param.
|
|
125
|
+
*/
|
|
126
|
+
attachmentInfo(playerId: string): Promise<{
|
|
127
|
+
ok: true;
|
|
128
|
+
info: AttachmentInfo;
|
|
129
|
+
} | {
|
|
130
|
+
ok: false;
|
|
131
|
+
error: string;
|
|
132
|
+
}>;
|
|
133
|
+
/**
|
|
134
|
+
* A player's full message timeline (received + sent). Hits the existing
|
|
135
|
+
* `POST /v1/ensembles/:e/recall` route with `{ full: true }` so the daemon
|
|
136
|
+
* returns the raw `{ received, sent }` arrays (the default count-only shape
|
|
137
|
+
* stays for the dashboard). Thin shim over `client.recall`; the caller feeds
|
|
138
|
+
* the result through the shared `buildTimeline`/`formatRecall` helpers. POST
|
|
139
|
+
* (not GET) because recall groups with the per-player action surface (T2).
|
|
140
|
+
*/
|
|
141
|
+
recall(playerId: string): Promise<{
|
|
142
|
+
ok: true;
|
|
143
|
+
received: Message[];
|
|
144
|
+
sent: SentMessage[];
|
|
145
|
+
} | {
|
|
146
|
+
ok: false;
|
|
147
|
+
error: string;
|
|
148
|
+
}>;
|
|
149
|
+
/**
|
|
150
|
+
* Active schedules for the bound ensemble (`GET /v1/ensembles/:e/schedules`,
|
|
151
|
+
* Tier-1 read). Thin shim over `client.getSchedules` — the same `ScheduleEntry[]`
|
|
152
|
+
* the MCP `schedules` tool and CLI render. Empty when no scheduler is running.
|
|
153
|
+
*/
|
|
154
|
+
listSchedules(): Promise<{
|
|
155
|
+
ok: true;
|
|
156
|
+
schedules: ScheduleEntry[];
|
|
157
|
+
} | {
|
|
158
|
+
ok: false;
|
|
159
|
+
error: string;
|
|
160
|
+
}>;
|
|
107
161
|
cue(to: string, message: string): Promise<ActionResult>;
|
|
108
162
|
pause(): Promise<ActionResult>;
|
|
109
163
|
play(release?: boolean): Promise<ActionResult>;
|
|
164
|
+
/**
|
|
165
|
+
* Free HELD players via the dedicated `POST /v1/ensembles/:e/release` route
|
|
166
|
+
* (#742 gap 8 — `/go`). Optional `playerId` releases ONE held player; omitted
|
|
167
|
+
* releases all held in the ensemble. Distinct from `play(release:true)`, which
|
|
168
|
+
* ALSO clears the PAUSE axis — `/go` frees holds without touching pause.
|
|
169
|
+
*/
|
|
170
|
+
release(playerId?: string): Promise<ActionResult>;
|
|
171
|
+
/**
|
|
172
|
+
* Cancel a named schedule (`POST /v1/ensembles/:e/unschedule`, #742 gap —
|
|
173
|
+
* `/unschedule` & `/schedule delete`). Thin shim over `client.cancelSchedule`.
|
|
174
|
+
*/
|
|
175
|
+
unschedule(name: string): Promise<ActionResult>;
|
|
110
176
|
restart(playerId: string, reason?: string): Promise<ActionResult>;
|
|
111
177
|
destroy(playerId: string, reason?: string): Promise<ActionResult>;
|
|
112
178
|
reset(playerId: string, reason?: string): Promise<ActionResult>;
|
|
@@ -211,6 +211,51 @@ class MissionControlActions {
|
|
|
211
211
|
const res = await this.getJson('/v1/ensembles');
|
|
212
212
|
return res.ok ? { ok: true, ensembles: res.data } : res;
|
|
213
213
|
}
|
|
214
|
+
// ── Read surface: hosts (#742 gap 5) ──
|
|
215
|
+
/**
|
|
216
|
+
* List connected hosts (`GET /v1/hosts`, Tier-1 read) — the cluster host
|
|
217
|
+
* registry + freshness + capability profile, the same payload the dashboard
|
|
218
|
+
* `/hosts` view and the CLI `agent-tempo hosts` consume. NOT ensemble-scoped
|
|
219
|
+
* (the host registry is cluster-global), so it ignores the bound ensemble.
|
|
220
|
+
*/
|
|
221
|
+
async listHosts() {
|
|
222
|
+
const res = await this.getJson('/v1/hosts');
|
|
223
|
+
return res.ok ? { ok: true, hosts: res.data } : res;
|
|
224
|
+
}
|
|
225
|
+
// ── Read surface: attachment-info (#742 gap — /attachment-info) ──
|
|
226
|
+
/**
|
|
227
|
+
* A player's V2 attachment-lifecycle snapshot (`GET /v1/ensembles/:e/attachment-info`,
|
|
228
|
+
* Tier-1 read). Thin shim over `client.attachmentInfo` — the same payload the
|
|
229
|
+
* MCP `attachment_info` tool and CLI `attachment-info` render via the shared
|
|
230
|
+
* `formatAttachmentInfoForDisplay`. The player is a `?playerId=` query param.
|
|
231
|
+
*/
|
|
232
|
+
async attachmentInfo(playerId) {
|
|
233
|
+
const res = await this.getJson(`/v1/ensembles/${this.ens()}/attachment-info?playerId=${encodeURIComponent(playerId)}`);
|
|
234
|
+
return res.ok ? { ok: true, info: res.data } : res;
|
|
235
|
+
}
|
|
236
|
+
// ── Read surface: recall (#742 gap — /recall) ──
|
|
237
|
+
/**
|
|
238
|
+
* A player's full message timeline (received + sent). Hits the existing
|
|
239
|
+
* `POST /v1/ensembles/:e/recall` route with `{ full: true }` so the daemon
|
|
240
|
+
* returns the raw `{ received, sent }` arrays (the default count-only shape
|
|
241
|
+
* stays for the dashboard). Thin shim over `client.recall`; the caller feeds
|
|
242
|
+
* the result through the shared `buildTimeline`/`formatRecall` helpers. POST
|
|
243
|
+
* (not GET) because recall groups with the per-player action surface (T2).
|
|
244
|
+
*/
|
|
245
|
+
async recall(playerId) {
|
|
246
|
+
const res = await this.postJson(`/v1/ensembles/${this.ens()}/recall`, { playerId, full: true });
|
|
247
|
+
return res.ok ? { ok: true, received: res.data.received, sent: res.data.sent } : res;
|
|
248
|
+
}
|
|
249
|
+
// ── Read surface: schedules (#742 gap — /schedule list) ──
|
|
250
|
+
/**
|
|
251
|
+
* Active schedules for the bound ensemble (`GET /v1/ensembles/:e/schedules`,
|
|
252
|
+
* Tier-1 read). Thin shim over `client.getSchedules` — the same `ScheduleEntry[]`
|
|
253
|
+
* the MCP `schedules` tool and CLI render. Empty when no scheduler is running.
|
|
254
|
+
*/
|
|
255
|
+
async listSchedules() {
|
|
256
|
+
const res = await this.getJson(`/v1/ensembles/${this.ens()}/schedules`);
|
|
257
|
+
return res.ok ? { ok: true, schedules: res.data } : res;
|
|
258
|
+
}
|
|
214
259
|
// ── Ensemble write surface (T2) ──
|
|
215
260
|
cue(to, message) {
|
|
216
261
|
// #822 — parse the deliverability hint so the board warns on a detached/gone
|
|
@@ -223,6 +268,22 @@ class MissionControlActions {
|
|
|
223
268
|
play(release) {
|
|
224
269
|
return this.post(`/v1/ensembles/${this.ens()}/play`, release ? { release } : {});
|
|
225
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Free HELD players via the dedicated `POST /v1/ensembles/:e/release` route
|
|
273
|
+
* (#742 gap 8 — `/go`). Optional `playerId` releases ONE held player; omitted
|
|
274
|
+
* releases all held in the ensemble. Distinct from `play(release:true)`, which
|
|
275
|
+
* ALSO clears the PAUSE axis — `/go` frees holds without touching pause.
|
|
276
|
+
*/
|
|
277
|
+
release(playerId) {
|
|
278
|
+
return this.post(`/v1/ensembles/${this.ens()}/release`, playerId ? { playerId } : {});
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Cancel a named schedule (`POST /v1/ensembles/:e/unschedule`, #742 gap —
|
|
282
|
+
* `/unschedule` & `/schedule delete`). Thin shim over `client.cancelSchedule`.
|
|
283
|
+
*/
|
|
284
|
+
unschedule(name) {
|
|
285
|
+
return this.post(`/v1/ensembles/${this.ens()}/unschedule`, { name });
|
|
286
|
+
}
|
|
226
287
|
restart(playerId, reason) {
|
|
227
288
|
return this.post(`/v1/ensembles/${this.ens()}/restart`, { playerId, ...(reason ? { reason } : {}) });
|
|
228
289
|
}
|