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
|
@@ -47,6 +47,29 @@ export interface HealthV1 {
|
|
|
47
47
|
* semantics. Optional so legacy clients ignoring the field don't break.
|
|
48
48
|
*/
|
|
49
49
|
memory?: MemoryUsageV1;
|
|
50
|
+
/**
|
|
51
|
+
* #886 slice 1 — daemon-side nondeterminism alarm state. Present when the
|
|
52
|
+
* daemon installed the alarm (always, in 2.0+); a non-zero `count` means a
|
|
53
|
+
* nondeterminism / determinism-violation flap was observed since boot (e.g.
|
|
54
|
+
* a 2.0 worker replaying a 1.x history, or a code/bundle skew). Lets external
|
|
55
|
+
* monitors and the dashboard poll the alarm without scraping `daemon.log`.
|
|
56
|
+
* Optional so pre-#886 clients (and non-daemon health responders) ignore it.
|
|
57
|
+
*/
|
|
58
|
+
nondeterminism?: NondeterminismAlarmV1;
|
|
59
|
+
}
|
|
60
|
+
/** #886 — `/v1/health` shape of the nondeterminism alarm snapshot. */
|
|
61
|
+
export interface NondeterminismAlarmV1 {
|
|
62
|
+
/** Total nondeterminism records seen since daemon boot (0 = healthy). */
|
|
63
|
+
count: number;
|
|
64
|
+
/** ISO timestamp of the first hit; absent when `count === 0`. */
|
|
65
|
+
firstSeenAt?: string;
|
|
66
|
+
/** ISO timestamp of the most recent hit; absent when `count === 0`. */
|
|
67
|
+
lastSeenAt?: string;
|
|
68
|
+
/** Most-recent samples (capped, newest last) — `{ at, detail }`. */
|
|
69
|
+
recent: Array<{
|
|
70
|
+
at: string;
|
|
71
|
+
detail: string;
|
|
72
|
+
}>;
|
|
50
73
|
}
|
|
51
74
|
/** Snapshot of `process.memoryUsage()` at request time. All values in bytes. */
|
|
52
75
|
export interface MemoryUsageV1 {
|
|
@@ -208,6 +231,16 @@ export interface PlayerSummaryV1 {
|
|
|
208
231
|
contextTokens?: number;
|
|
209
232
|
/** 3c Tier-1 coarse — context usage as a percentage of the model window. Additive. */
|
|
210
233
|
contextPercent?: number;
|
|
234
|
+
/**
|
|
235
|
+
* #886 slice 2 — `true` when the daemon's observation scan could only produce
|
|
236
|
+
* a DEGRADED row for this player: the session workflow was listed but its
|
|
237
|
+
* metadata extraction failed, so the non-identity fields (part/workDir/phase/…)
|
|
238
|
+
* are best-effort blanks. Lets the dashboard/board render an "uncertain" badge
|
|
239
|
+
* instead of dropping the player, which would read as a departure and cause
|
|
240
|
+
* roster flapping (contra #777). Additive + optional per the §6 stability rule;
|
|
241
|
+
* absent on every healthy row.
|
|
242
|
+
*/
|
|
243
|
+
degraded?: boolean;
|
|
211
244
|
}
|
|
212
245
|
/**
|
|
213
246
|
* The eventId token used in the `id:` line of the SSE frame and as the
|
|
@@ -242,10 +275,11 @@ export type EventIdToken = string;
|
|
|
242
275
|
* - `'stale'` — matches a host with `freshness === 'stale'`
|
|
243
276
|
* - `'missing'` — `preferredHost` is null OR no matching host record
|
|
244
277
|
*
|
|
245
|
-
* `migrateCommand` is the
|
|
246
|
-
* own session on the migrate target
|
|
247
|
-
*
|
|
248
|
-
* `
|
|
278
|
+
* `migrateCommand` is the `/migrate` slash-command the operator pastes into
|
|
279
|
+
* their own session on the migrate target (the command-center board; the Ink
|
|
280
|
+
* TUI that originally handled it was removed in #789). `--yes-steal=` (NOT
|
|
281
|
+
* `--confirm-steal-from-host`) is the actual flag the `/migrate` handler
|
|
282
|
+
* accepts. When `preferredHost` is null the
|
|
249
283
|
* command targets the local host and includes the steal guard pre-filled
|
|
250
284
|
* with the last-known host (or a literal `'(unknown)'` when even that is
|
|
251
285
|
* 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
|
@@ -72,6 +72,10 @@ const action_counters_1 = require("../utils/action-counters");
|
|
|
72
72
|
const snapshot_1 = require("./snapshot");
|
|
73
73
|
const orphans_1 = require("./orphans");
|
|
74
74
|
const sse_handler_1 = require("./sse-handler");
|
|
75
|
+
// #886 slice 1 — surface the daemon nondeterminism alarm on /v1/health. The
|
|
76
|
+
// alarm core is Temporal-VALUE-free, so this import doesn't pull the worker
|
|
77
|
+
// runtime into the HTTP layer.
|
|
78
|
+
const nondeterminism_alarm_1 = require("../observability/nondeterminism-alarm");
|
|
75
79
|
const log = (...args) => console.error(`[agent-tempo:http ${new Date().toISOString()}]`, ...args);
|
|
76
80
|
/** Default bind addr per SSE-PROTOCOL.md §1. */
|
|
77
81
|
exports.DEFAULT_BIND_ADDR = '127.0.0.1';
|
|
@@ -103,42 +107,22 @@ async function startHttpServer(opts) {
|
|
|
103
107
|
// generation now so the daemon doesn't crash mid-request when the first
|
|
104
108
|
// bearer-required call shows up.
|
|
105
109
|
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
|
-
}
|
|
110
|
+
// 3e RBAC token resolution. The READ token (T1) comes from the explicit
|
|
111
|
+
// `readToken` option (tests) or env/config/auto-gen; the ADMIN token is
|
|
112
|
+
// env-var-only. loopback bind ⇒ no bearer required ⇒ tokens may be null.
|
|
113
|
+
const readToken = opts.readToken !== undefined
|
|
114
|
+
? opts.readToken
|
|
115
|
+
: (0, auth_1.loadReadToken)({ bearerRequired: !bindIsLoopback });
|
|
124
116
|
const adminToken = opts.adminToken ?? (0, auth_1.loadAdminToken)();
|
|
125
117
|
if (!bindIsLoopback && !readToken) {
|
|
126
118
|
throw new Error('Bearer token required for non-loopback bind but none configured. ' +
|
|
127
119
|
'Set AGENT_TEMPO_HTTP_READ_TOKEN (or readToken in ~/.agent-tempo/config.json), ' +
|
|
128
120
|
'or unset AGENT_TEMPO_HTTP_BIND.');
|
|
129
121
|
}
|
|
130
|
-
// 3e MD-E — one-time startup
|
|
122
|
+
// 3e MD-E — one-time startup warning (non-blocking).
|
|
131
123
|
//
|
|
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.
|
|
124
|
+
// Plaintext-bearer exposure: binding to a non-loopback address serves the
|
|
125
|
+
// bearer token over cleartext HTTP. Suppressible, never blocking.
|
|
142
126
|
if (!bindIsLoopback && process.env[config_1.ENV.TLS_ACKNOWLEDGED] !== '1') {
|
|
143
127
|
log(`WARNING: binding to non-loopback ${bindAddr} serves the bearer token over ` +
|
|
144
128
|
'plaintext HTTP. Terminate TLS at a reverse proxy, or tunnel via SSH/Tailscale. ' +
|
|
@@ -433,6 +417,37 @@ async function handle(req, res, ctx) {
|
|
|
433
417
|
}
|
|
434
418
|
return (0, coat_check_1.handleCoatCheckGet)(res, ctx.client, ensemble, ticket);
|
|
435
419
|
}
|
|
420
|
+
// #742 — per-player attachment-info read (`/attachment-info` board command).
|
|
421
|
+
// 2-segment GET; MUST be matched BEFORE the generic writeMatch below, which
|
|
422
|
+
// would otherwise 404 `attachment-info` as an unknown write action. The
|
|
423
|
+
// player is a `?playerId=` query param (the path stays ensemble-scoped).
|
|
424
|
+
const attachmentInfoMatch = pathname.match(/^\/v1\/ensembles\/([^/]+)\/attachment-info$/);
|
|
425
|
+
if (attachmentInfoMatch) {
|
|
426
|
+
const ensemble = decodeURIComponent(attachmentInfoMatch[1]);
|
|
427
|
+
if (!gateTier(1))
|
|
428
|
+
return; // L3 — read (Tier 1).
|
|
429
|
+
if (method !== 'GET') {
|
|
430
|
+
return (0, responses_1.errorResponse)(res, 405, { error: 'method-not-allowed' }, { Allow: 'GET, OPTIONS' });
|
|
431
|
+
}
|
|
432
|
+
const playerId = url.searchParams.get('playerId');
|
|
433
|
+
if (!playerId)
|
|
434
|
+
return (0, responses_1.errorResponse)(res, 400, { error: 'missing-field', field: 'playerId' });
|
|
435
|
+
return handleAttachmentInfo(res, ctx, ensemble, playerId);
|
|
436
|
+
}
|
|
437
|
+
// #742 — active-schedule listing (`/schedule list` board command). 2-segment
|
|
438
|
+
// GET; same ordering rationale as attachment-info (precede writeMatch). The
|
|
439
|
+
// schedule WRITE path is the `unschedule` write action below + the MCP
|
|
440
|
+
// `schedule` tool for create (no thin-shim create method on the client).
|
|
441
|
+
const schedulesMatch = pathname.match(/^\/v1\/ensembles\/([^/]+)\/schedules$/);
|
|
442
|
+
if (schedulesMatch) {
|
|
443
|
+
const ensemble = decodeURIComponent(schedulesMatch[1]);
|
|
444
|
+
if (!gateTier(1))
|
|
445
|
+
return; // L3 — read (Tier 1).
|
|
446
|
+
if (method !== 'GET') {
|
|
447
|
+
return (0, responses_1.errorResponse)(res, 405, { error: 'method-not-allowed' }, { Allow: 'GET, OPTIONS' });
|
|
448
|
+
}
|
|
449
|
+
return handleSchedules(res, ctx, ensemble);
|
|
450
|
+
}
|
|
436
451
|
// Write surface (PR-7a of #340) — POST `/v1/ensembles/:ensemble/<action>`
|
|
437
452
|
// Match BEFORE the GET-only method gate; everything else (POST to a
|
|
438
453
|
// read endpoint, GET to a write endpoint) flows into the 405 fallback
|
|
@@ -695,6 +710,12 @@ function handleHealth(res, ctx) {
|
|
|
695
710
|
external: mem.external,
|
|
696
711
|
arrayBuffers: mem.arrayBuffers,
|
|
697
712
|
},
|
|
713
|
+
// #886 slice 1 — nondeterminism alarm snapshot (present when the daemon
|
|
714
|
+
// installed the alarm; `count > 0` signals an observed flap since boot).
|
|
715
|
+
...(() => {
|
|
716
|
+
const snap = (0, nondeterminism_alarm_1.getGlobalNondeterminismAlarm)()?.snapshot();
|
|
717
|
+
return snap ? { nondeterminism: snap } : {};
|
|
718
|
+
})(),
|
|
698
719
|
};
|
|
699
720
|
// Best-effort ensemble count — soft-fail to 0 rather than 500ing on a
|
|
700
721
|
// healthcheck. `/v1/health` MUST stay reachable even when Temporal is
|
|
@@ -718,6 +739,35 @@ async function handleHosts(res, ctx) {
|
|
|
718
739
|
const hosts = await ctx.client.listHosts();
|
|
719
740
|
(0, responses_1.jsonResponse)(res, 200, hosts);
|
|
720
741
|
}
|
|
742
|
+
/**
|
|
743
|
+
* #742 — a player's V2 attachment-lifecycle snapshot. Thin shim over
|
|
744
|
+
* `client.attachmentInfo`; the command-center `/attachment-info` board command
|
|
745
|
+
* renders the payload via the shared `formatAttachmentInfoForDisplay`. A 404 is
|
|
746
|
+
* surfaced when the player's session can't be resolved (it throws "No session
|
|
747
|
+
* found …", mirroring the recall not-found contract).
|
|
748
|
+
*/
|
|
749
|
+
async function handleAttachmentInfo(res, ctx, ensemble, playerId) {
|
|
750
|
+
try {
|
|
751
|
+
const info = await ctx.client.attachmentInfo(ensemble, playerId);
|
|
752
|
+
(0, responses_1.jsonResponse)(res, 200, info);
|
|
753
|
+
}
|
|
754
|
+
catch (err) {
|
|
755
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
756
|
+
if (/no session found|not found/i.test(message)) {
|
|
757
|
+
return (0, responses_1.errorResponse)(res, 404, { error: 'session-not-found', ensemble, playerId, detail: message });
|
|
758
|
+
}
|
|
759
|
+
throw err;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* #742 — active-schedule listing. Thin shim over `client.getSchedules` (which
|
|
764
|
+
* already degrades to `[]` when no scheduler is running). The command-center
|
|
765
|
+
* `/schedule [list]` board command renders the entries.
|
|
766
|
+
*/
|
|
767
|
+
async function handleSchedules(res, ctx, ensemble) {
|
|
768
|
+
const schedules = await ctx.client.getSchedules(ensemble);
|
|
769
|
+
(0, responses_1.jsonResponse)(res, 200, schedules);
|
|
770
|
+
}
|
|
721
771
|
/**
|
|
722
772
|
* #753 — daemon-process per-source Temporal action counters. Pure in-memory
|
|
723
773
|
* read (zero Temporal calls); adapter and Pi processes self-report via their
|
package/dist/http/snapshot.js
CHANGED
|
@@ -108,6 +108,9 @@ function toPlayerSummaryV1(p, wireMeta = null) {
|
|
|
108
108
|
...(wireMeta?.coarse?.currentTool !== undefined ? { currentTool: wireMeta.coarse.currentTool } : {}),
|
|
109
109
|
...(wireMeta?.coarse?.contextTokens !== undefined ? { contextTokens: wireMeta.coarse.contextTokens } : {}),
|
|
110
110
|
...(wireMeta?.coarse?.contextPercent !== undefined ? { contextPercent: wireMeta.coarse.contextPercent } : {}),
|
|
111
|
+
// #886 slice 2 — carry the degraded/uncertain flag onto the wire so the
|
|
112
|
+
// dashboard/board can badge the player instead of dropping it (anti-flap).
|
|
113
|
+
...(p.degraded ? { degraded: true } : {}),
|
|
111
114
|
};
|
|
112
115
|
}
|
|
113
116
|
/**
|
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
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nondeterminism alarm (#886 slice 1).
|
|
3
|
+
*
|
|
4
|
+
* The #801 incident produced **57 workflow-task failures in 3 minutes with
|
|
5
|
+
* ZERO operator signal** — a nondeterminism flap (a 2.0 worker replaying a
|
|
6
|
+
* 1.x-recorded history, or a code/bundle skew) surfaces only as Temporal's
|
|
7
|
+
* own `WARN`-level Core log line, buried in `daemon.log` among normal chatter.
|
|
8
|
+
* Nothing NAMED the flap, counted it, or raised its severity.
|
|
9
|
+
*
|
|
10
|
+
* This module gives the daemon a tiny, process-global counter that:
|
|
11
|
+
* 1. Intercepts Temporal Runtime log records (via {@link wrapLoggerWithAlarm},
|
|
12
|
+
* installed in the daemon's `Runtime.install({ logger })`).
|
|
13
|
+
* 2. Classifies nondeterminism / determinism-violation records
|
|
14
|
+
* ({@link isNondeterminismLog}) — wording-tolerant, like the SA-preflight
|
|
15
|
+
* marker set, so a Core/SDK phrasing change can't silently disarm it.
|
|
16
|
+
* 3. PROMOTES each hit to a prominent, greppable `[agent-tempo:ALARM]` line
|
|
17
|
+
* with a running count — so a flap is named the instant it starts.
|
|
18
|
+
* 4. Keeps a small rolling snapshot ({@link NondeterminismAlarm.snapshot})
|
|
19
|
+
* surfaced on `GET /v1/health` so external monitors / the dashboard can
|
|
20
|
+
* poll the alarm state without scraping logs.
|
|
21
|
+
*
|
|
22
|
+
* **Temporal-VALUE-free by design:** this module imports only the `Logger`
|
|
23
|
+
* TYPE from `@temporalio/common` (erased at compile). The worker `Runtime` /
|
|
24
|
+
* `DefaultLogger` VALUES are imported by the daemon, which wraps a real logger
|
|
25
|
+
* with {@link wrapLoggerWithAlarm} and installs it. That keeps the HTTP layer
|
|
26
|
+
* (`/v1/health`) able to read {@link getGlobalNondeterminismAlarm} without
|
|
27
|
+
* pulling `@temporalio/worker` into the HTTP module graph.
|
|
28
|
+
*
|
|
29
|
+
* NOTE: daemon-side observability code — NOT workflow code — so `Date`/clocks
|
|
30
|
+
* are fine here; `now` is injected purely for deterministic unit tests.
|
|
31
|
+
*/
|
|
32
|
+
import type { Logger, LogMetadata } from '@temporalio/common';
|
|
33
|
+
/**
|
|
34
|
+
* Substrings (matched case-insensitively against the log message) that mark a
|
|
35
|
+
* nondeterminism / determinism-violation record. A SET rather than one phrase
|
|
36
|
+
* because the exact wording varies across Core (Rust) and SDK (JS) sources and
|
|
37
|
+
* versions — `DeterminismViolationError` surfaces "Replay failed with a
|
|
38
|
+
* nondeterminism error", Core forwards a `WARN` referencing nondeterminism, and
|
|
39
|
+
* Temporal surfaces the incident class under the `TMPRL1100` code. Matching a
|
|
40
|
+
* set keeps the alarm armed across phrasing drift (same rationale as
|
|
41
|
+
* `UNREGISTERED_SA_MARKERS` in `sa-preflight.ts`).
|
|
42
|
+
*/
|
|
43
|
+
export declare const NONDETERMINISM_MARKERS: readonly string[];
|
|
44
|
+
/** A single recorded nondeterminism hit (most-recent-N kept by the alarm). */
|
|
45
|
+
export interface NondeterminismSample {
|
|
46
|
+
/** ISO timestamp of the hit. */
|
|
47
|
+
at: string;
|
|
48
|
+
/** Best-effort detail: workflow type / id from log meta + a message snippet. */
|
|
49
|
+
detail: string;
|
|
50
|
+
}
|
|
51
|
+
/** Pollable alarm state — embedded in `GET /v1/health` (`HealthV1.nondeterminism`). */
|
|
52
|
+
export interface NondeterminismAlarmSnapshot {
|
|
53
|
+
/** Total nondeterminism records seen since daemon boot. */
|
|
54
|
+
count: number;
|
|
55
|
+
/** ISO timestamp of the first hit, or `undefined` when count === 0. */
|
|
56
|
+
firstSeenAt?: string;
|
|
57
|
+
/** ISO timestamp of the most recent hit, or `undefined` when count === 0. */
|
|
58
|
+
lastSeenAt?: string;
|
|
59
|
+
/** Most-recent samples (capped), newest last. */
|
|
60
|
+
recent: NondeterminismSample[];
|
|
61
|
+
}
|
|
62
|
+
export interface NondeterminismAlarmOpts {
|
|
63
|
+
/** Injectable clock (epoch ms) — defaults to `Date.now`. Test seam only. */
|
|
64
|
+
now?: () => number;
|
|
65
|
+
/**
|
|
66
|
+
* Promotion sink — invoked on EVERY hit with the running count + sample, so
|
|
67
|
+
* the daemon can emit the prominent `[agent-tempo:ALARM]` line. Defaults to a
|
|
68
|
+
* no-op; the daemon passes a `console.error`-backed promoter. Kept injectable
|
|
69
|
+
* so unit tests can assert promotion without capturing stderr.
|
|
70
|
+
*/
|
|
71
|
+
onHit?: (count: number, sample: NondeterminismSample) => void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Process-global nondeterminism counter. One per daemon. Records hits, promotes
|
|
75
|
+
* each, and exposes a rolling snapshot.
|
|
76
|
+
*/
|
|
77
|
+
export declare class NondeterminismAlarm {
|
|
78
|
+
private _count;
|
|
79
|
+
private _firstSeenAt?;
|
|
80
|
+
private _lastSeenAt?;
|
|
81
|
+
private readonly _recent;
|
|
82
|
+
private readonly now;
|
|
83
|
+
private readonly onHit;
|
|
84
|
+
constructor(opts?: NondeterminismAlarmOpts);
|
|
85
|
+
/** Record one nondeterminism hit from a log record. */
|
|
86
|
+
record(message: string, meta?: LogMetadata): void;
|
|
87
|
+
/** Current count (total hits since boot). */
|
|
88
|
+
get count(): number;
|
|
89
|
+
/** Immutable snapshot for `/v1/health`. */
|
|
90
|
+
snapshot(): NondeterminismAlarmSnapshot;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* True when a log record (its message) signals a nondeterminism / determinism
|
|
94
|
+
* violation. Case-insensitive substring match over {@link NONDETERMINISM_MARKERS}.
|
|
95
|
+
*/
|
|
96
|
+
export declare function isNondeterminismLog(message: string, _meta?: LogMetadata): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Wrap a base {@link Logger} so every record still flows to `base`, but any
|
|
99
|
+
* `WARN`/`ERROR` record matching {@link isNondeterminismLog} also feeds the
|
|
100
|
+
* alarm. Transparent: trace/debug/info pass straight through; warn/error/log are
|
|
101
|
+
* classified first, then forwarded unchanged.
|
|
102
|
+
*
|
|
103
|
+
* Only `WARN`/`ERROR` are inspected — a nondeterminism failure is always logged
|
|
104
|
+
* at one of those levels, and ignoring lower levels avoids a hot classifier on
|
|
105
|
+
* the (high-volume) debug/trace path.
|
|
106
|
+
*/
|
|
107
|
+
export declare function wrapLoggerWithAlarm(base: Logger, alarm: NondeterminismAlarm): Logger;
|
|
108
|
+
/** Set the process-global alarm (daemon boot only). */
|
|
109
|
+
export declare function setGlobalNondeterminismAlarm(alarm: NondeterminismAlarm): void;
|
|
110
|
+
/** Read the process-global alarm, or `undefined` if not installed (e.g. tests, non-daemon). */
|
|
111
|
+
export declare function getGlobalNondeterminismAlarm(): NondeterminismAlarm | undefined;
|
|
112
|
+
/** Test seam — reset the singleton between unit tests. */
|
|
113
|
+
export declare function __resetGlobalNondeterminismAlarmForTests(): void;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NondeterminismAlarm = exports.NONDETERMINISM_MARKERS = void 0;
|
|
4
|
+
exports.isNondeterminismLog = isNondeterminismLog;
|
|
5
|
+
exports.wrapLoggerWithAlarm = wrapLoggerWithAlarm;
|
|
6
|
+
exports.setGlobalNondeterminismAlarm = setGlobalNondeterminismAlarm;
|
|
7
|
+
exports.getGlobalNondeterminismAlarm = getGlobalNondeterminismAlarm;
|
|
8
|
+
exports.__resetGlobalNondeterminismAlarmForTests = __resetGlobalNondeterminismAlarmForTests;
|
|
9
|
+
/**
|
|
10
|
+
* Substrings (matched case-insensitively against the log message) that mark a
|
|
11
|
+
* nondeterminism / determinism-violation record. A SET rather than one phrase
|
|
12
|
+
* because the exact wording varies across Core (Rust) and SDK (JS) sources and
|
|
13
|
+
* versions — `DeterminismViolationError` surfaces "Replay failed with a
|
|
14
|
+
* nondeterminism error", Core forwards a `WARN` referencing nondeterminism, and
|
|
15
|
+
* Temporal surfaces the incident class under the `TMPRL1100` code. Matching a
|
|
16
|
+
* set keeps the alarm armed across phrasing drift (same rationale as
|
|
17
|
+
* `UNREGISTERED_SA_MARKERS` in `sa-preflight.ts`).
|
|
18
|
+
*/
|
|
19
|
+
exports.NONDETERMINISM_MARKERS = Object.freeze([
|
|
20
|
+
'nondetermin',
|
|
21
|
+
'non-determin',
|
|
22
|
+
'determinismviolation',
|
|
23
|
+
'tmprl1100',
|
|
24
|
+
]);
|
|
25
|
+
/** Max recent samples retained for the `/v1/health` snapshot. */
|
|
26
|
+
const RECENT_CAP = 10;
|
|
27
|
+
/** Max message characters kept per sample (logs can be long stack-y blobs). */
|
|
28
|
+
const DETAIL_SNIPPET_MAX = 200;
|
|
29
|
+
/**
|
|
30
|
+
* Process-global nondeterminism counter. One per daemon. Records hits, promotes
|
|
31
|
+
* each, and exposes a rolling snapshot.
|
|
32
|
+
*/
|
|
33
|
+
class NondeterminismAlarm {
|
|
34
|
+
_count = 0;
|
|
35
|
+
_firstSeenAt;
|
|
36
|
+
_lastSeenAt;
|
|
37
|
+
_recent = [];
|
|
38
|
+
now;
|
|
39
|
+
onHit;
|
|
40
|
+
constructor(opts = {}) {
|
|
41
|
+
this.now = opts.now ?? (() => Date.now());
|
|
42
|
+
this.onHit = opts.onHit ?? (() => { });
|
|
43
|
+
}
|
|
44
|
+
/** Record one nondeterminism hit from a log record. */
|
|
45
|
+
record(message, meta) {
|
|
46
|
+
const at = new Date(this.now()).toISOString();
|
|
47
|
+
this._count += 1;
|
|
48
|
+
if (this._firstSeenAt === undefined)
|
|
49
|
+
this._firstSeenAt = at;
|
|
50
|
+
this._lastSeenAt = at;
|
|
51
|
+
const sample = { at, detail: buildDetail(message, meta) };
|
|
52
|
+
this._recent.push(sample);
|
|
53
|
+
if (this._recent.length > RECENT_CAP)
|
|
54
|
+
this._recent.shift();
|
|
55
|
+
// Promote — fire the prominent, greppable alarm line (the whole point: the
|
|
56
|
+
// #801 flap produced zero operator signal). Never let a throwing sink eat
|
|
57
|
+
// the count.
|
|
58
|
+
try {
|
|
59
|
+
this.onHit(this._count, sample);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* a broken promoter must not disarm the counter */
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Current count (total hits since boot). */
|
|
66
|
+
get count() {
|
|
67
|
+
return this._count;
|
|
68
|
+
}
|
|
69
|
+
/** Immutable snapshot for `/v1/health`. */
|
|
70
|
+
snapshot() {
|
|
71
|
+
return {
|
|
72
|
+
count: this._count,
|
|
73
|
+
...(this._firstSeenAt !== undefined ? { firstSeenAt: this._firstSeenAt } : {}),
|
|
74
|
+
...(this._lastSeenAt !== undefined ? { lastSeenAt: this._lastSeenAt } : {}),
|
|
75
|
+
recent: this._recent.slice(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.NondeterminismAlarm = NondeterminismAlarm;
|
|
80
|
+
/** Build the best-effort sample detail from log meta + a message snippet. */
|
|
81
|
+
function buildDetail(message, meta) {
|
|
82
|
+
const bits = [];
|
|
83
|
+
const wfType = meta?.['workflowType'];
|
|
84
|
+
const wfId = meta?.['workflowId'];
|
|
85
|
+
const runId = meta?.['runId'];
|
|
86
|
+
if (typeof wfType === 'string')
|
|
87
|
+
bits.push(`workflowType=${wfType}`);
|
|
88
|
+
if (typeof wfId === 'string')
|
|
89
|
+
bits.push(`workflowId=${wfId}`);
|
|
90
|
+
if (typeof runId === 'string')
|
|
91
|
+
bits.push(`runId=${runId}`);
|
|
92
|
+
const snippet = (message ?? '').slice(0, DETAIL_SNIPPET_MAX);
|
|
93
|
+
bits.push(snippet);
|
|
94
|
+
return bits.join(' · ');
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* True when a log record (its message) signals a nondeterminism / determinism
|
|
98
|
+
* violation. Case-insensitive substring match over {@link NONDETERMINISM_MARKERS}.
|
|
99
|
+
*/
|
|
100
|
+
function isNondeterminismLog(message, _meta) {
|
|
101
|
+
if (!message)
|
|
102
|
+
return false;
|
|
103
|
+
const m = message.toLowerCase();
|
|
104
|
+
return exports.NONDETERMINISM_MARKERS.some((marker) => m.includes(marker));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Wrap a base {@link Logger} so every record still flows to `base`, but any
|
|
108
|
+
* `WARN`/`ERROR` record matching {@link isNondeterminismLog} also feeds the
|
|
109
|
+
* alarm. Transparent: trace/debug/info pass straight through; warn/error/log are
|
|
110
|
+
* classified first, then forwarded unchanged.
|
|
111
|
+
*
|
|
112
|
+
* Only `WARN`/`ERROR` are inspected — a nondeterminism failure is always logged
|
|
113
|
+
* at one of those levels, and ignoring lower levels avoids a hot classifier on
|
|
114
|
+
* the (high-volume) debug/trace path.
|
|
115
|
+
*/
|
|
116
|
+
function wrapLoggerWithAlarm(base, alarm) {
|
|
117
|
+
const consider = (level, message, meta) => {
|
|
118
|
+
if ((level === 'WARN' || level === 'ERROR') && isNondeterminismLog(message, meta)) {
|
|
119
|
+
alarm.record(message, meta);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
log(level, message, meta) {
|
|
124
|
+
consider(level, message, meta);
|
|
125
|
+
base.log(level, message, meta);
|
|
126
|
+
},
|
|
127
|
+
trace(message, meta) {
|
|
128
|
+
base.trace(message, meta);
|
|
129
|
+
},
|
|
130
|
+
debug(message, meta) {
|
|
131
|
+
base.debug(message, meta);
|
|
132
|
+
},
|
|
133
|
+
info(message, meta) {
|
|
134
|
+
base.info(message, meta);
|
|
135
|
+
},
|
|
136
|
+
warn(message, meta) {
|
|
137
|
+
consider('WARN', message, meta);
|
|
138
|
+
base.warn(message, meta);
|
|
139
|
+
},
|
|
140
|
+
error(message, meta) {
|
|
141
|
+
consider('ERROR', message, meta);
|
|
142
|
+
base.error(message, meta);
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// ── Process singleton ───────────────────────────────────────────────────────
|
|
147
|
+
// The daemon installs ONE alarm at boot; `/v1/health` reads it without
|
|
148
|
+
// threading a reference through the HTTP server constructor (and without
|
|
149
|
+
// importing `@temporalio/worker` into the HTTP layer).
|
|
150
|
+
let globalAlarm;
|
|
151
|
+
/** Set the process-global alarm (daemon boot only). */
|
|
152
|
+
function setGlobalNondeterminismAlarm(alarm) {
|
|
153
|
+
globalAlarm = alarm;
|
|
154
|
+
}
|
|
155
|
+
/** Read the process-global alarm, or `undefined` if not installed (e.g. tests, non-daemon). */
|
|
156
|
+
function getGlobalNondeterminismAlarm() {
|
|
157
|
+
return globalAlarm;
|
|
158
|
+
}
|
|
159
|
+
/** Test seam — reset the singleton between unit tests. */
|
|
160
|
+
function __resetGlobalNondeterminismAlarmForTests() {
|
|
161
|
+
globalAlarm = undefined;
|
|
162
|
+
}
|
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
|