@oracle-agent/oracle 0.35.42 → 0.35.43

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.
Files changed (42) hide show
  1. package/dist/agent-sessions.mjs +25 -24
  2. package/dist/assets/skills/oracle-desk-product/SKILL.md +1 -1
  3. package/dist/bin/desk-server.mjs +27 -27
  4. package/dist/bin/oracle-data-mcp.mjs +18 -18
  5. package/dist/bin/oracle-gateway.mjs +44 -44
  6. package/dist/bin/oracle-launch-radar-shadow.mjs +6 -6
  7. package/dist/bin/oracle-public-server.mjs +5 -5
  8. package/dist/bin/oracle-route.mjs +16 -16
  9. package/dist/bin/oracle.mjs +12 -12
  10. package/dist/cli/commands/agent-session.mjs +27 -26
  11. package/dist/cli/commands/chat.mjs +245 -224
  12. package/dist/cli/commands/eval.mjs +181 -161
  13. package/dist/cli/commands/mint.mjs +11 -11
  14. package/dist/cli/commands/model.mjs +249 -228
  15. package/dist/cli/commands/sign.mjs +14 -14
  16. package/dist/cli/commands/signer.mjs +22 -22
  17. package/dist/cli/commands/swap.mjs +13 -13
  18. package/dist/cli/commands/venues.mjs +2 -2
  19. package/dist/data/desk-data.mjs +25 -25
  20. package/dist/index.mjs +46 -46
  21. package/dist/launch-radar/index.mjs +5 -5
  22. package/dist/local-signer/anvil-attestation-worker.mjs +1 -1
  23. package/dist/local-signer/index.mjs +3 -3
  24. package/dist/local-signer/runtime.mjs +19 -19
  25. package/dist/router/prepare-route.mjs +3 -3
  26. package/dist/task.mjs +2 -2
  27. package/dist/teams/index.mjs +6 -6
  28. package/dist/tui/standalone-client.mjs +115 -95
  29. package/package.json +1 -1
  30. package/public/oracle-splash/docs/browser-and-web/index.html +2 -2
  31. package/public/oracle-splash/docs/connectors/index.html +2 -2
  32. package/public/oracle-splash/docs/getting-started/index.html +2 -2
  33. package/public/oracle-splash/docs/index.html +3 -3
  34. package/public/oracle-splash/docs/reference/index.html +2 -2
  35. package/public/oracle-splash/docs/security/index.html +2 -2
  36. package/public/oracle-splash/docs/using-oracle/index.html +2 -2
  37. package/public/oracle-splash/eval/benchmarks/evm-escrow-20260827.json +170 -0
  38. package/public/oracle-splash/eval/eval-story.css +722 -0
  39. package/public/oracle-splash/eval/eval-story.js +198 -0
  40. package/public/oracle-splash/eval/index.html +49 -11
  41. package/public/oracle-splash/eval/live-eval.js +546 -0
  42. package/server.json +2 -2
@@ -0,0 +1,546 @@
1
+ // Live evaluation feed reader for the public /eval page.
2
+ //
3
+ // Producer records are serialized by a process this page cannot audit, so
4
+ // nothing here upgrades a recorded claim into a verified one. The sha256 in a
5
+ // descriptor proves that the bytes we fetched are the bytes the producer
6
+ // hashed; it proves nothing about whether the run happened or what it did.
7
+ // Every path below fails closed: an unreadable, unexpected, or unhashed feed
8
+ // leaves the last good result on screen, labelled as cached.
9
+
10
+ export const FEED_URL = "./live/index.json";
11
+ export const POLL_INTERVAL_MS = 3000;
12
+ export const REQUEST_TIMEOUT_MS = 8000;
13
+ export const STALE_AFTER_MS = 30000;
14
+
15
+ export const SLICE_ROSTER = [
16
+ "guards",
17
+ "fidelity",
18
+ "golden-prepare",
19
+ "hybrid",
20
+ "desk",
21
+ "agentic",
22
+ "adversarial",
23
+ "contracts",
24
+ "rlm",
25
+ "native",
26
+ ];
27
+
28
+ const RUN_STATES = ["running", "passed", "failed", "blocked", "interrupted"];
29
+ const SLICE_STATES = ["queued", "running", "passed", "failed", "blocked", "skipped", "unavailable"];
30
+ const COUNT_KEYS = ["total", "pass", "fail", "skipped", "cancelled", "todo"];
31
+ const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
32
+ const COMMIT = /^[0-9a-f]{40}$/;
33
+ const SHA256 = /^[0-9a-f]{64}$/;
34
+ const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
35
+ const RECORD_PATH = /^runs\/([0-9a-f-]{36})\/([1-9][0-9]*)\.json$/;
36
+
37
+ const VERSION_NOTE = "Source package version, not a confirmed installed release.";
38
+ const RECORDED_PASS = "recorded pass \u00b7 unattested";
39
+
40
+ const RUN_PRESENTATION = {
41
+ running: { label: "running", tone: "blue" },
42
+ passed: { label: RECORDED_PASS, tone: "amber" },
43
+ failed: { label: "recorded failure", tone: "red" },
44
+ blocked: { label: "blocked", tone: "red" },
45
+ interrupted: { label: "interrupted", tone: "red" },
46
+ };
47
+
48
+ const SLICE_PRESENTATION = {
49
+ queued: { label: "queued", tone: "muted" },
50
+ running: RUN_PRESENTATION.running,
51
+ passed: RUN_PRESENTATION.passed,
52
+ failed: RUN_PRESENTATION.failed,
53
+ blocked: RUN_PRESENTATION.blocked,
54
+ skipped: { label: "skipped", tone: "amber" },
55
+ unavailable: { label: "not available", tone: "amber" },
56
+ };
57
+
58
+ const fail = (error) => ({ ok: false, error });
59
+ const isPlainObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
60
+ const isIso = (value) => typeof value === "string" && value.length > 0 && Number.isFinite(Date.parse(value));
61
+ const isCount = (value) => Number.isSafeInteger(value) && value >= 0;
62
+
63
+ function sameSource(a, b) {
64
+ if (!isPlainObject(a) || !isPlainObject(b)) return false;
65
+ return a.commit === b.commit && a.dirty === b.dirty && a.node === b.node;
66
+ }
67
+
68
+ function validSource(source) {
69
+ if (!isPlainObject(source)) return false;
70
+ if (typeof source.commit !== "string") return false;
71
+ if (source.commit !== "unknown" && !COMMIT.test(source.commit)) return false;
72
+ if (source.dirty !== null && typeof source.dirty !== "boolean") return false;
73
+ return typeof source.node === "string" && source.node.length > 0;
74
+ }
75
+
76
+ export function safeLiveUrl(recordPath) {
77
+ if (typeof recordPath !== "string" || !RECORD_PATH.test(recordPath)) return null;
78
+ return `./live/${recordPath}`;
79
+ }
80
+
81
+ export function validateDescriptor(descriptor) {
82
+ if (!isPlainObject(descriptor)) return fail("descriptor is not an object");
83
+ const { runId, startedAt, updatedAt, finishedAt, status, scope } = descriptor;
84
+ if (typeof runId !== "string" || !UUID_V4.test(runId)) return fail("run id is not a lowercase uuid v4");
85
+ if (!RUN_STATES.includes(status)) return fail("unknown run status");
86
+ if (scope !== "development") return fail("scope must be development");
87
+ if (!isIso(startedAt) || !isIso(updatedAt)) return fail("start or update time is not an ISO instant");
88
+ if (Date.parse(updatedAt) < Date.parse(startedAt)) return fail("update time precedes start time");
89
+ if (status === "running") {
90
+ if (finishedAt !== null) return fail("a running run cannot have a finish time");
91
+ } else {
92
+ if (!isIso(finishedAt)) return fail("a completed run needs a finish time");
93
+ if (Date.parse(finishedAt) < Date.parse(startedAt)) return fail("finish time precedes start time");
94
+ }
95
+ if (typeof descriptor.packageVersion !== "string" || !SEMVER.test(descriptor.packageVersion)) return fail("bad package version");
96
+ if (typeof descriptor.target !== "string" || descriptor.target.trim().length === 0) return fail("target is empty");
97
+ if (!validSource(descriptor.source)) return fail("source provenance is incomplete");
98
+ if (typeof descriptor.sha256 !== "string" || !SHA256.test(descriptor.sha256)) return fail("digest is not 64 lowercase hex");
99
+ const match = RECORD_PATH.exec(String(descriptor.record ?? ""));
100
+ if (!match) return fail("record path is not runs/<runId>/<revision>.json");
101
+ if (match[1] !== runId) return fail("record path belongs to another run");
102
+ return { ok: true, revision: Number(match[2]) };
103
+ }
104
+
105
+ export function recordLocation(descriptor) {
106
+ const checked = validateDescriptor(descriptor);
107
+ if (!checked.ok) return null;
108
+ const url = safeLiveUrl(descriptor.record);
109
+ if (!url) return null;
110
+ return { url, revision: checked.revision };
111
+ }
112
+
113
+ export function validateManifest(manifest) {
114
+ if (!isPlainObject(manifest)) return { ...fail("feed is not an object"), runs: [] };
115
+ if (manifest.schemaVersion !== 1) return { ...fail("unsupported feed schema"), runs: [] };
116
+ if (manifest.kind !== "oracle-eval-feed") return { ...fail("unexpected feed kind"), runs: [] };
117
+ if (!isIso(manifest.updatedAt)) return { ...fail("feed update time is not an ISO instant"), runs: [] };
118
+ if (!Array.isArray(manifest.runs)) return { ...fail("feed runs is not a list"), runs: [] };
119
+
120
+ const seen = new Set();
121
+ let previous = Infinity;
122
+ for (const descriptor of manifest.runs) {
123
+ const checked = validateDescriptor(descriptor);
124
+ if (!checked.ok) return { ...fail(checked.error), runs: [] };
125
+ if (seen.has(descriptor.runId)) return { ...fail("duplicate run id in feed"), runs: [] };
126
+ seen.add(descriptor.runId);
127
+ const at = Date.parse(descriptor.startedAt);
128
+ if (at > previous) return { ...fail("feed runs are not newest first"), runs: [] };
129
+ previous = at;
130
+ }
131
+ return { ok: true, runs: manifest.runs, updatedAt: manifest.updatedAt };
132
+ }
133
+
134
+ function validateCounts(counts) {
135
+ if (counts === undefined) return true;
136
+ if (!isPlainObject(counts)) return false;
137
+ for (const key of Object.keys(counts)) if (!COUNT_KEYS.includes(key)) return false;
138
+ for (const key of ["total", "pass", "fail"]) if (!isCount(counts[key])) return false;
139
+ let sum = counts.pass + counts.fail;
140
+ for (const key of ["skipped", "cancelled", "todo"]) {
141
+ if (counts[key] === undefined) continue;
142
+ if (!isCount(counts[key])) return false;
143
+ sum += counts[key];
144
+ }
145
+ return sum === counts.total;
146
+ }
147
+
148
+ export function validateRecord(record, descriptor) {
149
+ const bound = validateDescriptor(descriptor);
150
+ if (!bound.ok) return fail(bound.error);
151
+ if (!isPlainObject(record)) return fail("record is not an object");
152
+ if (record.schemaVersion !== 1) return fail("unsupported record schema");
153
+ if (record.kind !== "oracle-eval-run") return fail("unexpected record kind");
154
+ if (record.package !== "@oracle-agent/oracle") return fail("unexpected package");
155
+ if (record.scope !== "development") return fail("scope must be development");
156
+ if (record.runId !== descriptor.runId) return fail("record run id does not match the descriptor");
157
+ if (!Number.isInteger(record.revision) || record.revision < 1) return fail("revision is not a positive integer");
158
+ if (record.revision !== bound.revision) return fail("revision does not match the record path");
159
+
160
+ for (const key of ["startedAt", "updatedAt", "finishedAt", "status", "packageVersion", "target"]) {
161
+ if (record[key] !== descriptor[key]) return fail(`record ${key} does not match the descriptor`);
162
+ }
163
+ if (!sameSource(record.source, descriptor.source)) return fail("record source does not match the descriptor");
164
+
165
+ const selected = record.selectedSlices;
166
+ if (!Array.isArray(selected) || selected.length === 0) return fail("no slices were selected");
167
+ const wanted = new Set();
168
+ for (const name of selected) {
169
+ if (!SLICE_ROSTER.includes(name)) return fail(`unknown slice ${String(name)}`);
170
+ if (wanted.has(name)) return fail("duplicate slice in the selection");
171
+ wanted.add(name);
172
+ }
173
+ if (!isPlainObject(record.slices)) return fail("slices is not an object");
174
+ const reported = Object.keys(record.slices);
175
+ for (const name of reported) {
176
+ if (!wanted.has(name)) return fail(`slice ${name} was reported without being selected`);
177
+ }
178
+ for (const name of wanted) {
179
+ if (!reported.includes(name)) return fail(`selected slice ${name} has no entry`);
180
+ const slice = record.slices[name];
181
+ if (!isPlainObject(slice)) return fail(`slice ${name} is not an object`);
182
+ if (!SLICE_STATES.includes(slice.status)) return fail(`slice ${name} has an unknown status`);
183
+ if (!validateCounts(slice.counts)) return fail(`slice ${name} has counts that do not reconcile`);
184
+ if (slice.durationMs !== undefined && !isCount(slice.durationMs)) return fail(`slice ${name} has a bad duration`);
185
+ if (slice.summary !== undefined && typeof slice.summary !== "string") return fail(`slice ${name} has a bad summary`);
186
+ if (slice.status === "passed" && slice.counts && (slice.counts.pass < 1 || slice.counts.fail > 0
187
+ || slice.counts.cancelled > 0 || slice.counts.todo > 0)) return fail(`slice ${name} contradicts its pass claim`);
188
+ }
189
+ if (record.status === "passed" && Object.values(record.slices).some((slice) => slice.status !== "passed")) return fail("run pass contradicts selected slices");
190
+
191
+ const evidence = record.evidence;
192
+ if (!isPlainObject(evidence)) return fail("evidence block is missing");
193
+ if (evidence.attested !== false) return fail("records are never attested");
194
+ if (typeof evidence.projection !== "string" || evidence.projection.length === 0) return fail("evidence projection is missing");
195
+ return { ok: true };
196
+ }
197
+
198
+ export function isCanonicalRecordText(text, record) {
199
+ return typeof text === "string" && text === `${JSON.stringify(record)}\n`;
200
+ }
201
+
202
+ export async function digestHex(text) {
203
+ const bytes = new TextEncoder().encode(text);
204
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
205
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
206
+ }
207
+
208
+ export async function verifyBytes(text, expected) {
209
+ if (typeof expected !== "string" || !SHA256.test(expected)) return false;
210
+ const actual = await digestHex(text);
211
+ if (actual.length !== expected.length) return false;
212
+ let diff = 0;
213
+ for (let index = 0; index < actual.length; index += 1) diff |= actual.charCodeAt(index) ^ expected.charCodeAt(index);
214
+ return diff === 0;
215
+ }
216
+
217
+ function formatDuration(durationMs) {
218
+ if (durationMs < 1000) return `${durationMs} ms`;
219
+ return `${Number((durationMs / 1000).toFixed(2))} s`;
220
+ }
221
+
222
+ function presentSlice(name, record) {
223
+ const selected = record.selectedSlices.includes(name);
224
+ if (!selected) {
225
+ return {
226
+ name,
227
+ selected: false,
228
+ status: "not-run",
229
+ label: "not run",
230
+ tone: "muted",
231
+ counts: null,
232
+ countsLabel: "not run",
233
+ durationLabel: null,
234
+ summary: null,
235
+ };
236
+ }
237
+ const slice = record.slices[name];
238
+ const presentation = SLICE_PRESENTATION[slice.status];
239
+ const counts = slice.counts ?? null;
240
+ return {
241
+ name,
242
+ selected: true,
243
+ status: slice.status,
244
+ label: presentation.label,
245
+ tone: presentation.tone,
246
+ counts,
247
+ countsLabel: counts ? `${counts.pass} passed, ${counts.fail} failed of ${counts.total}` : "counts not published",
248
+ durationLabel: isCount(slice.durationMs) ? formatDuration(slice.durationMs) : null,
249
+ summary: typeof slice.summary === "string" ? slice.summary : null,
250
+ };
251
+ }
252
+
253
+ export function presentRun(record, descriptor, now) {
254
+ const presentation = RUN_PRESENTATION[record.status];
255
+ const running = record.status === "running";
256
+ const stale = running && now - Date.parse(record.updatedAt) > STALE_AFTER_MS;
257
+ const liveness = running ? (stale ? "stalled" : "live") : "historical";
258
+ const location = recordLocation(descriptor);
259
+ const dirty = record.source.dirty;
260
+ return {
261
+ runId: record.runId,
262
+ revision: record.revision,
263
+ statusLabel: presentation.label,
264
+ statusTone: presentation.tone,
265
+ liveness,
266
+ livenessLabel: liveness === "stalled"
267
+ ? "stalled / no recent update"
268
+ : liveness === "live" ? "live \u00b7 updating" : "completed record",
269
+ target: record.target,
270
+ commit: record.source.commit,
271
+ node: record.source.node,
272
+ dirtyLabel: dirty === true ? "dirty working tree" : dirty === false ? "clean working tree" : "working tree state unknown",
273
+ packageVersion: record.packageVersion,
274
+ packageVersionNote: VERSION_NOTE,
275
+ startedAt: record.startedAt,
276
+ updatedAt: record.updatedAt,
277
+ finishedAt: record.finishedAt,
278
+ recordUrl: location ? location.url : null,
279
+ selectedCount: record.selectedSlices.length,
280
+ slices: SLICE_ROSTER.map((name) => presentSlice(name, record)),
281
+ };
282
+ }
283
+
284
+ export function nextSelection(value) {
285
+ if (typeof value === "string" && UUID_V4.test(value)) return { mode: "pinned", runId: value };
286
+ return { mode: "latest" };
287
+ }
288
+
289
+ export function presentHistory(runs, selection) {
290
+ const list = Array.isArray(runs) ? runs : [];
291
+ const pinned = selection?.mode === "pinned" ? selection.runId : null;
292
+ const selected = pinned ? list.find((run) => run.runId === pinned) ?? null : list[0] ?? null;
293
+ const options = [{ value: "latest", label: "Follow latest", selected: !pinned }];
294
+ for (const run of list) {
295
+ options.push({
296
+ value: run.runId,
297
+ label: `${run.startedAt} \u00b7 ${run.status} \u00b7 ${run.runId.slice(0, 8)}`,
298
+ selected: run.runId === pinned,
299
+ });
300
+ }
301
+ return { options, selected, missing: Boolean(pinned) && selected === null };
302
+ }
303
+
304
+ export function initialState() {
305
+ return {
306
+ phase: "waiting",
307
+ statusLabel: "Waiting for the first published run.",
308
+ error: null,
309
+ manifest: null,
310
+ lastGood: null,
311
+ selection: { mode: "latest" },
312
+ checkedAt: null,
313
+ };
314
+ }
315
+
316
+ export function selectRun(state, value) {
317
+ return { ...state, selection: nextSelection(value) };
318
+ }
319
+
320
+ export function applyFeedResult(state, result, now) {
321
+ if (result?.ok) {
322
+ const checked = validateManifest(result.manifest);
323
+ if (!checked.ok) return applyFeedResult(state, { ok: false, kind: "invalid", error: checked.error }, now);
324
+ const manifest = { ...result.manifest, runs: checked.runs };
325
+ if (checked.runs.length === 0) {
326
+ return { ...state, phase: "waiting", statusLabel: "Waiting for the first published run.", error: null, manifest, lastGood: state.lastGood, checkedAt: now };
327
+ }
328
+ return { ...state, phase: "live", statusLabel: "Live feed", error: null, manifest, lastGood: { manifest, at: now }, checkedAt: now };
329
+ }
330
+ const error = result?.error ?? "feed unavailable";
331
+ if (!state.lastGood) {
332
+ return { ...state, phase: "disconnected", statusLabel: "Feed disconnected or invalid. No results are shown.", error, manifest: null, lastGood: null, checkedAt: now };
333
+ }
334
+ return { ...state, phase: "cached", statusLabel: "Cached result, not live. The feed is disconnected or invalid.", error, manifest: state.lastGood.manifest, checkedAt: now };
335
+ }
336
+
337
+ export function createPoller({ run, intervalMs = POLL_INTERVAL_MS, schedule, cancel }) {
338
+ let inFlight = null;
339
+ let chain = Promise.resolve();
340
+ let timer = null;
341
+ let stopped = false;
342
+
343
+ function tick() {
344
+ if (stopped || inFlight) return inFlight ?? Promise.resolve();
345
+ if (timer !== null) cancel(timer);
346
+ timer = null;
347
+ let started;
348
+ try {
349
+ started = Promise.resolve(run());
350
+ } catch (error) {
351
+ started = Promise.resolve();
352
+ }
353
+ chain = started.catch(() => {}).then(() => {
354
+ inFlight = null;
355
+ if (!stopped) timer = schedule(tick, intervalMs);
356
+ });
357
+ inFlight = chain;
358
+ return chain;
359
+ }
360
+
361
+ return {
362
+ tick,
363
+ settled: () => chain,
364
+ stop() {
365
+ stopped = true;
366
+ if (timer !== null) cancel(timer);
367
+ timer = null;
368
+ },
369
+ };
370
+ }
371
+
372
+ async function fetchFeed(url = FEED_URL) {
373
+ const controller = new AbortController();
374
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
375
+ try {
376
+ const response = await fetch(url, { cache: "no-store", signal: controller.signal, credentials: "omit", redirect: "error" });
377
+ if (!response.ok) return { ok: false, kind: "disconnected", error: `feed responded ${response.status}` };
378
+ return { ok: true, manifest: JSON.parse(await response.text()) };
379
+ } catch (error) {
380
+ return { ok: false, kind: "disconnected", error: "feed request failed" };
381
+ } finally {
382
+ clearTimeout(timeout);
383
+ }
384
+ }
385
+
386
+ async function fetchRecord(descriptor) {
387
+ const location = recordLocation(descriptor);
388
+ if (!location) return { ok: false, error: "record path rejected" };
389
+ const controller = new AbortController();
390
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
391
+ try {
392
+ const response = await fetch(location.url, { cache: "no-store", signal: controller.signal, credentials: "omit", redirect: "error" });
393
+ if (!response.ok) return { ok: false, error: `record responded ${response.status}` };
394
+ const text = await response.text();
395
+ if (!await verifyBytes(text, descriptor.sha256)) return { ok: false, error: "record digest mismatch" };
396
+ const record = JSON.parse(text);
397
+ if (!isCanonicalRecordText(text, record)) return { ok: false, error: "record bytes are not canonical" };
398
+ const checked = validateRecord(record, descriptor);
399
+ if (!checked.ok) return { ok: false, error: checked.error };
400
+ return { ok: true, record };
401
+ } catch (error) {
402
+ return { ok: false, error: "record request failed" };
403
+ } finally {
404
+ clearTimeout(timeout);
405
+ }
406
+ }
407
+
408
+ function setText(root, id, value) {
409
+ const node = root.getElementById(id);
410
+ if (node) node.textContent = value;
411
+ }
412
+
413
+ function textNode(root, tag, value, className) {
414
+ const node = root.createElement(tag);
415
+ node.textContent = value;
416
+ if (className) node.className = className;
417
+ return node;
418
+ }
419
+
420
+ function renderSlices(root, view) {
421
+ const grid = root.getElementById("live-slices");
422
+ if (!grid) return;
423
+ grid.replaceChildren();
424
+ for (const slice of view.slices) {
425
+ const card = root.createElement("article");
426
+ card.className = "live-card";
427
+ card.setAttribute("data-slice", slice.name);
428
+ card.setAttribute("data-state", slice.status);
429
+ card.setAttribute("data-tone", slice.tone);
430
+ const head = root.createElement("div");
431
+ head.className = "live-card-head";
432
+ head.append(textNode(root, "h3", slice.name), textNode(root, "span", slice.label, "live-pill"));
433
+ card.append(head, textNode(root, "p", slice.countsLabel, "live-card-counts"));
434
+ if (slice.durationLabel) card.append(textNode(root, "p", slice.durationLabel));
435
+ if (slice.summary) card.append(textNode(root, "p", slice.summary));
436
+ grid.append(card);
437
+ }
438
+ }
439
+
440
+ function renderHistory(root, history) {
441
+ const picker = root.getElementById("live-run-select");
442
+ if (!picker) return;
443
+ picker.replaceChildren();
444
+ for (const option of history.options) {
445
+ const node = root.createElement("option");
446
+ node.value = option.value;
447
+ node.textContent = option.label;
448
+ if (option.selected) node.selected = true;
449
+ picker.append(node);
450
+ }
451
+ }
452
+
453
+ function renderRecordLink(root, url) {
454
+ const link = root.getElementById("live-record");
455
+ const none = root.getElementById("live-record-none");
456
+ if (!link || !none) return;
457
+ if (url) {
458
+ link.setAttribute("href", url);
459
+ link.hidden = false;
460
+ none.hidden = true;
461
+ } else {
462
+ link.removeAttribute("href");
463
+ link.hidden = true;
464
+ none.hidden = false;
465
+ }
466
+ }
467
+
468
+ const DETAIL_FIELDS = [
469
+ ["live-run-status", (view) => view.statusLabel],
470
+ ["live-liveness", (view) => view.livenessLabel],
471
+ ["live-target", (view) => view.target],
472
+ ["live-commit", (view) => view.commit],
473
+ ["live-dirty", (view) => view.dirtyLabel],
474
+ ["live-version", (view) => view.packageVersion],
475
+ ["live-started", (view) => view.startedAt],
476
+ ["live-updated", (view) => view.updatedAt],
477
+ ];
478
+
479
+ function renderFeedStatus(root, phase, label) {
480
+ const banner = root.getElementById("live-feed-status");
481
+ if (banner) banner.setAttribute("data-phase", phase);
482
+ setText(root, "live-feed-status", label);
483
+ }
484
+
485
+ function renderEmpty(root, phase, label) {
486
+ renderFeedStatus(root, phase, label);
487
+ for (const [id] of DETAIL_FIELDS) setText(root, id, "not published");
488
+ renderRecordLink(root, null);
489
+ const grid = root.getElementById("live-slices");
490
+ if (grid) grid.replaceChildren();
491
+ }
492
+
493
+ export function mount(root = globalThis.document) {
494
+ if (typeof root === "undefined" || !root || typeof root.getElementById !== "function") return null;
495
+ if (!root.getElementById("live-eval")) return null;
496
+
497
+ let state = initialState();
498
+ let lastRecord = null;
499
+ const announce = (record, connection) => root.dispatchEvent(new CustomEvent("oracle:eval-record", { detail: { record, connection } }));
500
+
501
+ async function refresh() {
502
+ state = applyFeedResult(state, await fetchFeed(), Date.now());
503
+ const history = presentHistory(state.manifest?.runs ?? [], state.selection);
504
+ renderHistory(root, history);
505
+ if (!history.selected) {
506
+ renderEmpty(root, state.phase, history.missing ? "The selected run is no longer published." : state.statusLabel);
507
+ announce(null, state.phase);
508
+ return;
509
+ }
510
+ const fetched = await fetchRecord(history.selected);
511
+ if (!fetched.ok) {
512
+ const label = `Record rejected, not live: ${fetched.error}`;
513
+ if (lastRecord?.runId === history.selected.runId) {
514
+ renderFeedStatus(root, "cached", label);
515
+ setText(root, "live-liveness", "cached result, not live");
516
+ } else renderEmpty(root, "disconnected", label);
517
+ announce(lastRecord, "cached");
518
+ return;
519
+ }
520
+ lastRecord = fetched.record;
521
+ const view = presentRun(fetched.record, history.selected, Date.now());
522
+ if (state.phase !== "live") view.livenessLabel = "cached result, not live";
523
+ renderFeedStatus(root, state.phase, state.statusLabel);
524
+ for (const [id, read] of DETAIL_FIELDS) setText(root, id, read(view));
525
+ renderRecordLink(root, view.recordUrl);
526
+ renderSlices(root, view);
527
+ announce(fetched.record, state.phase);
528
+ }
529
+
530
+ const poller = createPoller({
531
+ run: refresh,
532
+ schedule: (fn, ms) => globalThis.setTimeout(fn, ms),
533
+ cancel: (handle) => globalThis.clearTimeout(handle),
534
+ });
535
+
536
+ const picker = root.getElementById("live-run-select");
537
+ if (picker) picker.addEventListener("change", (event) => {
538
+ state = selectRun(state, event.target.value);
539
+ poller.tick();
540
+ });
541
+
542
+ poller.tick();
543
+ return poller;
544
+ }
545
+
546
+ if (typeof document !== "undefined") mount(document);
package/server.json CHANGED
@@ -3,12 +3,12 @@
3
3
  "name": "io.github.demi-hl/oracle",
4
4
  "description": "Keyless multichain crypto desk: chain reads + prepare-only unsigned txs. Your wallet signs.",
5
5
  "websiteUrl": "https://oracle.demi.la",
6
- "version": "0.35.42",
6
+ "version": "0.35.43",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "@oracle-agent/oracle",
11
- "version": "0.35.42",
11
+ "version": "0.35.43",
12
12
  "transport": {
13
13
  "type": "stdio"
14
14
  },