@openscout/scout 0.2.53 → 0.2.54

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.
@@ -7,8 +7,8 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&family=Spectral:wght@500;600&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-DzoSArBb.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-DYZld5kI.css">
10
+ <script type="module" crossorigin src="/assets/index-D0N02a0Z.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-CazCcdzO.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
package/dist/main.mjs CHANGED
@@ -4899,6 +4899,55 @@ class BaseAdapter {
4899
4899
  this.emit("event", { event: "session:update", session: { ...this.session } });
4900
4900
  }
4901
4901
  }
4902
+
4903
+ // ../agent-sessions/src/protocol/approval-normalization.ts
4904
+ function defaultApprovalTitle(block) {
4905
+ switch (block.action.kind) {
4906
+ case "command":
4907
+ return "Approve Command";
4908
+ case "file_change":
4909
+ return "Approve File Change";
4910
+ case "tool_call":
4911
+ return "Approve Tool Call";
4912
+ case "subagent":
4913
+ return "Approve Subagent";
4914
+ }
4915
+ }
4916
+ function defaultApprovalDetail(block) {
4917
+ switch (block.action.kind) {
4918
+ case "command":
4919
+ return block.action.command?.trim() || null;
4920
+ case "file_change":
4921
+ return block.action.path?.trim() || null;
4922
+ case "tool_call":
4923
+ return block.action.toolName?.trim() || null;
4924
+ case "subagent":
4925
+ return block.action.agentName?.trim() || block.action.agentId?.trim() || null;
4926
+ }
4927
+ }
4928
+ function normalizeApprovalRequest(session, turnId, block) {
4929
+ if (block.action.status !== "awaiting_approval" || !block.action.approval) {
4930
+ return null;
4931
+ }
4932
+ const title = defaultApprovalTitle(block);
4933
+ const detail = defaultApprovalDetail(block);
4934
+ const description = block.action.approval.description?.trim() || detail || title;
4935
+ return {
4936
+ sessionId: session.id,
4937
+ sessionName: session.name,
4938
+ adapterType: session.adapterType,
4939
+ turnId,
4940
+ blockId: block.id,
4941
+ version: block.action.approval.version,
4942
+ risk: block.action.approval.risk ?? "medium",
4943
+ title,
4944
+ description,
4945
+ detail,
4946
+ actionKind: block.action.kind,
4947
+ actionStatus: block.action.status
4948
+ };
4949
+ }
4950
+
4902
4951
  // ../agent-sessions/src/state.ts
4903
4952
  class StateTracker {
4904
4953
  states = new Map;
@@ -50795,6 +50844,68 @@ function getEventSessionId(event) {
50795
50844
  function trackedSequencedEventId(event) {
50796
50845
  return `${getEventSessionId(event) ?? "unknown"}:${event.seq}`;
50797
50846
  }
50847
+ function approvalInboxItemId(sessionId, turnId, blockId, version2) {
50848
+ return `approval:${sessionId}:${turnId}:${blockId}:v${version2}`;
50849
+ }
50850
+ function projectApprovalInboxItem(snapshot, turn, block) {
50851
+ const normalized = normalizeApprovalRequest(snapshot.session, turn.id, block);
50852
+ if (!normalized) {
50853
+ return null;
50854
+ }
50855
+ return {
50856
+ id: approvalInboxItemId(normalized.sessionId, normalized.turnId, normalized.blockId, normalized.version),
50857
+ kind: "approval",
50858
+ createdAt: turn.startedAt,
50859
+ sessionId: normalized.sessionId,
50860
+ sessionName: normalized.sessionName,
50861
+ adapterType: normalized.adapterType,
50862
+ turnId: normalized.turnId,
50863
+ blockId: normalized.blockId,
50864
+ version: normalized.version,
50865
+ risk: normalized.risk,
50866
+ title: normalized.title,
50867
+ description: normalized.description,
50868
+ detail: normalized.detail,
50869
+ actionKind: normalized.actionKind,
50870
+ actionStatus: normalized.actionStatus
50871
+ };
50872
+ }
50873
+ function lookupMobileInboxApprovalItem(bridge, sessionId, turnId, blockId) {
50874
+ const snapshot = bridge.getSessionSnapshot(sessionId);
50875
+ if (!snapshot) {
50876
+ return null;
50877
+ }
50878
+ const turn = snapshot.turns.find((candidate) => candidate.id === turnId);
50879
+ if (!turn) {
50880
+ return null;
50881
+ }
50882
+ const blockState = turn.blocks.find((candidate) => candidate.block.id === blockId);
50883
+ if (!blockState || blockState.block.type !== "action") {
50884
+ return null;
50885
+ }
50886
+ return projectApprovalInboxItem(snapshot, turn, blockState.block);
50887
+ }
50888
+ function queryMobileInboxItems(bridge) {
50889
+ const items = [];
50890
+ for (const session of bridge.getSessionSummaries()) {
50891
+ const snapshot = bridge.getSessionSnapshot(session.sessionId);
50892
+ if (!snapshot) {
50893
+ continue;
50894
+ }
50895
+ for (const turn of snapshot.turns) {
50896
+ for (const blockState of turn.blocks) {
50897
+ if (blockState.block.type !== "action") {
50898
+ continue;
50899
+ }
50900
+ const item = projectApprovalInboxItem(snapshot, turn, blockState.block);
50901
+ if (item) {
50902
+ items.push(item);
50903
+ }
50904
+ }
50905
+ }
50906
+ }
50907
+ return items.sort((left, right) => right.createdAt - left.createdAt || left.id.localeCompare(right.id));
50908
+ }
50798
50909
  function toTRPCRegistryError(error48) {
50799
50910
  if (!isSessionRegistryError(error48)) {
50800
50911
  return null;
@@ -50919,6 +51030,9 @@ var init_router = __esm(async () => {
50919
51030
  })
50920
51031
  });
50921
51032
  mobileRouter = t.router({
51033
+ inbox: procedure.query(({ ctx }) => ({
51034
+ items: queryMobileInboxItems(ctx.bridge)
51035
+ })),
50922
51036
  home: procedure.input(exports_external.object({
50923
51037
  workspaceLimit: exports_external.number().optional(),
50924
51038
  agentLimit: exports_external.number().optional(),
@@ -55576,6 +55690,16 @@ function startBridgeServerTRPC(options) {
55576
55690
  state.unsub = bridge.onEvent((sequenced) => {
55577
55691
  logBridgeEvent(sequenced.event, sequenced.seq);
55578
55692
  sendEvent(JSON.stringify({ seq: sequenced.seq, event: sequenced.event }));
55693
+ if (sequenced.event.event === "block:action:approval") {
55694
+ const item = lookupMobileInboxApprovalItem(bridge, sequenced.event.sessionId, sequenced.event.turnId, sequenced.event.blockId);
55695
+ if (item) {
55696
+ sendEvent(JSON.stringify({
55697
+ event: "operator:notify",
55698
+ tier: "interrupt",
55699
+ item
55700
+ }));
55701
+ }
55702
+ }
55579
55703
  });
55580
55704
  }
55581
55705
  const server = Bun.serve({
@@ -3420,6 +3420,53 @@ class BaseAdapter {
3420
3420
  this.emit("event", { event: "session:update", session: { ...this.session } });
3421
3421
  }
3422
3422
  }
3423
+ // ../agent-sessions/src/protocol/approval-normalization.ts
3424
+ function defaultApprovalTitle(block) {
3425
+ switch (block.action.kind) {
3426
+ case "command":
3427
+ return "Approve Command";
3428
+ case "file_change":
3429
+ return "Approve File Change";
3430
+ case "tool_call":
3431
+ return "Approve Tool Call";
3432
+ case "subagent":
3433
+ return "Approve Subagent";
3434
+ }
3435
+ }
3436
+ function defaultApprovalDetail(block) {
3437
+ switch (block.action.kind) {
3438
+ case "command":
3439
+ return block.action.command?.trim() || null;
3440
+ case "file_change":
3441
+ return block.action.path?.trim() || null;
3442
+ case "tool_call":
3443
+ return block.action.toolName?.trim() || null;
3444
+ case "subagent":
3445
+ return block.action.agentName?.trim() || block.action.agentId?.trim() || null;
3446
+ }
3447
+ }
3448
+ function normalizeApprovalRequest(session, turnId, block) {
3449
+ if (block.action.status !== "awaiting_approval" || !block.action.approval) {
3450
+ return null;
3451
+ }
3452
+ const title = defaultApprovalTitle(block);
3453
+ const detail = defaultApprovalDetail(block);
3454
+ const description = block.action.approval.description?.trim() || detail || title;
3455
+ return {
3456
+ sessionId: session.id,
3457
+ sessionName: session.name,
3458
+ adapterType: session.adapterType,
3459
+ turnId,
3460
+ blockId: block.id,
3461
+ version: block.action.approval.version,
3462
+ risk: block.action.approval.risk ?? "medium",
3463
+ title,
3464
+ description,
3465
+ detail,
3466
+ actionKind: block.action.kind,
3467
+ actionStatus: block.action.status
3468
+ };
3469
+ }
3423
3470
  // ../agent-sessions/src/state.ts
3424
3471
  class StateTracker {
3425
3472
  states = new Map;
@@ -30357,6 +30404,68 @@ function getEventSessionId(event) {
30357
30404
  function trackedSequencedEventId(event) {
30358
30405
  return `${getEventSessionId(event) ?? "unknown"}:${event.seq}`;
30359
30406
  }
30407
+ function approvalInboxItemId(sessionId, turnId, blockId, version2) {
30408
+ return `approval:${sessionId}:${turnId}:${blockId}:v${version2}`;
30409
+ }
30410
+ function projectApprovalInboxItem(snapshot, turn, block) {
30411
+ const normalized = normalizeApprovalRequest(snapshot.session, turn.id, block);
30412
+ if (!normalized) {
30413
+ return null;
30414
+ }
30415
+ return {
30416
+ id: approvalInboxItemId(normalized.sessionId, normalized.turnId, normalized.blockId, normalized.version),
30417
+ kind: "approval",
30418
+ createdAt: turn.startedAt,
30419
+ sessionId: normalized.sessionId,
30420
+ sessionName: normalized.sessionName,
30421
+ adapterType: normalized.adapterType,
30422
+ turnId: normalized.turnId,
30423
+ blockId: normalized.blockId,
30424
+ version: normalized.version,
30425
+ risk: normalized.risk,
30426
+ title: normalized.title,
30427
+ description: normalized.description,
30428
+ detail: normalized.detail,
30429
+ actionKind: normalized.actionKind,
30430
+ actionStatus: normalized.actionStatus
30431
+ };
30432
+ }
30433
+ function lookupMobileInboxApprovalItem(bridge, sessionId, turnId, blockId) {
30434
+ const snapshot = bridge.getSessionSnapshot(sessionId);
30435
+ if (!snapshot) {
30436
+ return null;
30437
+ }
30438
+ const turn = snapshot.turns.find((candidate) => candidate.id === turnId);
30439
+ if (!turn) {
30440
+ return null;
30441
+ }
30442
+ const blockState = turn.blocks.find((candidate) => candidate.block.id === blockId);
30443
+ if (!blockState || blockState.block.type !== "action") {
30444
+ return null;
30445
+ }
30446
+ return projectApprovalInboxItem(snapshot, turn, blockState.block);
30447
+ }
30448
+ function queryMobileInboxItems(bridge) {
30449
+ const items = [];
30450
+ for (const session of bridge.getSessionSummaries()) {
30451
+ const snapshot = bridge.getSessionSnapshot(session.sessionId);
30452
+ if (!snapshot) {
30453
+ continue;
30454
+ }
30455
+ for (const turn of snapshot.turns) {
30456
+ for (const blockState of turn.blocks) {
30457
+ if (blockState.block.type !== "action") {
30458
+ continue;
30459
+ }
30460
+ const item = projectApprovalInboxItem(snapshot, turn, blockState.block);
30461
+ if (item) {
30462
+ items.push(item);
30463
+ }
30464
+ }
30465
+ }
30466
+ }
30467
+ return items.sort((left, right) => right.createdAt - left.createdAt || left.id.localeCompare(right.id));
30468
+ }
30360
30469
  function toTRPCRegistryError(error48) {
30361
30470
  if (!isSessionRegistryError(error48)) {
30362
30471
  return null;
@@ -30431,6 +30540,9 @@ var sessionRouter = t.router({
30431
30540
  })
30432
30541
  });
30433
30542
  var mobileRouter = t.router({
30543
+ inbox: procedure.query(({ ctx }) => ({
30544
+ items: queryMobileInboxItems(ctx.bridge)
30545
+ })),
30434
30546
  home: procedure.input(exports_external.object({
30435
30547
  workspaceLimit: exports_external.number().optional(),
30436
30548
  agentLimit: exports_external.number().optional(),
@@ -35006,6 +35118,16 @@ function startBridgeServerTRPC(options) {
35006
35118
  state.unsub = bridge.onEvent((sequenced) => {
35007
35119
  logBridgeEvent(sequenced.event, sequenced.seq);
35008
35120
  sendEvent(JSON.stringify({ seq: sequenced.seq, event: sequenced.event }));
35121
+ if (sequenced.event.event === "block:action:approval") {
35122
+ const item = lookupMobileInboxApprovalItem(bridge, sequenced.event.sessionId, sequenced.event.turnId, sequenced.event.blockId);
35123
+ if (item) {
35124
+ sendEvent(JSON.stringify({
35125
+ event: "operator:notify",
35126
+ tier: "interrupt",
35127
+ item
35128
+ }));
35129
+ }
35130
+ }
35009
35131
  });
35010
35132
  }
35011
35133
  const server = Bun.serve({
@@ -14,7 +14,6 @@ var __export = (target, all) => {
14
14
  });
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
- var __require = import.meta.require;
18
17
  // packages/agent-sessions/src/protocol/adapter.ts
19
18
  class BaseAdapter {
20
19
  config;
@@ -11852,7 +11851,7 @@ var init_local_agents = __esm(async () => {
11852
11851
 
11853
11852
  // packages/web/server/index.ts
11854
11853
  import { existsSync as existsSync13 } from "fs";
11855
- import { dirname as dirname12, join as join20 } from "path";
11854
+ import { dirname as dirname12, join as join20, resolve as resolve9 } from "path";
11856
11855
  import { fileURLToPath as fileURLToPath8 } from "url";
11857
11856
 
11858
11857
  // packages/web/server/create-openscout-web-server.ts
@@ -14529,80 +14528,22 @@ function installScoutApiMiddleware(app, label = "api") {
14529
14528
  });
14530
14529
  }
14531
14530
  async function registerScoutWebAssets(app, options) {
14532
- if (options.assetMode === "vite-dev") {
14533
- const { createServer } = await import("vite");
14534
- const fs = await import("fs");
14535
- const path = await import("path");
14536
- const vite = await createServer({
14537
- configFile: options.viteConfigPath,
14538
- server: { middlewareMode: true, hmr: { port: 5183 } },
14539
- appType: "custom"
14540
- });
14541
- const viteRoot = (vite.config.root ?? process.cwd()).replace(/\/$/, "");
14542
- const proxyToVite = (req, url) => {
14543
- return new Promise((resolve5) => {
14544
- const http = __require("http");
14545
- const { Duplex } = __require("stream");
14546
- const socket = new Duplex({
14547
- read() {},
14548
- write(_chunk, _encoding, cb) {
14549
- cb();
14550
- }
14551
- });
14552
- const nodeReq = new http.IncomingMessage(socket);
14553
- nodeReq.method = req.method;
14554
- nodeReq.url = url.pathname + url.search;
14555
- nodeReq.headers = {};
14556
- for (const [key, value] of req.headers.entries()) {
14557
- nodeReq.headers[key.toLowerCase()] = value;
14558
- }
14559
- const nodeRes = new http.ServerResponse(nodeReq);
14560
- const chunks = [];
14561
- nodeRes.write = function(chunk) {
14562
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
14563
- return true;
14564
- };
14565
- const originalEnd = nodeRes.end.bind(nodeRes);
14566
- nodeRes.end = function(chunk) {
14567
- if (chunk) {
14568
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
14569
- }
14570
- const body = Buffer.concat(chunks);
14571
- const headers = new Headers;
14572
- for (const [key, val] of Object.entries(nodeRes.getHeaders())) {
14573
- if (val == null)
14574
- continue;
14575
- if (Array.isArray(val)) {
14576
- for (const v of val)
14577
- headers.append(key, v);
14578
- } else {
14579
- headers.set(key, String(val));
14580
- }
14581
- }
14582
- resolve5(new Response(body.length > 0 ? body : null, {
14583
- status: nodeRes.statusCode,
14584
- headers
14585
- }));
14586
- return originalEnd();
14587
- };
14588
- vite.middlewares.handle(nodeReq, nodeRes, async () => {
14589
- try {
14590
- const indexPath = path.join(viteRoot, "index.html");
14591
- let html2 = fs.readFileSync(indexPath, "utf-8");
14592
- html2 = await vite.transformIndexHtml(url.pathname, html2);
14593
- resolve5(new Response(html2, {
14594
- status: 200,
14595
- headers: { "content-type": "text/html; charset=utf-8" }
14596
- }));
14597
- } catch {
14598
- resolve5(new Response("Not Found", { status: 404 }));
14599
- }
14600
- });
14601
- });
14602
- };
14531
+ const viteUrl = options.viteDevUrl?.trim() || options.defaultViteUrl;
14532
+ if (options.assetMode === "vite-proxy") {
14603
14533
  app.all("/*", async (c) => {
14604
- const url = new URL(c.req.url);
14605
- return proxyToVite(c.req.raw, url);
14534
+ const target = new URL(c.req.path, viteUrl);
14535
+ target.search = new URL(c.req.url).search;
14536
+ const headers = new Headers(c.req.header());
14537
+ headers.delete("host");
14538
+ const response = await fetch(target.toString(), {
14539
+ method: c.req.method,
14540
+ headers,
14541
+ body: c.req.method !== "GET" && c.req.method !== "HEAD" ? c.req.raw.body : undefined
14542
+ });
14543
+ return new Response(response.body, {
14544
+ status: response.status,
14545
+ headers: response.headers
14546
+ });
14606
14547
  });
14607
14548
  return;
14608
14549
  }
@@ -15912,6 +15853,128 @@ function queryFleet(opts) {
15912
15853
  activity
15913
15854
  };
15914
15855
  }
15856
+ var WINDOW_TIERS = [
15857
+ { ms: 30 * 60000, label: "30m" },
15858
+ { ms: 60 * 60000, label: "1h" },
15859
+ { ms: 6 * 60 * 60000, label: "6h" },
15860
+ { ms: 24 * 60 * 60000, label: "24h" },
15861
+ { ms: 7 * 24 * 60 * 60000, label: "7d" }
15862
+ ];
15863
+ var MIN_FILLED_BUCKETS = 3;
15864
+ var _sealedCounts = new Map;
15865
+ var _cachedTier = null;
15866
+ function queryHeartrate(numBuckets = 48) {
15867
+ const nowMs = Date.now();
15868
+ let chosenTier = WINDOW_TIERS[WINDOW_TIERS.length - 1];
15869
+ for (const tier of WINDOW_TIERS) {
15870
+ const bucketMs2 = tier.ms / numBuckets;
15871
+ const currentBucketStart2 = Math.floor(nowMs / bucketMs2) * bucketMs2;
15872
+ const alignedStart2 = currentBucketStart2 - (numBuckets - 1) * bucketMs2;
15873
+ if (_cachedTier === tier.label) {
15874
+ let sealedHits = 0;
15875
+ let filledSealed = 0;
15876
+ for (let i = 0;i < numBuckets - 1; i++) {
15877
+ const key = String(alignedStart2 + i * bucketMs2);
15878
+ if (_sealedCounts.has(key)) {
15879
+ sealedHits++;
15880
+ if (_sealedCounts.get(key) > 0)
15881
+ filledSealed++;
15882
+ }
15883
+ }
15884
+ if (sealedHits === numBuckets - 1 && filledSealed >= MIN_FILLED_BUCKETS) {
15885
+ chosenTier = tier;
15886
+ break;
15887
+ }
15888
+ }
15889
+ const rows2 = db().prepare(`SELECT ts FROM activity_items WHERE ts >= ? ORDER BY ts ASC`).all(alignedStart2);
15890
+ const counts2 = new Array(numBuckets).fill(0);
15891
+ for (const row of rows2) {
15892
+ const ms = row.ts < 1000000000000 ? row.ts * 1000 : row.ts;
15893
+ if (ms < alignedStart2)
15894
+ continue;
15895
+ const idx = Math.min(numBuckets - 1, Math.floor((ms - alignedStart2) / bucketMs2));
15896
+ if (idx >= 0)
15897
+ counts2[idx]++;
15898
+ }
15899
+ const filled = counts2.filter((c) => c > 0).length;
15900
+ if (filled >= MIN_FILLED_BUCKETS) {
15901
+ if (_cachedTier !== tier.label) {
15902
+ _sealedCounts.clear();
15903
+ _cachedTier = tier.label;
15904
+ }
15905
+ for (let i = 0;i < numBuckets - 1; i++) {
15906
+ _sealedCounts.set(String(alignedStart2 + i * bucketMs2), counts2[i]);
15907
+ }
15908
+ const peak2 = Math.max(1, ...counts2);
15909
+ return {
15910
+ windowLabel: tier.label,
15911
+ buckets: counts2.map((count, i) => ({
15912
+ ts: Math.round(alignedStart2 + i * bucketMs2),
15913
+ count,
15914
+ value: count / peak2
15915
+ }))
15916
+ };
15917
+ }
15918
+ }
15919
+ const bucketMs = chosenTier.ms / numBuckets;
15920
+ const currentBucketStart = Math.floor(nowMs / bucketMs) * bucketMs;
15921
+ const alignedStart = currentBucketStart - (numBuckets - 1) * bucketMs;
15922
+ if (_cachedTier === chosenTier.label) {
15923
+ const counts2 = new Array(numBuckets).fill(0);
15924
+ let allSealed = true;
15925
+ for (let i = 0;i < numBuckets - 1; i++) {
15926
+ const key = String(alignedStart + i * bucketMs);
15927
+ if (_sealedCounts.has(key)) {
15928
+ counts2[i] = _sealedCounts.get(key);
15929
+ } else {
15930
+ allSealed = false;
15931
+ break;
15932
+ }
15933
+ }
15934
+ if (allSealed) {
15935
+ const liveStart = alignedStart + (numBuckets - 1) * bucketMs;
15936
+ const row = db().prepare(`SELECT COUNT(*) AS c FROM activity_items WHERE ts >= ?`).get(liveStart);
15937
+ counts2[numBuckets - 1] = row.c;
15938
+ const peak2 = Math.max(1, ...counts2);
15939
+ return {
15940
+ windowLabel: chosenTier.label,
15941
+ buckets: counts2.map((count, i) => ({
15942
+ ts: Math.round(alignedStart + i * bucketMs),
15943
+ count,
15944
+ value: count / peak2
15945
+ }))
15946
+ };
15947
+ }
15948
+ }
15949
+ _sealedCounts.clear();
15950
+ _cachedTier = chosenTier.label;
15951
+ const rows = db().prepare(`SELECT ts FROM activity_items WHERE ts >= ? ORDER BY ts ASC`).all(alignedStart);
15952
+ const counts = new Array(numBuckets).fill(0);
15953
+ for (const row of rows) {
15954
+ const ms = row.ts < 1000000000000 ? row.ts * 1000 : row.ts;
15955
+ if (ms < alignedStart)
15956
+ continue;
15957
+ const idx = Math.min(numBuckets - 1, Math.floor((ms - alignedStart) / bucketMs));
15958
+ if (idx >= 0)
15959
+ counts[idx]++;
15960
+ }
15961
+ for (let i = 0;i < numBuckets - 1; i++) {
15962
+ _sealedCounts.set(String(alignedStart + i * bucketMs), counts[i]);
15963
+ }
15964
+ for (const key of _sealedCounts.keys()) {
15965
+ if (Number(key) < alignedStart)
15966
+ _sealedCounts.delete(key);
15967
+ }
15968
+ const peak = Math.max(1, ...counts);
15969
+ return {
15970
+ windowLabel: chosenTier.label,
15971
+ buckets: counts.map((count, i) => ({
15972
+ ts: Math.round(alignedStart + i * bucketMs),
15973
+ count,
15974
+ value: count / peak
15975
+ }))
15976
+ };
15977
+ }
15915
15978
 
15916
15979
  // packages/web/server/core/broker/service.ts
15917
15980
  init_src2();
@@ -17258,6 +17321,7 @@ async function createOpenScoutWebServer(options) {
17258
17321
  app.get("/api/shell-state/refresh", async (c) => c.json(await shellStateCache.refresh()));
17259
17322
  app.get("/api/agents", (c) => c.json(queryAgents()));
17260
17323
  app.get("/api/activity", (c) => c.json(queryActivity()));
17324
+ app.get("/api/heartrate", (c) => c.json(queryHeartrate()));
17261
17325
  app.get("/api/fleet", (c) => c.json(queryFleet({
17262
17326
  limit: parseOptionalPositiveInt(c.req.query("limit")),
17263
17327
  activityLimit: parseOptionalPositiveInt(c.req.query("activityLimit"))
@@ -17479,7 +17543,8 @@ async function createOpenScoutWebServer(options) {
17479
17543
  await registerScoutWebAssets(app, {
17480
17544
  assetMode: options.assetMode,
17481
17545
  staticRoot: resolveStaticRoot(options.staticRoot),
17482
- viteConfigPath: resolve8(dirname11(fileURLToPath7(import.meta.url)), "../vite.config.ts")
17546
+ viteDevUrl: options.viteDevUrl,
17547
+ defaultViteUrl: "http://127.0.0.1:5180"
17483
17548
  });
17484
17549
  const warmupCaches = () => Promise.allSettled([
17485
17550
  shellStateCache.refresh(),
@@ -17887,19 +17952,25 @@ function resolveStaticRoot2() {
17887
17952
  return process.env.OPENSCOUT_WEB_STATIC_ROOT.trim();
17888
17953
  }
17889
17954
  const selfDir = dirname12(fileURLToPath8(import.meta.url));
17890
- const siblingClient = join20(selfDir, "client", "index.html");
17891
- if (existsSync13(siblingClient)) {
17892
- return join20(selfDir, "client");
17955
+ const siblingClientRoot = join20(selfDir, "client");
17956
+ if (existsSync13(join20(siblingClientRoot, "index.html"))) {
17957
+ return siblingClientRoot;
17958
+ }
17959
+ const sourceDistClientRoot = resolve9(selfDir, "../dist/client");
17960
+ if (existsSync13(join20(sourceDistClientRoot, "index.html"))) {
17961
+ return sourceDistClientRoot;
17893
17962
  }
17894
17963
  return;
17895
17964
  }
17896
17965
  var staticRoot = resolveStaticRoot2();
17897
- var dev = !staticRoot;
17898
- var idleTimeoutSeconds = Number.parseInt(process.env.OPENSCOUT_WEB_IDLE_TIMEOUT_SECONDS?.trim() || (dev ? "180" : "30"), 10);
17966
+ var viteDevUrl = process.env.OPENSCOUT_WEB_VITE_URL?.trim() || undefined;
17967
+ var useViteProxy = Boolean(viteDevUrl) || !staticRoot;
17968
+ var idleTimeoutSeconds = Number.parseInt(process.env.OPENSCOUT_WEB_IDLE_TIMEOUT_SECONDS?.trim() || (useViteProxy ? "180" : "30"), 10);
17899
17969
  var { app, warmupCaches } = await createOpenScoutWebServer({
17900
17970
  currentDirectory,
17901
17971
  shellStateCacheTtlMs,
17902
- assetMode: dev ? "vite-dev" : "static",
17972
+ assetMode: useViteProxy ? "vite-proxy" : "static",
17973
+ viteDevUrl,
17903
17974
  staticRoot
17904
17975
  });
17905
17976
  var honoFetch = app.fetch;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openscout/scout",
3
- "version": "0.2.53",
3
+ "version": "0.2.54",
4
4
  "description": "Published Scout package that installs the `scout` command",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -23,6 +23,6 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@openscout/runtime": "0.2.53"
26
+ "@openscout/runtime": "0.2.54"
27
27
  }
28
28
  }