agent-tempo 1.3.1 → 1.4.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 +39 -5
- package/README.md +6 -2
- package/dashboard/dist/assets/{index-D6Xyje_n.js → index-jmYe6rmS.js} +2 -2
- package/dashboard/dist/assets/index-jmYe6rmS.js.map +1 -0
- package/dashboard/dist/index.html +1 -1
- package/dashboard/package.json +1 -1
- package/dist/activities/outbox.d.ts +30 -1
- package/dist/activities/outbox.js +96 -3
- package/dist/adapters/base.js +5 -0
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +7 -0
- package/dist/adapters/pi/adapter.d.ts +2 -0
- package/dist/adapters/pi/adapter.js +43 -0
- package/dist/adapters/pi/index.d.ts +16 -0
- package/dist/adapters/pi/index.js +10 -0
- package/dist/client/core.js +9 -2
- package/dist/client/interface.d.ts +6 -0
- package/dist/config.d.ts +79 -0
- package/dist/config.js +74 -0
- package/dist/daemon.js +32 -1
- package/dist/http/aggregate.d.ts +22 -1
- package/dist/http/aggregate.js +41 -0
- package/dist/http/auth.d.ts +94 -8
- package/dist/http/auth.js +93 -9
- package/dist/http/body.d.ts +4 -1
- package/dist/http/body.js +6 -3
- package/dist/http/event-bus.js +1 -0
- package/dist/http/event-types.d.ts +34 -2
- package/dist/http/event-types.js +1 -0
- package/dist/http/gate-audit.d.ts +12 -0
- package/dist/http/gate-audit.js +95 -0
- package/dist/http/gate-registry.d.ts +167 -0
- package/dist/http/gate-registry.js +163 -0
- package/dist/http/gate-routes.d.ts +48 -0
- package/dist/http/gate-routes.js +102 -0
- package/dist/http/ingest-registry.d.ts +30 -0
- package/dist/http/ingest-registry.js +108 -0
- package/dist/http/inner-loop-routes.d.ts +66 -0
- package/dist/http/inner-loop-routes.js +182 -0
- package/dist/http/inner-loop.d.ts +92 -0
- package/dist/http/inner-loop.js +155 -0
- package/dist/http/server.d.ts +38 -3
- package/dist/http/server.js +211 -6
- package/dist/http/snapshot.d.ts +6 -0
- package/dist/http/snapshot.js +6 -0
- package/dist/pi/cue-pump.d.ts +61 -0
- package/dist/pi/cue-pump.js +95 -0
- package/dist/pi/extension.d.ts +45 -0
- package/dist/pi/extension.js +407 -0
- package/dist/pi/gate-client.d.ts +54 -0
- package/dist/pi/gate-client.js +136 -0
- package/dist/pi/headless.d.ts +85 -0
- package/dist/pi/headless.js +250 -0
- package/dist/pi/index.d.ts +28 -0
- package/dist/pi/index.js +43 -0
- package/dist/pi/inner-loop-client.d.ts +67 -0
- package/dist/pi/inner-loop-client.js +164 -0
- package/dist/pi/inner-loop-publisher.d.ts +187 -0
- package/dist/pi/inner-loop-publisher.js +236 -0
- package/dist/pi/lazy-proxy.d.ts +37 -0
- package/dist/pi/lazy-proxy.js +55 -0
- package/dist/pi/mission-control/actions.d.ts +48 -0
- package/dist/pi/mission-control/actions.js +98 -0
- package/dist/pi/mission-control/board.d.ts +88 -0
- package/dist/pi/mission-control/board.js +141 -0
- package/dist/pi/mission-control/extension.d.ts +51 -0
- package/dist/pi/mission-control/extension.js +330 -0
- package/dist/pi/mission-control/index.d.ts +15 -0
- package/dist/pi/mission-control/index.js +32 -0
- package/dist/pi/mission-control/inner-tail.d.ts +48 -0
- package/dist/pi/mission-control/inner-tail.js +76 -0
- package/dist/pi/mission-control/pi-ui.d.ts +43 -0
- package/dist/pi/mission-control/pi-ui.js +10 -0
- package/dist/pi/mission-control/render.d.ts +6 -0
- package/dist/pi/mission-control/render.js +98 -0
- package/dist/pi/phase-driver.d.ts +74 -0
- package/dist/pi/phase-driver.js +122 -0
- package/dist/pi/pi-types.d.ts +222 -0
- package/dist/pi/pi-types.js +21 -0
- package/dist/pi/probe.d.ts +99 -0
- package/dist/pi/probe.js +179 -0
- package/dist/pi/render-tools.d.ts +17 -0
- package/dist/pi/render-tools.js +56 -0
- package/dist/pi/reset-pump.d.ts +47 -0
- package/dist/pi/reset-pump.js +85 -0
- package/dist/pi/session-seed.d.ts +74 -0
- package/dist/pi/session-seed.js +103 -0
- package/dist/pi/tool-capability.d.ts +60 -0
- package/dist/pi/tool-capability.js +156 -0
- package/dist/pi/workflow-client.d.ts +158 -0
- package/dist/pi/workflow-client.js +289 -0
- package/dist/pi/zod-to-typebox.d.ts +74 -0
- package/dist/pi/zod-to-typebox.js +191 -0
- package/dist/server-tools.d.ts +2 -0
- package/dist/server-tools.js +50 -46
- package/dist/spawn.d.ts +55 -0
- package/dist/spawn.js +72 -0
- package/dist/tools/agent-types.d.ts +2 -2
- package/dist/tools/agent-types.js +22 -17
- package/dist/tools/attachment-info.d.ts +2 -2
- package/dist/tools/attachment-info.js +38 -33
- package/dist/tools/broadcast.d.ts +2 -2
- package/dist/tools/broadcast.js +69 -64
- package/dist/tools/cancel-stage.d.ts +2 -2
- package/dist/tools/cancel-stage.js +20 -15
- package/dist/tools/clear-state.d.ts +2 -2
- package/dist/tools/clear-state.js +25 -20
- package/dist/tools/coat-check-evict.d.ts +2 -2
- package/dist/tools/coat-check-evict.js +29 -24
- package/dist/tools/coat-check-get.d.ts +2 -2
- package/dist/tools/coat-check-get.js +38 -33
- package/dist/tools/coat-check-list.d.ts +2 -2
- package/dist/tools/coat-check-list.js +48 -43
- package/dist/tools/coat-check-put.d.ts +2 -2
- package/dist/tools/coat-check-put.js +38 -33
- package/dist/tools/cue.d.ts +2 -2
- package/dist/tools/cue.js +57 -52
- package/dist/tools/descriptor.d.ts +72 -0
- package/dist/tools/descriptor.js +39 -0
- package/dist/tools/destroy.d.ts +2 -2
- package/dist/tools/destroy.js +153 -148
- package/dist/tools/ensemble.d.ts +2 -2
- package/dist/tools/ensemble.js +71 -66
- package/dist/tools/evaluate-gate.d.ts +2 -2
- package/dist/tools/evaluate-gate.js +33 -27
- package/dist/tools/fetch-state.d.ts +2 -2
- package/dist/tools/fetch-state.js +42 -37
- package/dist/tools/gates.d.ts +2 -2
- package/dist/tools/gates.js +39 -34
- package/dist/tools/hosts.d.ts +2 -2
- package/dist/tools/hosts.js +25 -20
- package/dist/tools/listen.d.ts +2 -2
- package/dist/tools/listen.js +23 -18
- package/dist/tools/load-lineup.d.ts +2 -2
- package/dist/tools/load-lineup.js +324 -319
- package/dist/tools/migrate.d.ts +2 -2
- package/dist/tools/migrate.js +45 -40
- package/dist/tools/pause.d.ts +2 -2
- package/dist/tools/pause.js +34 -29
- package/dist/tools/play.d.ts +2 -2
- package/dist/tools/play.js +53 -48
- package/dist/tools/quality-gate.d.ts +2 -2
- package/dist/tools/quality-gate.js +26 -21
- package/dist/tools/recall.d.ts +2 -2
- package/dist/tools/recall.js +32 -27
- package/dist/tools/recruit.d.ts +2 -2
- package/dist/tools/recruit.js +340 -256
- package/dist/tools/release.d.ts +2 -2
- package/dist/tools/release.js +85 -80
- package/dist/tools/report.d.ts +2 -2
- package/dist/tools/report.js +28 -23
- package/dist/tools/reset.d.ts +3 -0
- package/dist/tools/reset.js +51 -0
- package/dist/tools/restart.d.ts +2 -2
- package/dist/tools/restart.js +51 -46
- package/dist/tools/restore.d.ts +2 -2
- package/dist/tools/restore.js +76 -71
- package/dist/tools/save-lineup.d.ts +2 -2
- package/dist/tools/save-lineup.js +32 -27
- package/dist/tools/save-state.d.ts +2 -2
- package/dist/tools/save-state.js +31 -26
- package/dist/tools/schedule.d.ts +2 -2
- package/dist/tools/schedule.js +133 -128
- package/dist/tools/schedules.d.ts +2 -2
- package/dist/tools/schedules.js +41 -36
- package/dist/tools/set-ensemble-description.d.ts +2 -2
- package/dist/tools/set-ensemble-description.js +26 -21
- package/dist/tools/set-name.d.ts +2 -2
- package/dist/tools/set-name.js +38 -33
- package/dist/tools/set-part.d.ts +2 -2
- package/dist/tools/set-part.js +20 -15
- package/dist/tools/shutdown.d.ts +2 -2
- package/dist/tools/shutdown.js +39 -34
- package/dist/tools/stage.d.ts +2 -2
- package/dist/tools/stage.js +28 -23
- package/dist/tools/stages.d.ts +2 -2
- package/dist/tools/stages.js +36 -31
- package/dist/tools/unschedule.d.ts +2 -2
- package/dist/tools/unschedule.js +30 -25
- package/dist/tools/who-am-i.d.ts +2 -2
- package/dist/tools/who-am-i.js +36 -31
- package/dist/tools/worktree.d.ts +2 -2
- package/dist/tools/worktree.js +134 -129
- package/dist/tui/index.js +6 -6
- package/dist/types.d.ts +47 -2
- package/dist/types.js +1 -1
- package/dist/utils/default-part.js +1 -0
- package/dist/utils/sdk-probe.d.ts +23 -0
- package/dist/utils/sdk-probe.js +46 -7
- package/dist/worker.d.ts +3 -1
- package/dist/worker.js +6 -2
- package/dist/workflows/session.js +70 -2
- package/dist/workflows/signals.d.ts +32 -2
- package/dist/workflows/signals.js +25 -2
- package/package.json +4 -1
- package/workflow-bundle.js +97 -6
- package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
- package/dist/tools/helpers.d.ts +0 -21
- package/dist/tools/helpers.js +0 -25
package/dist/daemon.js
CHANGED
|
@@ -66,6 +66,10 @@ const config_1 = require("./config");
|
|
|
66
66
|
const dev_banner_1 = require("./cli/dev-banner");
|
|
67
67
|
const worker_1 = require("./worker");
|
|
68
68
|
const connection_1 = require("./connection");
|
|
69
|
+
const inner_loop_1 = require("./http/inner-loop");
|
|
70
|
+
const ingest_registry_1 = require("./http/ingest-registry");
|
|
71
|
+
const gate_registry_1 = require("./http/gate-registry");
|
|
72
|
+
const gate_audit_1 = require("./http/gate-audit");
|
|
69
73
|
const grpc_shutdown_guard_1 = require("./utils/grpc-shutdown-guard");
|
|
70
74
|
const daemon_1 = require("./cli/daemon");
|
|
71
75
|
const client_3 = require("./client");
|
|
@@ -828,6 +832,19 @@ async function main() {
|
|
|
828
832
|
// #94/#95 PR-2 — aggregate poll loop + per-ensemble buses. Owned by
|
|
829
833
|
// the daemon process; `close()` drains every per-ensemble bus.
|
|
830
834
|
let aggregateRunner = null;
|
|
835
|
+
// 3c Tier-2 — daemon-owned singletons shared between the Temporal worker
|
|
836
|
+
// (outbox pi-spawn mints / destroy revokes) and the HTTP server (/inner SSE
|
|
837
|
+
// + /inner/ingest validation). Both the worker and startHttpServer run in
|
|
838
|
+
// THIS process, so one instance each suffices. Constructed eagerly (no I/O)
|
|
839
|
+
// so the shutdown handler — declared just below — can drain them.
|
|
840
|
+
const innerLoop = new inner_loop_1.InnerLoopRegistry();
|
|
841
|
+
const ingestTokens = new ingest_registry_1.IngestTokenRegistry();
|
|
842
|
+
// 3d MD-G — the operator-gate registry, same daemon-owned-singleton pattern.
|
|
843
|
+
// Audit sink = the append-only JSONL writer; publishToInner = innerLoop.publish
|
|
844
|
+
// (the DI that emits gate_resolved on the player's /inner stream without a
|
|
845
|
+
// GateRegistry↔inner-loop circular import).
|
|
846
|
+
const gate = new gate_registry_1.GateRegistry((0, gate_audit_1.createGateAuditSink)(), Date.now, undefined, // default 45s auto-allow
|
|
847
|
+
(workflowId, frame) => innerLoop.publish(workflowId, frame));
|
|
831
848
|
const shutdown = () => {
|
|
832
849
|
if (shuttingDown)
|
|
833
850
|
return;
|
|
@@ -861,6 +878,12 @@ async function main() {
|
|
|
861
878
|
// sockets — preventing wasted work in the drain window.
|
|
862
879
|
aggregateRunner?.close();
|
|
863
880
|
httpServerHandle?.close().catch((err) => log('http close error (non-fatal):', err instanceof Error ? err.message : err));
|
|
881
|
+
// 3c Tier-2 — clear-all on shutdown: drop every minted ingest token (no
|
|
882
|
+
// dead token outlives the daemon) and close every open /inner subscriber
|
|
883
|
+
// (streams end cleanly rather than dangling).
|
|
884
|
+
ingestTokens.revokeAll();
|
|
885
|
+
innerLoop.close();
|
|
886
|
+
gate.clear();
|
|
864
887
|
sharedWorker?.shutdown();
|
|
865
888
|
hostWorker?.shutdown();
|
|
866
889
|
};
|
|
@@ -868,7 +891,7 @@ async function main() {
|
|
|
868
891
|
process.on('SIGINT', shutdown);
|
|
869
892
|
// Create workers (signal handlers already active via mutable refs)
|
|
870
893
|
log(`Connecting to Temporal at ${config.temporalAddress} (namespace: ${config.temporalNamespace})`);
|
|
871
|
-
const workers = await (0, worker_1.createWorkers)(config);
|
|
894
|
+
const workers = await (0, worker_1.createWorkers)(config, ingestTokens, gate);
|
|
872
895
|
sharedWorker = workers.sharedWorker;
|
|
873
896
|
hostWorker = workers.hostWorker;
|
|
874
897
|
log('Workers created — processing tasks');
|
|
@@ -953,6 +976,14 @@ async function main() {
|
|
|
953
976
|
taskQueue: config.taskQueue,
|
|
954
977
|
version: daemonVersion(),
|
|
955
978
|
aggregate: aggregateRunner,
|
|
979
|
+
// 3c Tier-2 — same singletons the worker's outbox activities use, so
|
|
980
|
+
// the operator /inner SSE reads the registry the publisher POSTs into
|
|
981
|
+
// and /inner/ingest validates against the tokens the spawn path minted.
|
|
982
|
+
innerLoop,
|
|
983
|
+
ingestTokens,
|
|
984
|
+
// 3d MD-G — the same gate registry the worker's outbox auto-disarms on
|
|
985
|
+
// detach/destroy; the HTTP server serves arm/disarm/decide + resolution.
|
|
986
|
+
gate,
|
|
956
987
|
});
|
|
957
988
|
log(`HTTP listening on http://${httpServerHandle.bindAddr}:${httpServerHandle.port}`);
|
|
958
989
|
log(`Aggregate poll loop running (bootEpoch=${bootEpoch})`);
|
package/dist/http/aggregate.d.ts
CHANGED
|
@@ -41,6 +41,21 @@ export declare class TickSkipped extends Error {
|
|
|
41
41
|
readonly name = "TickSkipped";
|
|
42
42
|
constructor(reason: string);
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* 3c Tier-1 coarse activity snapshot for a single player — the bits the
|
|
46
|
+
* aggregate diffs to decide whether to emit `player.activity`. `currentTool`
|
|
47
|
+
* is normalized to `null` when idle/absent; the context fields are `undefined`
|
|
48
|
+
* when Pi can't report usage (e.g. right after compaction).
|
|
49
|
+
*/
|
|
50
|
+
export interface PlayerCoarse {
|
|
51
|
+
currentTool: string | null;
|
|
52
|
+
contextTokens?: number;
|
|
53
|
+
contextPercent?: number;
|
|
54
|
+
}
|
|
55
|
+
/** Extract the coarse-activity tuple from a player summary, normalizing idle → null. */
|
|
56
|
+
export declare function coarseOf(p: PlayerSummaryV1): PlayerCoarse;
|
|
57
|
+
/** True when two coarse snapshots differ in any field. */
|
|
58
|
+
export declare function coarseChanged(a: PlayerCoarse | undefined, b: PlayerCoarse): boolean;
|
|
44
59
|
/** Per-ensemble tracking state across ticks. */
|
|
45
60
|
interface EnsembleTrack {
|
|
46
61
|
bus: EnsembleEventBus;
|
|
@@ -56,6 +71,12 @@ interface EnsembleTrack {
|
|
|
56
71
|
consecutiveFailures: number;
|
|
57
72
|
/** Last seen player phase, keyed by playerId. */
|
|
58
73
|
playerPhases: Map<string, string | undefined>;
|
|
74
|
+
/**
|
|
75
|
+
* 3c Tier-1 — last-seen coarse activity per playerId (currentTool + context
|
|
76
|
+
* usage). Diffed each poll to emit `player.activity` on change. Seeded on
|
|
77
|
+
* `player.added`, dropped on `player.removed` — lockstep with `playerPhases`.
|
|
78
|
+
*/
|
|
79
|
+
playerCoarse: Map<string, PlayerCoarse>;
|
|
59
80
|
/**
|
|
60
81
|
* Adapter family per playerId — used to faithfully reconstruct the
|
|
61
82
|
* prior `AggregateEnsembleSnapshot` view at tick boundaries (see #535).
|
|
@@ -175,7 +196,7 @@ export interface DiffEvent {
|
|
|
175
196
|
type: import('./event-types').SseEventKind;
|
|
176
197
|
payload: unknown;
|
|
177
198
|
}
|
|
178
|
-
export declare function diffEnsembleSnapshot(prev: AggregateEnsembleSnapshot | null, next: AggregateEnsembleSnapshot, track: Pick<EnsembleTrack, 'playerPhases' | 'playerAgentTypes' | 'flags' | 'schedulesHash' | 'chatIds' | 'chatIdOrder'>, capturedAt: string): DiffEvent[];
|
|
199
|
+
export declare function diffEnsembleSnapshot(prev: AggregateEnsembleSnapshot | null, next: AggregateEnsembleSnapshot, track: Pick<EnsembleTrack, 'playerPhases' | 'playerCoarse' | 'playerAgentTypes' | 'flags' | 'schedulesHash' | 'chatIds' | 'chatIdOrder'>, capturedAt: string): DiffEvent[];
|
|
179
200
|
/**
|
|
180
201
|
* Diff host profiles map → per-host events. Pure function.
|
|
181
202
|
*/
|
package/dist/http/aggregate.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AggregateRunner = exports.TickSkipped = exports.MAX_CONSECUTIVE_FAILURES = exports.AGGREGATE_LIST_DEADLINE_MS = exports.DEFAULT_POLL_INTERVAL_MS = void 0;
|
|
4
|
+
exports.coarseOf = coarseOf;
|
|
5
|
+
exports.coarseChanged = coarseChanged;
|
|
4
6
|
exports.canonicalize = canonicalize;
|
|
5
7
|
exports.hashOf = hashOf;
|
|
6
8
|
exports.diffEnsembleSnapshot = diffEnsembleSnapshot;
|
|
@@ -84,6 +86,21 @@ class TickSkipped extends Error {
|
|
|
84
86
|
constructor(reason) { super(reason); }
|
|
85
87
|
}
|
|
86
88
|
exports.TickSkipped = TickSkipped;
|
|
89
|
+
/** Extract the coarse-activity tuple from a player summary, normalizing idle → null. */
|
|
90
|
+
function coarseOf(p) {
|
|
91
|
+
return {
|
|
92
|
+
currentTool: p.currentTool ?? null,
|
|
93
|
+
...(p.contextTokens !== undefined ? { contextTokens: p.contextTokens } : {}),
|
|
94
|
+
...(p.contextPercent !== undefined ? { contextPercent: p.contextPercent } : {}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** True when two coarse snapshots differ in any field. */
|
|
98
|
+
function coarseChanged(a, b) {
|
|
99
|
+
return (!a ||
|
|
100
|
+
a.currentTool !== b.currentTool ||
|
|
101
|
+
a.contextTokens !== b.contextTokens ||
|
|
102
|
+
a.contextPercent !== b.contextPercent);
|
|
103
|
+
}
|
|
87
104
|
/**
|
|
88
105
|
* Stable JSON canonicalization — keys sorted, no extraneous whitespace.
|
|
89
106
|
* Used for SHA-256 diff suppression so reordered key emits don't
|
|
@@ -118,6 +135,9 @@ function diffEnsembleSnapshot(prev, next, track, capturedAt) {
|
|
|
118
135
|
if (!prevPlayers.has(p.playerId)) {
|
|
119
136
|
events.push({ type: 'player.added', payload: p });
|
|
120
137
|
track.playerPhases.set(p.playerId, p.phase);
|
|
138
|
+
// 3c — seed coarse so the first real change (not the add itself, whose
|
|
139
|
+
// payload already carries currentTool/context) emits player.activity.
|
|
140
|
+
track.playerCoarse.set(p.playerId, coarseOf(p));
|
|
121
141
|
// #535 — record the adapter family so the prior reconstruction at
|
|
122
142
|
// the next tick (aggregate.ts ~L600) carries the real agentType
|
|
123
143
|
// instead of a hardcoded `'claude'` stand-in. Treated as immutable
|
|
@@ -140,6 +160,25 @@ function diffEnsembleSnapshot(prev, next, track, capturedAt) {
|
|
|
140
160
|
});
|
|
141
161
|
track.playerPhases.set(p.playerId, p.phase);
|
|
142
162
|
}
|
|
163
|
+
// player.activity (3c Tier-1) — emit when coarse currentTool/context
|
|
164
|
+
// changes between polls. Distinct from phase_changed (phase vs activity).
|
|
165
|
+
// Source freshness ~30s (heartbeat metadata piggyback); the live, fine
|
|
166
|
+
// tail is the off-wire /inner side-channel.
|
|
167
|
+
const nextCoarse = coarseOf(p);
|
|
168
|
+
if (coarseChanged(track.playerCoarse.get(p.playerId), nextCoarse)) {
|
|
169
|
+
events.push({
|
|
170
|
+
type: 'player.activity',
|
|
171
|
+
payload: {
|
|
172
|
+
playerId: p.playerId,
|
|
173
|
+
ensemble,
|
|
174
|
+
currentTool: nextCoarse.currentTool,
|
|
175
|
+
...(nextCoarse.contextTokens !== undefined ? { contextTokens: nextCoarse.contextTokens } : {}),
|
|
176
|
+
...(nextCoarse.contextPercent !== undefined ? { contextPercent: nextCoarse.contextPercent } : {}),
|
|
177
|
+
at: capturedAt,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
track.playerCoarse.set(p.playerId, nextCoarse);
|
|
181
|
+
}
|
|
143
182
|
}
|
|
144
183
|
// player.removed — iterate prev.
|
|
145
184
|
if (prev) {
|
|
@@ -159,6 +198,7 @@ function diffEnsembleSnapshot(prev, next, track, capturedAt) {
|
|
|
159
198
|
},
|
|
160
199
|
});
|
|
161
200
|
track.playerPhases.delete(p.playerId);
|
|
201
|
+
track.playerCoarse.delete(p.playerId);
|
|
162
202
|
track.playerAgentTypes.delete(p.playerId);
|
|
163
203
|
}
|
|
164
204
|
}
|
|
@@ -377,6 +417,7 @@ class AggregateRunner {
|
|
|
377
417
|
bus,
|
|
378
418
|
consecutiveFailures: 0,
|
|
379
419
|
playerPhases: new Map(),
|
|
420
|
+
playerCoarse: new Map(),
|
|
380
421
|
playerAgentTypes: new Map(),
|
|
381
422
|
flags: null,
|
|
382
423
|
schedulesHash: null,
|
package/dist/http/auth.d.ts
CHANGED
|
@@ -51,17 +51,103 @@ export declare function extractBearerToken(authHeader: string | undefined): stri
|
|
|
51
51
|
*/
|
|
52
52
|
export declare function tokensMatch(received: string, expected: string): boolean;
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* `httpToken`, generate one (`crypto.randomBytes(32).toString('base64url')`)
|
|
58
|
-
* and persist it via `saveConfigFile` (which sets 0600 on POSIX).
|
|
59
|
-
* - When `bearerRequired` is false, return whatever is in the config
|
|
60
|
-
* without generating — operators may still want a token saved for
|
|
61
|
-
* future use, and we shouldn't write secrets the user didn't request.
|
|
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.
|
|
62
57
|
*/
|
|
63
58
|
export declare function loadOrGenerateHttpToken(opts: {
|
|
64
59
|
bearerRequired: boolean;
|
|
65
60
|
load?: () => PersistedConfig;
|
|
66
61
|
save?: (cfg: PersistedConfig) => void;
|
|
67
62
|
}): string | null;
|
|
63
|
+
/** Resolved RBAC tokens for the daemon HTTP surface (3e). */
|
|
64
|
+
export interface RbacTokens {
|
|
65
|
+
/** T1 read token (env > config.json > auto-gen), or `null` when none + not required. */
|
|
66
|
+
readToken: string | null;
|
|
67
|
+
/** T1+T2+T3 admin token — ENV-VAR-ONLY, or `null` when unset (→ 503 on T≥2). */
|
|
68
|
+
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
|
+
}
|
|
75
|
+
/** Admin token — ENV-VAR-ONLY (never config.json/disk, never auto-generated). */
|
|
76
|
+
export declare function loadAdminToken(env?: NodeJS.ProcessEnv): string | null;
|
|
77
|
+
/**
|
|
78
|
+
* Read token (T1). Priority: env `AGENT_TEMPO_HTTP_READ_TOKEN` > config.json
|
|
79
|
+
* `readToken` > LEGACY config.json `httpToken` (adopted → `legacy:true`) >
|
|
80
|
+
* auto-generate when bearer mode is required (persisted as `readToken`).
|
|
81
|
+
*/
|
|
82
|
+
export declare function loadReadToken(opts: {
|
|
83
|
+
bearerRequired: boolean;
|
|
84
|
+
env?: NodeJS.ProcessEnv;
|
|
85
|
+
load?: () => PersistedConfig;
|
|
86
|
+
save?: (cfg: PersistedConfig) => void;
|
|
87
|
+
}): {
|
|
88
|
+
token: string | null;
|
|
89
|
+
legacy: boolean;
|
|
90
|
+
};
|
|
91
|
+
/** Load both RBAC tokens. The daemon calls this once at startup. */
|
|
92
|
+
export declare function loadRbacTokens(opts: {
|
|
93
|
+
bearerRequired: boolean;
|
|
94
|
+
env?: NodeJS.ProcessEnv;
|
|
95
|
+
load?: () => PersistedConfig;
|
|
96
|
+
save?: (cfg: PersistedConfig) => void;
|
|
97
|
+
}): RbacTokens;
|
|
98
|
+
/** Access tiers (MD-E): 1 = read/observe, 2 = write/mutate, 3 = supervisory (gate/inner). */
|
|
99
|
+
export type Tier = 1 | 2 | 3;
|
|
100
|
+
/**
|
|
101
|
+
* Granted tier for a presented bearer, given the RBAC tokens (timing-safe).
|
|
102
|
+
* Admin grants the FULL ladder (3 ⊇ 2 ⊇ 1); read grants T1 only; no match → 0.
|
|
103
|
+
* There is no T2-only token — T2 and T3 are both "admin required".
|
|
104
|
+
*/
|
|
105
|
+
export declare function tierForToken(presented: string, tokens: {
|
|
106
|
+
readToken: string | null;
|
|
107
|
+
adminToken: string | null;
|
|
108
|
+
}): 0 | 1 | 3;
|
|
109
|
+
export interface TierGuardInput {
|
|
110
|
+
/** Daemon bind address — decides loopback trust. */
|
|
111
|
+
bindAddr: string;
|
|
112
|
+
/** Request `Origin` header (may be absent for a non-browser client). */
|
|
113
|
+
originHeader: string | undefined;
|
|
114
|
+
/** Request `Authorization` header. */
|
|
115
|
+
authHeader: string | undefined;
|
|
116
|
+
/** Read-tier token, or `null`. */
|
|
117
|
+
readToken: string | null;
|
|
118
|
+
/** Admin token, or `null` when unset (→ 503 on T≥2). */
|
|
119
|
+
adminToken: string | null;
|
|
120
|
+
}
|
|
121
|
+
export type TierGuardResult = {
|
|
122
|
+
ok: true;
|
|
123
|
+
} | {
|
|
124
|
+
ok: false;
|
|
125
|
+
status: 401;
|
|
126
|
+
error: 'unauthorized';
|
|
127
|
+
} | {
|
|
128
|
+
ok: false;
|
|
129
|
+
status: 403;
|
|
130
|
+
error: 'insufficient-tier';
|
|
131
|
+
detail: string;
|
|
132
|
+
} | {
|
|
133
|
+
ok: false;
|
|
134
|
+
status: 503;
|
|
135
|
+
error: 'admin-token-not-configured';
|
|
136
|
+
detail: string;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Authorization guard (3e MD-E). Assumes the shared upstream pass already settled
|
|
140
|
+
* AUTHENTICATION + CORS + the DNS-rebind/Origin defense (architect's Layer 2);
|
|
141
|
+
* this is Layer-3 authZ — it ONLY decides tier ≥ N and emits 503/403/401.
|
|
142
|
+
*
|
|
143
|
+
* Matrix (bearer mode):
|
|
144
|
+
* - loopback (`!bearerRequired`) → PASS all tiers (local-trust short-circuit).
|
|
145
|
+
* - N ≥ 2 AND adminToken unset → 503 admin-token-not-configured.
|
|
146
|
+
* - no/invalid bearer (granted 0) → 401 unauthorized.
|
|
147
|
+
* - granted < N (read token on T≥2) → 403 insufficient-tier (+ migration hint).
|
|
148
|
+
* - granted ≥ N (admin, or read on T1) → PASS.
|
|
149
|
+
*
|
|
150
|
+
* Keyed off the `Authorization` bearer only (no `Origin`/cookie requirement) so a
|
|
151
|
+
* headless Node client passes once its token validates. View-agnostic by design.
|
|
152
|
+
*/
|
|
153
|
+
export declare function requireTier(tier: Tier, input: TierGuardInput): TierGuardResult;
|
package/dist/http/auth.js
CHANGED
|
@@ -39,6 +39,11 @@ exports.bearerRequired = bearerRequired;
|
|
|
39
39
|
exports.extractBearerToken = extractBearerToken;
|
|
40
40
|
exports.tokensMatch = tokensMatch;
|
|
41
41
|
exports.loadOrGenerateHttpToken = loadOrGenerateHttpToken;
|
|
42
|
+
exports.loadAdminToken = loadAdminToken;
|
|
43
|
+
exports.loadReadToken = loadReadToken;
|
|
44
|
+
exports.loadRbacTokens = loadRbacTokens;
|
|
45
|
+
exports.tierForToken = tierForToken;
|
|
46
|
+
exports.requireTier = requireTier;
|
|
42
47
|
/**
|
|
43
48
|
* Authentication for the daemon HTTP surface (SSE-PROTOCOL.md §3).
|
|
44
49
|
*
|
|
@@ -152,14 +157,9 @@ function tokensMatch(received, expected) {
|
|
|
152
157
|
return crypto.timingSafeEqual(a, b);
|
|
153
158
|
}
|
|
154
159
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* `httpToken`, generate one (`crypto.randomBytes(32).toString('base64url')`)
|
|
159
|
-
* and persist it via `saveConfigFile` (which sets 0600 on POSIX).
|
|
160
|
-
* - When `bearerRequired` is false, return whatever is in the config
|
|
161
|
-
* without generating — operators may still want a token saved for
|
|
162
|
-
* future use, and we shouldn't write secrets the user didn't request.
|
|
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
163
|
*/
|
|
164
164
|
function loadOrGenerateHttpToken(opts) {
|
|
165
165
|
const load = opts.load ?? config_1.loadConfigFile;
|
|
@@ -170,8 +170,92 @@ function loadOrGenerateHttpToken(opts) {
|
|
|
170
170
|
}
|
|
171
171
|
if (!opts.bearerRequired)
|
|
172
172
|
return null;
|
|
173
|
-
// Auto-generate. base64url chars are safe inside Authorization values.
|
|
174
173
|
const token = crypto.randomBytes(32).toString('base64url');
|
|
175
174
|
save({ ...cfg, httpToken: token });
|
|
176
175
|
return token;
|
|
177
176
|
}
|
|
177
|
+
/** Admin token — ENV-VAR-ONLY (never config.json/disk, never auto-generated). */
|
|
178
|
+
function loadAdminToken(env = process.env) {
|
|
179
|
+
const t = env[config_1.ENV.HTTP_ADMIN_TOKEN];
|
|
180
|
+
return t && t.length > 0 ? t : null;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Read token (T1). Priority: env `AGENT_TEMPO_HTTP_READ_TOKEN` > config.json
|
|
184
|
+
* `readToken` > LEGACY config.json `httpToken` (adopted → `legacy:true`) >
|
|
185
|
+
* auto-generate when bearer mode is required (persisted as `readToken`).
|
|
186
|
+
*/
|
|
187
|
+
function loadReadToken(opts) {
|
|
188
|
+
const env = opts.env ?? process.env;
|
|
189
|
+
const load = opts.load ?? config_1.loadConfigFile;
|
|
190
|
+
const save = opts.save ?? config_1.saveConfigFile;
|
|
191
|
+
const envTok = env[config_1.ENV.HTTP_READ_TOKEN];
|
|
192
|
+
if (envTok && envTok.length > 0)
|
|
193
|
+
return { token: envTok, legacy: false };
|
|
194
|
+
const cfg = load();
|
|
195
|
+
if (cfg.readToken && cfg.readToken.length > 0)
|
|
196
|
+
return { token: cfg.readToken, legacy: false };
|
|
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 };
|
|
200
|
+
if (!opts.bearerRequired)
|
|
201
|
+
return { token: null, legacy: false };
|
|
202
|
+
const token = crypto.randomBytes(32).toString('base64url');
|
|
203
|
+
save({ ...cfg, readToken: token });
|
|
204
|
+
return { token, legacy: false };
|
|
205
|
+
}
|
|
206
|
+
/** Load both RBAC tokens. The daemon calls this once at startup. */
|
|
207
|
+
function loadRbacTokens(opts) {
|
|
208
|
+
const { token: readToken, legacy } = loadReadToken(opts);
|
|
209
|
+
const adminToken = loadAdminToken(opts.env);
|
|
210
|
+
return { readToken, adminToken, legacyMigrated: legacy };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Granted tier for a presented bearer, given the RBAC tokens (timing-safe).
|
|
214
|
+
* Admin grants the FULL ladder (3 ⊇ 2 ⊇ 1); read grants T1 only; no match → 0.
|
|
215
|
+
* There is no T2-only token — T2 and T3 are both "admin required".
|
|
216
|
+
*/
|
|
217
|
+
function tierForToken(presented, tokens) {
|
|
218
|
+
if (tokens.adminToken && tokensMatch(presented, tokens.adminToken))
|
|
219
|
+
return 3;
|
|
220
|
+
if (tokens.readToken && tokensMatch(presented, tokens.readToken))
|
|
221
|
+
return 1;
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
/** Migration hint surfaced in the 403 body so a read-token holder knows what's missing. */
|
|
225
|
+
const INSUFFICIENT_TIER_HINT = 'This token is read-tier. Writes, the operator gate, and the inner-tail require the admin token (set AGENT_TEMPO_HTTP_ADMIN_TOKEN).';
|
|
226
|
+
const ADMIN_UNSET_HINT = 'Set AGENT_TEMPO_HTTP_ADMIN_TOKEN (env-var only) to enable writes / gate / inner-tail.';
|
|
227
|
+
/**
|
|
228
|
+
* Authorization guard (3e MD-E). Assumes the shared upstream pass already settled
|
|
229
|
+
* AUTHENTICATION + CORS + the DNS-rebind/Origin defense (architect's Layer 2);
|
|
230
|
+
* this is Layer-3 authZ — it ONLY decides tier ≥ N and emits 503/403/401.
|
|
231
|
+
*
|
|
232
|
+
* Matrix (bearer mode):
|
|
233
|
+
* - loopback (`!bearerRequired`) → PASS all tiers (local-trust short-circuit).
|
|
234
|
+
* - N ≥ 2 AND adminToken unset → 503 admin-token-not-configured.
|
|
235
|
+
* - no/invalid bearer (granted 0) → 401 unauthorized.
|
|
236
|
+
* - granted < N (read token on T≥2) → 403 insufficient-tier (+ migration hint).
|
|
237
|
+
* - granted ≥ N (admin, or read on T1) → PASS.
|
|
238
|
+
*
|
|
239
|
+
* Keyed off the `Authorization` bearer only (no `Origin`/cookie requirement) so a
|
|
240
|
+
* headless Node client passes once its token validates. View-agnostic by design.
|
|
241
|
+
*/
|
|
242
|
+
function requireTier(tier, input) {
|
|
243
|
+
// Loopback trust: local clients (TUI / CLI / future Pi widget) get full access.
|
|
244
|
+
if (!bearerRequired(input.bindAddr, input.originHeader))
|
|
245
|
+
return { ok: true };
|
|
246
|
+
// A tier that needs admin is UNAVAILABLE when no admin token is configured —
|
|
247
|
+
// the honest answer is 503, regardless of what the caller presents.
|
|
248
|
+
if (tier >= 2 && input.adminToken === null) {
|
|
249
|
+
return { ok: false, status: 503, error: 'admin-token-not-configured', detail: ADMIN_UNSET_HINT };
|
|
250
|
+
}
|
|
251
|
+
const provided = extractBearerToken(input.authHeader);
|
|
252
|
+
if (!provided)
|
|
253
|
+
return { ok: false, status: 401, error: 'unauthorized' };
|
|
254
|
+
const granted = tierForToken(provided, input);
|
|
255
|
+
if (granted === 0)
|
|
256
|
+
return { ok: false, status: 401, error: 'unauthorized' };
|
|
257
|
+
if (granted < tier) {
|
|
258
|
+
return { ok: false, status: 403, error: 'insufficient-tier', detail: INSUFFICIENT_TIER_HINT };
|
|
259
|
+
}
|
|
260
|
+
return { ok: true };
|
|
261
|
+
}
|
package/dist/http/body.d.ts
CHANGED
|
@@ -13,6 +13,9 @@ import type { IncomingMessage, ServerResponse } from 'http';
|
|
|
13
13
|
import { type AgentType } from '../types';
|
|
14
14
|
/** Hard cap on incoming JSON body size (1 MiB). */
|
|
15
15
|
export declare const WRITE_BODY_MAX: number;
|
|
16
|
+
/** 3c Tier-2 ingest cap (32 KiB) — the DOS backstop for `/inner/ingest`; the
|
|
17
|
+
* source already ~2KB-truncates summaries, so real frames are far smaller. */
|
|
18
|
+
export declare const INGEST_BODY_MAX: number;
|
|
16
19
|
export declare const BODY_TOO_LARGE: unique symbol;
|
|
17
20
|
export declare const BODY_INVALID_JSON: unique symbol;
|
|
18
21
|
export type ReadJsonBodyResult = Record<string, unknown> | typeof BODY_TOO_LARGE | typeof BODY_INVALID_JSON;
|
|
@@ -26,7 +29,7 @@ export type ReadJsonBodyResult = Record<string, unknown> | typeof BODY_TOO_LARGE
|
|
|
26
29
|
* handler ends. Explicit `req.destroy()` would race the response
|
|
27
30
|
* write — left alone.
|
|
28
31
|
*/
|
|
29
|
-
export declare function readJsonBody(req: IncomingMessage): Promise<ReadJsonBodyResult>;
|
|
32
|
+
export declare function readJsonBody(req: IncomingMessage, maxBytes?: number): Promise<ReadJsonBodyResult>;
|
|
30
33
|
/**
|
|
31
34
|
* Pluck a string field from a parsed JSON body. Returns `undefined`
|
|
32
35
|
* for absent or non-string values; with `requireNonEmpty: true`, also
|
package/dist/http/body.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ALLOWED_AGENTS_PROD = exports.ALLOWED_AGENTS_DEV = exports.BODY_INVALID_JSON = exports.BODY_TOO_LARGE = exports.WRITE_BODY_MAX = void 0;
|
|
3
|
+
exports.ALLOWED_AGENTS_PROD = exports.ALLOWED_AGENTS_DEV = exports.BODY_INVALID_JSON = exports.BODY_TOO_LARGE = exports.INGEST_BODY_MAX = exports.WRITE_BODY_MAX = void 0;
|
|
4
4
|
exports.readJsonBody = readJsonBody;
|
|
5
5
|
exports.stringField = stringField;
|
|
6
6
|
exports.allowedAgentsForCurrentMode = allowedAgentsForCurrentMode;
|
|
@@ -12,6 +12,9 @@ const responses_1 = require("./responses");
|
|
|
12
12
|
const validation_1 = require("../utils/validation");
|
|
13
13
|
/** Hard cap on incoming JSON body size (1 MiB). */
|
|
14
14
|
exports.WRITE_BODY_MAX = 1024 * 1024;
|
|
15
|
+
/** 3c Tier-2 ingest cap (32 KiB) — the DOS backstop for `/inner/ingest`; the
|
|
16
|
+
* source already ~2KB-truncates summaries, so real frames are far smaller. */
|
|
17
|
+
exports.INGEST_BODY_MAX = 32 * 1024;
|
|
15
18
|
exports.BODY_TOO_LARGE = Symbol('body-too-large');
|
|
16
19
|
exports.BODY_INVALID_JSON = Symbol('body-invalid-json');
|
|
17
20
|
/**
|
|
@@ -24,13 +27,13 @@ exports.BODY_INVALID_JSON = Symbol('body-invalid-json');
|
|
|
24
27
|
* handler ends. Explicit `req.destroy()` would race the response
|
|
25
28
|
* write — left alone.
|
|
26
29
|
*/
|
|
27
|
-
async function readJsonBody(req) {
|
|
30
|
+
async function readJsonBody(req, maxBytes = exports.WRITE_BODY_MAX) {
|
|
28
31
|
const chunks = [];
|
|
29
32
|
let total = 0;
|
|
30
33
|
for await (const chunk of req) {
|
|
31
34
|
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
32
35
|
total += buf.length;
|
|
33
|
-
if (total >
|
|
36
|
+
if (total > maxBytes)
|
|
34
37
|
return exports.BODY_TOO_LARGE;
|
|
35
38
|
chunks.push(buf);
|
|
36
39
|
}
|
package/dist/http/event-bus.js
CHANGED
|
@@ -64,6 +64,7 @@ const NON_ESSENTIAL_AFTER_THROTTLE = new Set([
|
|
|
64
64
|
function topicOf(kind) {
|
|
65
65
|
switch (kind) {
|
|
66
66
|
case 'player.phase_changed': return 'phase';
|
|
67
|
+
case 'player.activity': return 'phase';
|
|
67
68
|
case 'chat.appended':
|
|
68
69
|
case 'chat.compressed': return 'chat';
|
|
69
70
|
case 'flags.changed': return 'flags';
|
|
@@ -152,7 +152,7 @@ export interface PlayerSummaryV1 {
|
|
|
152
152
|
* stability rule at §6 — adding new adapters in future versions remains
|
|
153
153
|
* non-breaking, removing one requires `/v2/`. See #535.
|
|
154
154
|
*/
|
|
155
|
-
agentType: 'claude' | 'copilot' | 'mock' | 'claude-api' | 'opencode' | 'claude-code-headless';
|
|
155
|
+
agentType: 'claude' | 'copilot' | 'mock' | 'claude-api' | 'opencode' | 'claude-code-headless' | 'pi';
|
|
156
156
|
playerType?: string;
|
|
157
157
|
/** Authoritative attachment phase (post-v0.26 — see WIRE-PROTOCOL.md). */
|
|
158
158
|
phase?: AttachmentPhase;
|
|
@@ -194,6 +194,20 @@ export interface PlayerSummaryV1 {
|
|
|
194
194
|
/** Q5.6 — ISO timestamp of the most recent activity. Already on
|
|
195
195
|
* `MaestroPlayerInfo`; passed through verbatim by `toPlayerSummaryV1`. */
|
|
196
196
|
lastActivityAt?: string;
|
|
197
|
+
/**
|
|
198
|
+
* 3c Tier-1 coarse observability — the tool the player is currently
|
|
199
|
+
* executing, or `null` when idle/between tools. Sourced from session
|
|
200
|
+
* metadata via the heartbeat piggyback (~30s freshness); the live,
|
|
201
|
+
* fine-grained tail is the off-wire `/inner` side-channel (MD-F). Additive.
|
|
202
|
+
*/
|
|
203
|
+
currentTool?: string | null;
|
|
204
|
+
/**
|
|
205
|
+
* 3c Tier-1 coarse — estimated context tokens in use (pull-only, from Pi's
|
|
206
|
+
* `getContextUsage()`; `null`/absent right after compaction). Additive.
|
|
207
|
+
*/
|
|
208
|
+
contextTokens?: number;
|
|
209
|
+
/** 3c Tier-1 coarse — context usage as a percentage of the model window. Additive. */
|
|
210
|
+
contextPercent?: number;
|
|
197
211
|
}
|
|
198
212
|
/**
|
|
199
213
|
* The eventId token used in the `id:` line of the SSE frame and as the
|
|
@@ -269,7 +283,7 @@ export declare const PR1_SENTINEL_EVENT_ID: EventIdToken;
|
|
|
269
283
|
* **Append-only**. Do not remove. New event types ship as additive `/v1/`
|
|
270
284
|
* additions; removals require `/v2/`.
|
|
271
285
|
*/
|
|
272
|
-
export declare const SSE_EVENT_KINDS: readonly ["snapshot", "gap", "throttled", "heartbeat", "ensemble.created", "ensemble.destroyed", "player.added", "player.removed", "player.phase_changed", "chat.appended", "chat.compressed", "flags.changed", "schedules.changed", "host_profile.changed"];
|
|
286
|
+
export declare const SSE_EVENT_KINDS: readonly ["snapshot", "gap", "throttled", "heartbeat", "ensemble.created", "ensemble.destroyed", "player.added", "player.removed", "player.phase_changed", "chat.appended", "chat.compressed", "flags.changed", "schedules.changed", "host_profile.changed", "player.activity"];
|
|
273
287
|
export type SseEventKind = (typeof SSE_EVENT_KINDS)[number];
|
|
274
288
|
/** Common envelope for every event. */
|
|
275
289
|
export interface SseEventBase {
|
|
@@ -364,6 +378,24 @@ export type TempoEvent = (SseEventBase & {
|
|
|
364
378
|
}) | (SseEventBase & {
|
|
365
379
|
type: 'host_profile.changed';
|
|
366
380
|
payload: HostProfile;
|
|
381
|
+
}) | (SseEventBase & {
|
|
382
|
+
type: 'player.activity';
|
|
383
|
+
/**
|
|
384
|
+
* 3c Tier-1 coarse activity (MD-F). Emitted by the aggregate poll/diff
|
|
385
|
+
* when a player's `currentTool` or context usage changes between polls.
|
|
386
|
+
* `busy/idle` is DERIVED consumer-side from the player's phase
|
|
387
|
+
* (busy = phase==='processing'); `activityCount`/`lastActivityAt` already
|
|
388
|
+
* ride `PlayerSummaryV1`. This is the ON-wire coarse tier; the fine,
|
|
389
|
+
* live inner tail is the off-wire `/inner` side-channel.
|
|
390
|
+
*/
|
|
391
|
+
payload: {
|
|
392
|
+
playerId: string;
|
|
393
|
+
ensemble: string;
|
|
394
|
+
currentTool: string | null;
|
|
395
|
+
contextTokens?: number;
|
|
396
|
+
contextPercent?: number;
|
|
397
|
+
at: string;
|
|
398
|
+
};
|
|
367
399
|
});
|
|
368
400
|
/**
|
|
369
401
|
* Subset of event categories the server can pre-filter on `?topics=...`.
|
package/dist/http/event-types.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { GateAuditSink } from './gate-registry';
|
|
2
|
+
/** Root of the per-player gate-audit tree. */
|
|
3
|
+
export declare function gateAuditRoot(): string;
|
|
4
|
+
/** Absolute JSONL path for a (ensemble, workflowId) pair under `root`. */
|
|
5
|
+
export declare function gateAuditPath(ensemble: string, workflowId: string, root?: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Build the daemon's audit sink. Returns a {@link GateAuditSink} that appends one
|
|
8
|
+
* JSON line per record. Append + mkdir are synchronous (durable-before-return);
|
|
9
|
+
* any I/O error is logged + swallowed so a disk problem never wedges a gate
|
|
10
|
+
* decision. `root` is injectable for tests (defaults to {@link gateAuditRoot}).
|
|
11
|
+
*/
|
|
12
|
+
export declare function createGateAuditSink(root?: string): GateAuditSink;
|