agent-tempo 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +219 -219
- package/LICENSE +21 -21
- package/README.md +289 -289
- package/assets/icon-dark.svg +9 -9
- package/assets/icon.svg +9 -9
- package/assets/logo-dark.svg +11 -11
- package/assets/logo-light.svg +11 -11
- package/dashboard/README.md +91 -91
- package/dashboard/dist/assets/index-D6Xyje_n.js.map +1 -1
- package/dashboard/dist/index.html +19 -19
- package/dashboard/package.json +47 -47
- package/dist/adapters/copilot/adapter.js +12 -1
- package/dist/cli/global-wrapper.d.ts +19 -0
- package/dist/cli/global-wrapper.js +169 -0
- package/dist/cli/help-text.js +97 -97
- package/dist/cli/startup.js +11 -0
- package/dist/cli/upgrade-command.js +81 -81
- package/dist/cli.js +12 -0
- package/dist/daemon.js +5 -0
- package/dist/scripts/verify-daemon-isolation-guard.js +24 -24
- package/dist/server.js +4 -0
- package/dist/spawn.js +12 -12
- package/dist/tools/coat-check-evict.js +2 -2
- package/dist/tools/coat-check-get.js +2 -2
- package/dist/tools/coat-check-put.js +4 -4
- package/dist/tools/fetch-state.js +2 -2
- package/dist/tools/save-state.js +13 -13
- package/dist/utils/grpc-shutdown-guard.d.ts +52 -0
- package/dist/utils/grpc-shutdown-guard.js +88 -0
- package/examples/agents/tempo-composer.md +56 -56
- package/examples/agents/tempo-conductor.md +117 -117
- package/examples/agents/tempo-critic.md +73 -73
- package/examples/agents/tempo-improv.md +74 -74
- package/examples/agents/tempo-liner.md +75 -75
- package/examples/agents/tempo-roadie.md +61 -61
- package/examples/agents/tempo-soloist.md +71 -71
- package/examples/agents/tempo-tuner.md +94 -94
- package/examples/ensembles/tempo-big-band.yaml +146 -146
- package/examples/ensembles/tempo-dev-team.yaml +58 -58
- package/examples/ensembles/tempo-headless-jam.yaml +77 -77
- package/examples/ensembles/tempo-jam-session.yaml +41 -41
- package/examples/ensembles/tempo-mock-jam.yaml +79 -79
- package/examples/ensembles/tempo-review-squad.yaml +32 -32
- package/package.json +173 -173
- package/packaging/launchd/com.agent.tempo.plist +46 -46
- package/packaging/systemd/agent-tempo.service +32 -32
- package/packaging/windows/install-task.ps1 +71 -71
- package/scenarios/conductor-recruit-mock.yaml +33 -33
- package/scenarios/echo-roundtrip.yaml +15 -15
- package/scenarios/multi-player-handoff.yaml +38 -38
- package/scenarios/recruit-cascade.yaml +38 -38
- package/scenarios/two-player-conversation.yaml +33 -33
- package/workflow-bundle.js +1 -1
- package/dist/activities/claude-stop.d.ts +0 -21
- package/dist/activities/claude-stop.js +0 -94
- package/dist/channel.d.ts +0 -3
- package/dist/channel.js +0 -48
- package/dist/copilot-bridge.d.ts +0 -22
- package/dist/copilot-bridge.js +0 -565
- package/dist/scripts/258-spotcheck.js +0 -303
- package/dist/tools/detach.d.ts +0 -4
- package/dist/tools/detach.js +0 -45
- package/dist/tools/encore.d.ts +0 -4
- package/dist/tools/encore.js +0 -31
- package/dist/tools/pause-ensemble.d.ts +0 -4
- package/dist/tools/pause-ensemble.js +0 -58
- package/dist/tools/resume-ensemble.d.ts +0 -4
- package/dist/tools/resume-ensemble.js +0 -79
- package/dist/tools/stop.d.ts +0 -4
- package/dist/tools/stop.js +0 -29
- package/dist/tui/client.d.ts +0 -6
- package/dist/tui/client.js +0 -9
- package/dist/tui/components/ActivityLog.d.ts +0 -16
- package/dist/tui/components/ActivityLog.js +0 -36
- package/dist/tui/components/CommandOverlay.d.ts +0 -15
- package/dist/tui/components/CommandOverlay.js +0 -34
- package/dist/tui/components/ConductorChat.d.ts +0 -16
- package/dist/tui/components/ConductorChat.js +0 -32
- package/dist/tui/components/EnsembleListView.d.ts +0 -14
- package/dist/tui/components/EnsembleListView.js +0 -32
- package/dist/tui/components/EnsemblePanel.d.ts +0 -12
- package/dist/tui/components/EnsemblePanel.js +0 -40
- package/dist/tui/components/InputBar.d.ts +0 -13
- package/dist/tui/components/InputBar.js +0 -58
- package/dist/tui/components/ScheduleOverlay.d.ts +0 -13
- package/dist/tui/components/ScheduleOverlay.js +0 -113
- package/dist/tui/components/TopBar.d.ts +0 -12
- package/dist/tui/components/TopBar.js +0 -15
- package/dist/tui/core-api.d.ts +0 -26
- package/dist/tui/core-api.js +0 -67
- package/dist/tui/hooks/useEnsembleDiscovery.d.ts +0 -3
- package/dist/tui/hooks/useEnsembleDiscovery.js +0 -30
- package/dist/tui/hooks/useMaestroPoller.d.ts +0 -3
- package/dist/tui/hooks/useMaestroPoller.js +0 -36
- package/dist/tui/hooks/useSendCommand.d.ts +0 -7
- package/dist/tui/hooks/useSendCommand.js +0 -29
- package/dist/utils/bg-preflight.d.ts +0 -25
- package/dist/utils/bg-preflight.js +0 -154
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
/**
|
|
4
|
-
* scripts/258-spotcheck.ts — automated #258 "one-delivery-then-silence" probe
|
|
5
|
-
*
|
|
6
|
-
* Ops tooling for the v0.26.0-beta.4 burn-in window (issue #258). Replaces
|
|
7
|
-
* the manual 4-step analysis performed by tempo-researcher on 2026-04-19
|
|
8
|
-
* at ~T+2h into the burn-in (dump at C:\tmp\wf-conductor-burnin.json). Run
|
|
9
|
-
* this whenever you want a fresh read on whether the #249 fix is still
|
|
10
|
-
* holding under real ensemble traffic.
|
|
11
|
-
*
|
|
12
|
-
* NOT committed. NOT production code. No tests, no docs. If this script
|
|
13
|
-
* stops working because the wire protocol evolves, delete and rewrite.
|
|
14
|
-
*
|
|
15
|
-
* ── Usage ─────────────────────────────────────────────────────────────
|
|
16
|
-
*
|
|
17
|
-
* npx ts-node --project scripts/tsconfig.json scripts/258-spotcheck.ts
|
|
18
|
-
*
|
|
19
|
-
* Flags (all optional):
|
|
20
|
-
* --ensemble <name> Override ensemble (default: $CLAUDE_TEMPO_ENSEMBLE or 'tempo-impl')
|
|
21
|
-
* --conductor <name> Override conductor player name (default: 'conductor')
|
|
22
|
-
* --json Emit JSON instead of the human table
|
|
23
|
-
* --save <path> Write full report JSON to <path>
|
|
24
|
-
*
|
|
25
|
-
* ── Exit codes ────────────────────────────────────────────────────────
|
|
26
|
-
* 0 pattern-not-detected (healthy)
|
|
27
|
-
* 1 unexpected error (Temporal unreachable, workflow missing, etc.)
|
|
28
|
-
* 2 pattern-recurring (hypothesis A or B) — HIGH PRIORITY
|
|
29
|
-
* 3 inconclusive-too-early (< 30 min window)
|
|
30
|
-
*
|
|
31
|
-
* ── Assumptions ───────────────────────────────────────────────────────
|
|
32
|
-
* • Temporal server reachable via the canonical getConfig() chain
|
|
33
|
-
* (localhost:7233 default; overridable via TEMPORAL_ADDRESS,
|
|
34
|
-
* ~/.claude-tempo/config.json, or ~/.config/temporalio/temporal.yaml).
|
|
35
|
-
* • Conductor workflow ID = `claude-session-{ensemble}-conductor`
|
|
36
|
-
* (src/config.ts::conductorWorkflowId). For non-default ensembles,
|
|
37
|
-
* set CLAUDE_TEMPO_ENSEMBLE or pass --ensemble.
|
|
38
|
-
* • Reads the *current* run via handle-without-runId. If a CAN has
|
|
39
|
-
* occurred, the SDK returns the latest run — which is exactly what
|
|
40
|
-
* we want for post-CAN health.
|
|
41
|
-
* • Signal / update names are per docs/WIRE-PROTOCOL.md (stable):
|
|
42
|
-
* Signals: 'heartbeat', 'markDelivered', 'receiveMessage',
|
|
43
|
-
* 'playerReport', 'setPaused', 'setPart', 'releaseHeld', ...
|
|
44
|
-
* Updates: 'claimAttachment', 'submitOutbox', ...
|
|
45
|
-
*
|
|
46
|
-
* ── #258 pattern definitions (from C:\tmp\spike-0d17dc0f-verdict.md) ──
|
|
47
|
-
*
|
|
48
|
-
* • Hypothesis A — adapter process death:
|
|
49
|
-
* heartbeats stop mid-window, markDelivered goes silent while
|
|
50
|
-
* receiveMessage continues arriving, NO reclaim attempts.
|
|
51
|
-
*
|
|
52
|
-
* • Hypothesis B — delayed fireTerminal('destroy') via reconnect loop:
|
|
53
|
-
* same silence signature, PLUS at least one claimAttachment reclaim
|
|
54
|
-
* fired after the silence started (adapter tried to recover, then
|
|
55
|
-
* a terminal-class error on the pre-check killed the poller).
|
|
56
|
-
*
|
|
57
|
-
* Thresholds used here (conservative; tune if we recalibrate):
|
|
58
|
-
* – Heartbeat staleness: > 180 s since last (3× expected 60 s cadence)
|
|
59
|
-
* – Silence skew: receiveMessage last-ts > markDelivered last-ts + 60 s
|
|
60
|
-
* – Too-early window: total history span < 30 min
|
|
61
|
-
*/
|
|
62
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
63
|
-
const client_1 = require("@temporalio/client");
|
|
64
|
-
const fs_1 = require("fs");
|
|
65
|
-
const config_1 = require("../src/config");
|
|
66
|
-
const connection_1 = require("../src/connection");
|
|
67
|
-
// ─── CLI ──────────────────────────────────────────────────────────────
|
|
68
|
-
const args = process.argv.slice(2);
|
|
69
|
-
function argVal(flag) {
|
|
70
|
-
const i = args.indexOf(flag);
|
|
71
|
-
return i >= 0 && i + 1 < args.length ? args[i + 1] : undefined;
|
|
72
|
-
}
|
|
73
|
-
const emitJson = args.includes('--json');
|
|
74
|
-
const savePath = argVal('--save');
|
|
75
|
-
const ensembleOverride = argVal('--ensemble');
|
|
76
|
-
const conductorNameOverride = argVal('--conductor') ?? 'conductor';
|
|
77
|
-
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
78
|
-
function tsIso(t) {
|
|
79
|
-
// Temporal SDK decodes protobuf Timestamp to { seconds: Long|number|bigint, nanos: number }.
|
|
80
|
-
// Fall back through a few shapes defensively; the SDK's Long type has .toNumber().
|
|
81
|
-
if (!t)
|
|
82
|
-
return '';
|
|
83
|
-
if (typeof t === 'string')
|
|
84
|
-
return t;
|
|
85
|
-
const rawSeconds = t.seconds;
|
|
86
|
-
let seconds;
|
|
87
|
-
if (typeof rawSeconds === 'bigint')
|
|
88
|
-
seconds = Number(rawSeconds);
|
|
89
|
-
else if (typeof rawSeconds === 'number')
|
|
90
|
-
seconds = rawSeconds;
|
|
91
|
-
else if (rawSeconds && typeof rawSeconds.toNumber === 'function')
|
|
92
|
-
seconds = rawSeconds.toNumber();
|
|
93
|
-
else
|
|
94
|
-
seconds = 0;
|
|
95
|
-
const nanos = typeof t.nanos === 'number' ? t.nanos : 0;
|
|
96
|
-
return new Date(seconds * 1000 + nanos / 1e6).toISOString();
|
|
97
|
-
}
|
|
98
|
-
function addStat(map, name, ts) {
|
|
99
|
-
const s = map.get(name) ?? { count: 0 };
|
|
100
|
-
s.count++;
|
|
101
|
-
if (!s.first)
|
|
102
|
-
s.first = ts;
|
|
103
|
-
s.last = ts;
|
|
104
|
-
map.set(name, s);
|
|
105
|
-
}
|
|
106
|
-
function isoToMs(iso) {
|
|
107
|
-
return iso ? new Date(iso).getTime() : 0;
|
|
108
|
-
}
|
|
109
|
-
function fmtDur(ms) {
|
|
110
|
-
const s = Math.max(0, Math.floor(ms / 1000));
|
|
111
|
-
const h = Math.floor(s / 3600);
|
|
112
|
-
const m = Math.floor((s % 3600) / 60);
|
|
113
|
-
const ss = s % 60;
|
|
114
|
-
return h ? `${h}h ${m}m ${ss}s` : `${m}m ${ss}s`;
|
|
115
|
-
}
|
|
116
|
-
function fmtSec(ms) {
|
|
117
|
-
return ms === null ? 'n/a' : `${(ms / 1000).toFixed(1)}s`;
|
|
118
|
-
}
|
|
119
|
-
// ─── Main ─────────────────────────────────────────────────────────────
|
|
120
|
-
async function run() {
|
|
121
|
-
const config = (0, config_1.getConfig)();
|
|
122
|
-
const ensemble = ensembleOverride ?? config.ensemble ?? 'tempo-impl';
|
|
123
|
-
const workflowId = conductorNameOverride === 'conductor'
|
|
124
|
-
? (0, config_1.conductorWorkflowId)(ensemble)
|
|
125
|
-
: `claude-session-${ensemble}-${conductorNameOverride}`;
|
|
126
|
-
const connection = await (0, connection_1.createTemporalConnection)(config);
|
|
127
|
-
try {
|
|
128
|
-
const client = new client_1.Client({ connection, namespace: config.temporalNamespace });
|
|
129
|
-
const handle = client.workflow.getHandle(workflowId);
|
|
130
|
-
const history = await handle.fetchHistory();
|
|
131
|
-
const events = history.events ?? [];
|
|
132
|
-
if (events.length === 0) {
|
|
133
|
-
throw new Error(`Workflow "${workflowId}" returned no events — is it running? (ensemble="${ensemble}", ns="${config.temporalNamespace}", addr="${config.temporalAddress}")`);
|
|
134
|
-
}
|
|
135
|
-
// Run identity (first event is EXECUTION_STARTED; capture its runId).
|
|
136
|
-
let runId = '(unknown)';
|
|
137
|
-
const startedAttrs = events[0].workflowExecutionStartedEventAttributes;
|
|
138
|
-
if (startedAttrs?.originalExecutionRunId)
|
|
139
|
-
runId = startedAttrs.originalExecutionRunId;
|
|
140
|
-
const signals = new Map();
|
|
141
|
-
const updates = new Map();
|
|
142
|
-
const hbTimes = [];
|
|
143
|
-
for (const e of events) {
|
|
144
|
-
const ts = tsIso(e.eventTime);
|
|
145
|
-
// Presence-of-attribute pattern — matches how src/adapters/base.ts
|
|
146
|
-
// detects CAN events. Avoids coupling to the numeric eventType enum.
|
|
147
|
-
if (e.workflowExecutionSignaledEventAttributes) {
|
|
148
|
-
const name = e.workflowExecutionSignaledEventAttributes.signalName ?? '<unknown>';
|
|
149
|
-
addStat(signals, name, ts);
|
|
150
|
-
if (name === 'heartbeat')
|
|
151
|
-
hbTimes.push(isoToMs(ts));
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (e.workflowExecutionUpdateAcceptedEventAttributes) {
|
|
155
|
-
const req = e.workflowExecutionUpdateAcceptedEventAttributes.acceptedRequest;
|
|
156
|
-
const name = req?.input?.name ?? '<unknown>';
|
|
157
|
-
addStat(updates, name, ts);
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// Window = first event -> last event
|
|
162
|
-
const windowStart = tsIso(events[0].eventTime);
|
|
163
|
-
const windowEnd = tsIso(events[events.length - 1].eventTime);
|
|
164
|
-
const windowMs = isoToMs(windowEnd) - isoToMs(windowStart);
|
|
165
|
-
// Heartbeat cadence stats
|
|
166
|
-
let minGap = null;
|
|
167
|
-
let maxGap = null;
|
|
168
|
-
let meanGap = null;
|
|
169
|
-
if (hbTimes.length >= 2) {
|
|
170
|
-
const gaps = [];
|
|
171
|
-
for (let i = 1; i < hbTimes.length; i++)
|
|
172
|
-
gaps.push(hbTimes[i] - hbTimes[i - 1]);
|
|
173
|
-
minGap = Math.min(...gaps);
|
|
174
|
-
maxGap = Math.max(...gaps);
|
|
175
|
-
meanGap = gaps.reduce((a, b) => a + b, 0) / gaps.length;
|
|
176
|
-
}
|
|
177
|
-
const nowMs = Date.now();
|
|
178
|
-
const STALE_MS = 180000; // 3× 60 s expected cadence
|
|
179
|
-
const msSinceLast = hbTimes.length ? nowMs - hbTimes[hbTimes.length - 1] : null;
|
|
180
|
-
const stalled = msSinceLast !== null && msSinceLast > STALE_MS;
|
|
181
|
-
// Silence-skew detector: markDelivered silent while receiveMessage continues.
|
|
182
|
-
const receiveLast = signals.get('receiveMessage')?.last;
|
|
183
|
-
const markLast = signals.get('markDelivered')?.last;
|
|
184
|
-
const skew = !!receiveLast &&
|
|
185
|
-
!!markLast &&
|
|
186
|
-
isoToMs(receiveLast) > isoToMs(markLast) + 60000;
|
|
187
|
-
// Reclaim-after-silence detector (hypothesis B): a claimAttachment
|
|
188
|
-
// update fired AFTER markDelivered went quiet.
|
|
189
|
-
const claimLast = updates.get('claimAttachment')?.last;
|
|
190
|
-
const reclaimAfterSilence = !!claimLast && !!markLast && isoToMs(claimLast) > isoToMs(markLast) + 60000;
|
|
191
|
-
// ── Verdict ─────────────────────────────────────────────────────────
|
|
192
|
-
const TOO_EARLY_MS = 30 * 60 * 1000;
|
|
193
|
-
let verdict;
|
|
194
|
-
let verdictReason;
|
|
195
|
-
if (windowMs < TOO_EARLY_MS) {
|
|
196
|
-
verdict = 'inconclusive-too-early';
|
|
197
|
-
verdictReason = `window ${fmtDur(windowMs)} < 30 min minimum — re-run later.`;
|
|
198
|
-
}
|
|
199
|
-
else if (stalled && skew && reclaimAfterSilence) {
|
|
200
|
-
verdict = 'pattern-recurring-hypothesis-B';
|
|
201
|
-
verdictReason = `heartbeats stale (${fmtSec(msSinceLast)}); markDelivered silent while receiveMessage continued; claimAttachment reclaim fired after silence — consistent with delayed fireTerminal('destroy').`;
|
|
202
|
-
}
|
|
203
|
-
else if (stalled && skew) {
|
|
204
|
-
verdict = 'pattern-recurring-hypothesis-A';
|
|
205
|
-
verdictReason = `heartbeats stale (${fmtSec(msSinceLast)}); markDelivered silent while receiveMessage continued; no reclaim — consistent with adapter process death.`;
|
|
206
|
-
}
|
|
207
|
-
else if (stalled) {
|
|
208
|
-
verdict = 'pattern-recurring-hypothesis-A';
|
|
209
|
-
verdictReason = `heartbeats stale (${fmtSec(msSinceLast)} > ${STALE_MS / 1000}s threshold); no inbound-vs-outbound skew detected but cadence is broken.`;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
verdict = 'pattern-not-detected';
|
|
213
|
-
const lastPart = msSinceLast !== null ? `last tick ${fmtSec(msSinceLast)} ago` : 'no heartbeats';
|
|
214
|
-
const maxPart = maxGap !== null ? `max gap ${fmtSec(maxGap)}` : '';
|
|
215
|
-
verdictReason = `heartbeat cadence healthy (${lastPart}${maxPart ? `, ${maxPart}` : ''}); no signal-flow anomaly.`;
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
workflowId,
|
|
219
|
-
runId,
|
|
220
|
-
windowStart,
|
|
221
|
-
windowEnd,
|
|
222
|
-
windowMs,
|
|
223
|
-
eventCount: events.length,
|
|
224
|
-
signals: Object.fromEntries(signals),
|
|
225
|
-
updates: Object.fromEntries(updates),
|
|
226
|
-
heartbeat: {
|
|
227
|
-
count: hbTimes.length,
|
|
228
|
-
minGapMs: minGap,
|
|
229
|
-
maxGapMs: maxGap,
|
|
230
|
-
meanGapMs: meanGap,
|
|
231
|
-
staleThresholdMs: STALE_MS,
|
|
232
|
-
msSinceLast,
|
|
233
|
-
stalled,
|
|
234
|
-
},
|
|
235
|
-
verdict,
|
|
236
|
-
verdictReason,
|
|
237
|
-
capturedAt: new Date().toISOString(),
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
finally {
|
|
241
|
-
await connection.close();
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
function printHuman(r) {
|
|
245
|
-
const sep = '─'.repeat(74);
|
|
246
|
-
console.log(`\n${sep}`);
|
|
247
|
-
console.log(`#258 spot-check — ${r.workflowId}`);
|
|
248
|
-
console.log(sep);
|
|
249
|
-
console.log(`Run: ${r.runId}`);
|
|
250
|
-
console.log(`Window: ${r.windowStart} → ${r.windowEnd}`);
|
|
251
|
-
console.log(` ${fmtDur(r.windowMs)}, ${r.eventCount} events`);
|
|
252
|
-
console.log(`Captured: ${r.capturedAt}`);
|
|
253
|
-
console.log();
|
|
254
|
-
console.log('Signal counts:');
|
|
255
|
-
const sigRows = Object.entries(r.signals).sort((a, b) => b[1].count - a[1].count);
|
|
256
|
-
if (sigRows.length === 0)
|
|
257
|
-
console.log(' (none)');
|
|
258
|
-
for (const [name, s] of sigRows) {
|
|
259
|
-
console.log(` ${name.padEnd(22)} ${String(s.count).padStart(4)} first=${s.first} last=${s.last}`);
|
|
260
|
-
}
|
|
261
|
-
console.log('\nAccepted-update counts:');
|
|
262
|
-
const updRows = Object.entries(r.updates).sort((a, b) => b[1].count - a[1].count);
|
|
263
|
-
if (updRows.length === 0)
|
|
264
|
-
console.log(' (none)');
|
|
265
|
-
for (const [name, s] of updRows) {
|
|
266
|
-
console.log(` ${name.padEnd(22)} ${String(s.count).padStart(4)} first=${s.first} last=${s.last}`);
|
|
267
|
-
}
|
|
268
|
-
console.log('\nHeartbeat cadence:');
|
|
269
|
-
const hb = r.heartbeat;
|
|
270
|
-
console.log(` count=${hb.count} min=${fmtSec(hb.minGapMs)} max=${fmtSec(hb.maxGapMs)} mean=${fmtSec(hb.meanGapMs)}`);
|
|
271
|
-
console.log(` since last heartbeat: ${fmtSec(hb.msSinceLast)} stale threshold: ${hb.staleThresholdMs / 1000}s stalled=${hb.stalled ? 'YES' : 'no'}`);
|
|
272
|
-
console.log(`\nVerdict: ${r.verdict}`);
|
|
273
|
-
console.log(` ${r.verdictReason}`);
|
|
274
|
-
console.log();
|
|
275
|
-
}
|
|
276
|
-
run()
|
|
277
|
-
.then((r) => {
|
|
278
|
-
if (emitJson) {
|
|
279
|
-
console.log(JSON.stringify(r, null, 2));
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
printHuman(r);
|
|
283
|
-
}
|
|
284
|
-
if (savePath) {
|
|
285
|
-
(0, fs_1.writeFileSync)(savePath, JSON.stringify(r, null, 2));
|
|
286
|
-
if (!emitJson)
|
|
287
|
-
console.log(`(saved report JSON to ${savePath})`);
|
|
288
|
-
}
|
|
289
|
-
switch (r.verdict) {
|
|
290
|
-
case 'pattern-not-detected':
|
|
291
|
-
process.exit(0);
|
|
292
|
-
case 'inconclusive-too-early':
|
|
293
|
-
process.exit(3);
|
|
294
|
-
default:
|
|
295
|
-
process.exit(2); // hypothesis-A or hypothesis-B — escalate
|
|
296
|
-
}
|
|
297
|
-
})
|
|
298
|
-
.catch((err) => {
|
|
299
|
-
console.error('\n[258-spotcheck] error:', err instanceof Error ? err.message : err);
|
|
300
|
-
if (err instanceof Error && err.stack)
|
|
301
|
-
console.error(err.stack);
|
|
302
|
-
process.exit(1);
|
|
303
|
-
});
|
package/dist/tools/detach.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { Client, WorkflowHandle } from '@temporalio/client';
|
|
3
|
-
import { Config } from '../config';
|
|
4
|
-
export declare function registerDetachTool(server: McpServer, _client: Client, _config: Config, getPlayerId: () => string, handle: WorkflowHandle): void;
|
package/dist/tools/detach.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerDetachTool = registerDetachTool;
|
|
4
|
-
/**
|
|
5
|
-
* `detach` — graceful reap of a session's adapter without destroying the workflow.
|
|
6
|
-
*
|
|
7
|
-
* QA B1: enqueues a `DetachOutboxEntry` on the caller's workflow outbox rather
|
|
8
|
-
* than firing `requestDetachSignal` directly from tool code. The session
|
|
9
|
-
* workflow's dispatch loop runs the `deliverDetach` activity on the target,
|
|
10
|
-
* which signals `requestDetachSignal` with the supplied `reason` + `deadlineMs`.
|
|
11
|
-
*
|
|
12
|
-
* Design reference: §8.1 (three verbs), §2.4 (phase transitions).
|
|
13
|
-
*/
|
|
14
|
-
const zod_1 = require("zod");
|
|
15
|
-
const signals_1 = require("../workflows/signals");
|
|
16
|
-
const helpers_1 = require("./helpers");
|
|
17
|
-
const validation_1 = require("../utils/validation");
|
|
18
|
-
const DEFAULT_DETACH_DEADLINE_MS = 5_000;
|
|
19
|
-
function registerDetachTool(server, _client, _config, getPlayerId, handle) {
|
|
20
|
-
(0, helpers_1.defineTool)(server, 'detach', 'Gracefully detach a session\'s adapter. The session enters `draining` phase and reaps to `detached` after the deadline or when the adapter exits. The workflow survives — use `restart` to attach a new adapter, or `destroy` to terminate permanently.', {
|
|
21
|
-
playerId: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).describe('The player name to detach'),
|
|
22
|
-
deadlineMs: zod_1.z.number().min(0).max(validation_1.MAX_DETACH_DEADLINE_MS).optional().describe(`Max drain time in ms before force-detach (default ${DEFAULT_DETACH_DEADLINE_MS}, max ${validation_1.MAX_DETACH_DEADLINE_MS})`),
|
|
23
|
-
}, async (args) => {
|
|
24
|
-
const { playerId, deadlineMs = DEFAULT_DETACH_DEADLINE_MS } = args;
|
|
25
|
-
const nameError = (0, validation_1.validatePlayerName)(playerId);
|
|
26
|
-
if (nameError)
|
|
27
|
-
return (0, helpers_1.fail)(nameError);
|
|
28
|
-
if (playerId === getPlayerId()) {
|
|
29
|
-
return (0, helpers_1.fail)('Cannot detach your own session.');
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
const entry = {
|
|
33
|
-
type: 'detach',
|
|
34
|
-
targetPlayerId: playerId,
|
|
35
|
-
reason: 'user-stop',
|
|
36
|
-
deadlineMs,
|
|
37
|
-
};
|
|
38
|
-
const entryId = await handle.executeUpdate(signals_1.submitOutboxUpdate, { args: [entry] });
|
|
39
|
-
return (0, helpers_1.ok)(`Detach queued for **${playerId}** (deadline: ${deadlineMs}ms; outbox: ${entryId}).`);
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
return (0, helpers_1.fail)(`Failed to detach: ${(0, helpers_1.formatError)(err)}`);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
}
|
package/dist/tools/encore.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import type { Client, WorkflowHandle } from '@temporalio/client';
|
|
3
|
-
import type { Config } from '../config';
|
|
4
|
-
export declare function registerEncoreTool(_server: McpServer, _client: Client, _config: Config, _getPlayerId: () => string, _handle: WorkflowHandle): void;
|
package/dist/tools/encore.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerEncoreTool = registerEncoreTool;
|
|
4
|
-
/**
|
|
5
|
-
* `encore` — deprecation shim (PR-D).
|
|
6
|
-
*
|
|
7
|
-
* The `encore` verb is retired in v0.25 per design §8.1 "three verbs" —
|
|
8
|
-
* replaced by `restart`, which runs the same §8.2 algorithm (forceDetach +
|
|
9
|
-
* claimAttachment + context replay + enqueueSpawn) but is not limited to
|
|
10
|
-
* `stale`-only targets.
|
|
11
|
-
*
|
|
12
|
-
* The MCP tool remains registered so existing clients discover it via
|
|
13
|
-
* ListTools but returns an `isError` pointing to the replacement. The
|
|
14
|
-
* internal `case 'encore':` outbox dispatch and `performEncore` activity
|
|
15
|
-
* are still live; `restart` doesn't use them, but they stay so scheduler /
|
|
16
|
-
* test fixtures that enqueue encore entries keep working until v0.25.1.
|
|
17
|
-
*/
|
|
18
|
-
const zod_1 = require("zod");
|
|
19
|
-
const helpers_1 = require("./helpers");
|
|
20
|
-
function registerEncoreTool(_server, _client, _config, _getPlayerId, _handle) {
|
|
21
|
-
(0, helpers_1.defineTool)(_server, 'encore', '[deprecated in v0.25] The `encore` verb is retired — use `restart` instead, which runs the same forceDetach + claim + context-replay + enqueueSpawn algorithm but is not limited to stale targets.', {
|
|
22
|
-
playerId: zod_1.z.string().describe('The player name to revive'),
|
|
23
|
-
host: zod_1.z.string().optional().describe('Target hostname (legacy)'),
|
|
24
|
-
contextMessages: zod_1.z.number().optional().describe('Number of recent messages (legacy)'),
|
|
25
|
-
}, async () => {
|
|
26
|
-
return (0, helpers_1.fail)('The `encore` tool is deprecated. Use `restart` instead.\n' +
|
|
27
|
-
' • `restart` runs the same algorithm (forceDetach → claim → context replay → spawn)\n' +
|
|
28
|
-
' • Not limited to `stale`-only targets — works on any non-`gone` phase\n' +
|
|
29
|
-
' • Pair with `host=<h>` or use `migrate` for cross-host revive');
|
|
30
|
-
});
|
|
31
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { Client } from '@temporalio/client';
|
|
3
|
-
import { Config } from '../config';
|
|
4
|
-
export declare function registerPauseEnsembleTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string): void;
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerPauseEnsembleTool = registerPauseEnsembleTool;
|
|
4
|
-
const config_1 = require("../config");
|
|
5
|
-
const resolve_1 = require("../activities/resolve");
|
|
6
|
-
const helpers_1 = require("./helpers");
|
|
7
|
-
const log = (...args) => console.error('[claude-tempo:pause]', ...args);
|
|
8
|
-
function registerPauseEnsembleTool(server, client, config, getPlayerId) {
|
|
9
|
-
(0, helpers_1.defineTool)(server, 'pause_ensemble', 'Pause all sessions in the ensemble — locks outbox dispatch and pauses the scheduler. Stop commands still go through. Use resume_ensemble to unpause.', {}, async () => {
|
|
10
|
-
try {
|
|
11
|
-
const results = [];
|
|
12
|
-
const errors = [];
|
|
13
|
-
// 1. Signal maestro with paused state (ground truth)
|
|
14
|
-
try {
|
|
15
|
-
const maestroId = (0, config_1.maestroWorkflowId)(config.ensemble);
|
|
16
|
-
const maestroHandle = client.workflow.getHandle(maestroId);
|
|
17
|
-
await maestroHandle.signal('maestroSetPaused', true);
|
|
18
|
-
results.push('maestro paused');
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
// Maestro may not be running
|
|
22
|
-
}
|
|
23
|
-
// 2. Signal all active sessions
|
|
24
|
-
const sessions = await (0, resolve_1.scanEnsembleSessions)(client, config.ensemble);
|
|
25
|
-
let sessionCount = 0;
|
|
26
|
-
for (const session of sessions) {
|
|
27
|
-
try {
|
|
28
|
-
const sessionHandle = client.workflow.getHandle(session.workflowId);
|
|
29
|
-
await sessionHandle.signal('setPaused', true);
|
|
30
|
-
sessionCount++;
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
errors.push(`${session.playerId}: ${(0, helpers_1.formatError)(err)}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
results.push(`${sessionCount} session(s) paused`);
|
|
37
|
-
// 3. Signal scheduler
|
|
38
|
-
try {
|
|
39
|
-
const schedulerId = (0, config_1.schedulerWorkflowId)(config.ensemble);
|
|
40
|
-
const schedulerHandle = client.workflow.getHandle(schedulerId);
|
|
41
|
-
await schedulerHandle.signal('setSchedulerPaused', true);
|
|
42
|
-
results.push('scheduler paused');
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Scheduler may not be running
|
|
46
|
-
}
|
|
47
|
-
const lines = [`Ensemble **${config.ensemble}** paused.`, results.join(', ')];
|
|
48
|
-
if (errors.length > 0) {
|
|
49
|
-
lines.push(`Errors:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
50
|
-
}
|
|
51
|
-
log(`Paused ensemble "${config.ensemble}" by ${getPlayerId()}`);
|
|
52
|
-
return (0, helpers_1.ok)(lines.join('\n'));
|
|
53
|
-
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
return (0, helpers_1.fail)(`Failed to pause ensemble: ${(0, helpers_1.formatError)(err)}`);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { Client } from '@temporalio/client';
|
|
3
|
-
import { Config } from '../config';
|
|
4
|
-
export declare function registerResumeEnsembleTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string): void;
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerResumeEnsembleTool = registerResumeEnsembleTool;
|
|
4
|
-
const zod_1 = require("zod");
|
|
5
|
-
const config_1 = require("../config");
|
|
6
|
-
const resolve_1 = require("../activities/resolve");
|
|
7
|
-
const signals_1 = require("../workflows/signals");
|
|
8
|
-
const helpers_1 = require("./helpers");
|
|
9
|
-
const log = (...args) => console.error('[claude-tempo:resume]', ...args);
|
|
10
|
-
function registerResumeEnsembleTool(server, client, config, getPlayerId) {
|
|
11
|
-
(0, helpers_1.defineTool)(server, 'resume_ensemble', 'Resume all paused sessions in the ensemble — unlocks outbox dispatch and resumes the scheduler. Buffered outbox entries will be dispatched. Pass `release: true` to also release any held sessions (deliver deferred task messages and unlock their outboxes) in the same call.', {
|
|
12
|
-
release: zod_1.z.boolean().optional().describe('Also release any held sessions (deliver deferred task messages and unlock outboxes). Safe to call when no sessions are held — it is a no-op on those. Default: false.'),
|
|
13
|
-
}, async (args) => {
|
|
14
|
-
const release = args.release === true;
|
|
15
|
-
try {
|
|
16
|
-
const results = [];
|
|
17
|
-
const errors = [];
|
|
18
|
-
// 1. Signal maestro with unpaused state (ground truth)
|
|
19
|
-
try {
|
|
20
|
-
const maestroId = (0, config_1.maestroWorkflowId)(config.ensemble);
|
|
21
|
-
const maestroHandle = client.workflow.getHandle(maestroId);
|
|
22
|
-
await maestroHandle.signal('maestroSetPaused', false);
|
|
23
|
-
results.push('maestro resumed');
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
// Maestro may not be running
|
|
27
|
-
}
|
|
28
|
-
// 2. Signal all active sessions — unpause, and release held ones if opted in.
|
|
29
|
-
// `releaseHeld` is idempotent on sessions that aren't holding (no heldMessage,
|
|
30
|
-
// outboxLocked: false already), so it's safe to fan out unconditionally when
|
|
31
|
-
// `release: true`. See `src/workflows/session.ts` releaseHeldSignal handler.
|
|
32
|
-
const sessions = await (0, resolve_1.scanEnsembleSessions)(client, config.ensemble);
|
|
33
|
-
let sessionCount = 0;
|
|
34
|
-
let releasedCount = 0;
|
|
35
|
-
for (const session of sessions) {
|
|
36
|
-
try {
|
|
37
|
-
const sessionHandle = client.workflow.getHandle(session.workflowId);
|
|
38
|
-
await sessionHandle.signal('setPaused', false);
|
|
39
|
-
sessionCount++;
|
|
40
|
-
if (release) {
|
|
41
|
-
try {
|
|
42
|
-
await sessionHandle.signal(signals_1.releaseHeldSignal);
|
|
43
|
-
releasedCount++;
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
errors.push(`${session.playerId} release: ${(0, helpers_1.formatError)(err)}`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
errors.push(`${session.playerId}: ${(0, helpers_1.formatError)(err)}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
results.push(`${sessionCount} session(s) resumed`);
|
|
55
|
-
if (release) {
|
|
56
|
-
results.push(`${releasedCount} session(s) signalled for release`);
|
|
57
|
-
}
|
|
58
|
-
// 3. Signal scheduler
|
|
59
|
-
try {
|
|
60
|
-
const schedulerId = (0, config_1.schedulerWorkflowId)(config.ensemble);
|
|
61
|
-
const schedulerHandle = client.workflow.getHandle(schedulerId);
|
|
62
|
-
await schedulerHandle.signal('setSchedulerPaused', false);
|
|
63
|
-
results.push('scheduler resumed');
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// Scheduler may not be running
|
|
67
|
-
}
|
|
68
|
-
const lines = [`Ensemble **${config.ensemble}** resumed.`, results.join(', ')];
|
|
69
|
-
if (errors.length > 0) {
|
|
70
|
-
lines.push(`Errors:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
71
|
-
}
|
|
72
|
-
log(`Resumed ensemble "${config.ensemble}" by ${getPlayerId()}${release ? ' (with release)' : ''}`);
|
|
73
|
-
return (0, helpers_1.ok)(lines.join('\n'));
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
return (0, helpers_1.fail)(`Failed to resume ensemble: ${(0, helpers_1.formatError)(err)}`);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
}
|
package/dist/tools/stop.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import type { Client, WorkflowHandle } from '@temporalio/client';
|
|
3
|
-
import type { Config } from '../config';
|
|
4
|
-
export declare function registerStopTool(_server: McpServer, _client: Client, _config: Config, _getPlayerId: () => string, _handle: WorkflowHandle): void;
|
package/dist/tools/stop.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerStopTool = registerStopTool;
|
|
4
|
-
/**
|
|
5
|
-
* `stop` — deprecation shim (PR-D).
|
|
6
|
-
*
|
|
7
|
-
* The `stop` verb is retired in v0.25 per design §8.1 "three verbs". Its two
|
|
8
|
-
* distinct semantics are now separate tools:
|
|
9
|
-
* - graceful reap of the adapter, workflow survives → `detach`
|
|
10
|
-
* - terminal teardown of the workflow → `destroy`
|
|
11
|
-
*
|
|
12
|
-
* The MCP tool remains registered (so existing clients discover it via
|
|
13
|
-
* ListTools) but returns an `isError` result pointing to the replacement.
|
|
14
|
-
* The CLI-level `claude-tempo stop` command is unchanged — it's still useful
|
|
15
|
-
* for bulk cleanup (ensemble-wide or `--all`).
|
|
16
|
-
*/
|
|
17
|
-
const zod_1 = require("zod");
|
|
18
|
-
const helpers_1 = require("./helpers");
|
|
19
|
-
function registerStopTool(_server, _client, _config, _getPlayerId, _handle) {
|
|
20
|
-
(0, helpers_1.defineTool)(_server, 'stop', '[deprecated in v0.25] The `stop` verb was split into `detach` (graceful adapter reap, workflow survives) and `destroy` (terminal teardown). This tool now returns an error pointing you to the right one.', {
|
|
21
|
-
playerId: zod_1.z.string().describe('The player name to stop'),
|
|
22
|
-
force: zod_1.z.boolean().optional().describe('Legacy force flag — no longer honored'),
|
|
23
|
-
}, async () => {
|
|
24
|
-
return (0, helpers_1.fail)('The `stop` tool is deprecated. Use:\n' +
|
|
25
|
-
' • `detach` — gracefully reap the adapter; workflow survives (use this for most cases)\n' +
|
|
26
|
-
' • `destroy` — terminally end the workflow; abandons in-flight outbox\n' +
|
|
27
|
-
' • `restart` — reap + claim a fresh adapter with context replay');
|
|
28
|
-
});
|
|
29
|
-
}
|
package/dist/tui/client.d.ts
DELETED
package/dist/tui/client.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createTempoClient = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Re-export TempoClient from src/client/ for backward compatibility.
|
|
6
|
-
* All new consumers should import from '../client' directly.
|
|
7
|
-
*/
|
|
8
|
-
var client_1 = require("../client");
|
|
9
|
-
Object.defineProperty(exports, "createTempoClient", { enumerable: true, get: function () { return client_1.createTempoClient; } });
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Activity log — shows recent Maestro events (player joins, leaves, status changes).
|
|
3
|
-
*/
|
|
4
|
-
import React from 'react';
|
|
5
|
-
import type { MaestroEvent } from '../../types';
|
|
6
|
-
export interface ActivityLogProps {
|
|
7
|
-
events: MaestroEvent[];
|
|
8
|
-
maxVisible?: number;
|
|
9
|
-
}
|
|
10
|
-
export declare function ActivityLog({ events, maxVisible }: ActivityLogProps): React.FunctionComponentElement<{
|
|
11
|
-
flexDirection: string;
|
|
12
|
-
}> | React.CElement<{
|
|
13
|
-
flexDirection: string;
|
|
14
|
-
}, React.Component<{
|
|
15
|
-
flexDirection: string;
|
|
16
|
-
}, any, any>>;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ActivityLog = ActivityLog;
|
|
7
|
-
/**
|
|
8
|
-
* Activity log — shows recent Maestro events (player joins, leaves, status changes).
|
|
9
|
-
*/
|
|
10
|
-
const react_1 = __importDefault(require("react"));
|
|
11
|
-
const ink_context_1 = require("../ink-context");
|
|
12
|
-
const format_1 = require("../utils/format");
|
|
13
|
-
const platform_1 = require("../utils/platform");
|
|
14
|
-
function ActivityLog({ events, maxVisible = 15 }) {
|
|
15
|
-
const { Box, Text } = (0, ink_context_1.useInk)();
|
|
16
|
-
const icons = (0, platform_1.statusIcons)();
|
|
17
|
-
const visible = events.slice(-maxVisible);
|
|
18
|
-
if (visible.length === 0) {
|
|
19
|
-
return react_1.default.createElement(Box, { flexDirection: 'column' }, react_1.default.createElement(Text, { dimColor: true }, 'No events yet'));
|
|
20
|
-
}
|
|
21
|
-
return react_1.default.createElement(Box, { flexDirection: 'column' }, ...visible.map((e, i) => {
|
|
22
|
-
const time = (0, format_1.formatTime)(e.timestamp);
|
|
23
|
-
const typeLabel = (0, format_1.formatEventType)(e.type);
|
|
24
|
-
const icon = e.type === 'player_joined' ? icons.check
|
|
25
|
-
: e.type === 'player_left' ? icons.cross
|
|
26
|
-
: icons.arrow;
|
|
27
|
-
const color = e.type === 'player_joined' ? 'green'
|
|
28
|
-
: e.type === 'player_left' ? 'red'
|
|
29
|
-
: e.type === 'status_changed' ? 'yellow'
|
|
30
|
-
: 'white';
|
|
31
|
-
const detail = e.newValue
|
|
32
|
-
? ` ${icons.arrow} ${e.newValue}`
|
|
33
|
-
: '';
|
|
34
|
-
return react_1.default.createElement(Box, { key: `${e.timestamp}-${i}` }, react_1.default.createElement(Text, { dimColor: true }, `[${time}] `), react_1.default.createElement(Text, { color }, `${icon} `), react_1.default.createElement(Text, { bold: true }, e.playerId), react_1.default.createElement(Text, { dimColor: true }, ` ${typeLabel}`), react_1.default.createElement(Text, null, detail));
|
|
35
|
-
}));
|
|
36
|
-
}
|