@openscout/scout 0.2.56 → 0.2.57

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.
@@ -6,9 +6,9 @@
6
6
  <title>Scout</title>
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link href="https://fonts.googleapis.com/css2?family=Instrument+Serif&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-DvJjuWtM.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-MaClzcbW.css">
9
+ <link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter+Tight:wght@400;500;600&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&family=Spectral:wght@500;600&display=swap" rel="stylesheet" />
10
+ <script type="module" crossorigin src="/assets/index-X8QwrbtL.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-CN8PCsrs.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
package/dist/main.mjs CHANGED
@@ -49339,6 +49339,12 @@ function compact(p) {
49339
49339
  return null;
49340
49340
  return p.startsWith(HOME) ? `~${p.slice(HOME.length)}` : p;
49341
49341
  }
49342
+ function sqlQuoteLiteral(value) {
49343
+ return `'${value.replaceAll("'", "''")}'`;
49344
+ }
49345
+ function sqlStringList(values) {
49346
+ return `(${values.map(sqlQuoteLiteral).join(",")})`;
49347
+ }
49342
49348
  function queryExecutingAgentIds() {
49343
49349
  return new Set(db().prepare(`SELECT DISTINCT target_agent_id FROM flights
49344
49350
  WHERE state = 'running'`).all().map((row) => row.target_agent_id));
@@ -49588,9 +49594,11 @@ var _db = null, HOME, LATEST_AGENT_ENDPOINT_JOIN = `LEFT JOIN agent_endpoints ep
49588
49594
  WHERE ep2.agent_id = a.id
49589
49595
  ORDER BY ep2.updated_at DESC
49590
49596
  LIMIT 1
49591
- )`;
49597
+ )`, ACTIVE_FLIGHT_STATES_SQL, ACTIVE_WORK_STATES_SQL;
49592
49598
  var init_db_queries = __esm(() => {
49593
49599
  HOME = homedir17();
49600
+ ACTIVE_FLIGHT_STATES_SQL = sqlStringList(["running", "waking", "waiting", "queued"]);
49601
+ ACTIVE_WORK_STATES_SQL = sqlStringList(["open", "working", "waiting", "review"]);
49594
49602
  });
49595
49603
 
49596
49604
  // ../../apps/desktop/src/core/mobile/service.ts
@@ -15050,6 +15050,12 @@ function compact(p) {
15050
15050
  return null;
15051
15051
  return p.startsWith(HOME) ? `~${p.slice(HOME.length)}` : p;
15052
15052
  }
15053
+ function sqlQuoteLiteral(value) {
15054
+ return `'${value.replaceAll("'", "''")}'`;
15055
+ }
15056
+ function sqlStringList(values) {
15057
+ return `(${values.map(sqlQuoteLiteral).join(",")})`;
15058
+ }
15053
15059
  var LATEST_AGENT_ENDPOINT_JOIN = `LEFT JOIN agent_endpoints ep ON ep.id = (
15054
15060
  SELECT ep2.id
15055
15061
  FROM agent_endpoints ep2
@@ -15077,6 +15083,8 @@ function summarizeAgentStatusLabel(rawState, isWorking) {
15077
15083
  return "Offline";
15078
15084
  }
15079
15085
  }
15086
+ var ACTIVE_FLIGHT_STATES_SQL = sqlStringList(["running", "waking", "waiting", "queued"]);
15087
+ var ACTIVE_WORK_STATES_SQL = sqlStringList(["open", "working", "waiting", "review"]);
15080
15088
  function conversationIdForAgent(agentId) {
15081
15089
  return `dm.operator.${agentId}`;
15082
15090
  }
@@ -14987,94 +14987,6 @@ async function controlScoutWebPairingService(action, currentDirectory) {
14987
14987
  });
14988
14988
  }
14989
14989
 
14990
- // node_modules/.bun/hono@4.12.10/node_modules/hono/dist/middleware/cors/index.js
14991
- var cors = (options) => {
14992
- const defaults = {
14993
- origin: "*",
14994
- allowMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"],
14995
- allowHeaders: [],
14996
- exposeHeaders: []
14997
- };
14998
- const opts = {
14999
- ...defaults,
15000
- ...options
15001
- };
15002
- const findAllowOrigin = ((optsOrigin) => {
15003
- if (typeof optsOrigin === "string") {
15004
- if (optsOrigin === "*") {
15005
- if (opts.credentials) {
15006
- return (origin) => origin || null;
15007
- }
15008
- return () => optsOrigin;
15009
- } else {
15010
- return (origin) => optsOrigin === origin ? origin : null;
15011
- }
15012
- } else if (typeof optsOrigin === "function") {
15013
- return optsOrigin;
15014
- } else {
15015
- return (origin) => optsOrigin.includes(origin) ? origin : null;
15016
- }
15017
- })(opts.origin);
15018
- const findAllowMethods = ((optsAllowMethods) => {
15019
- if (typeof optsAllowMethods === "function") {
15020
- return optsAllowMethods;
15021
- } else if (Array.isArray(optsAllowMethods)) {
15022
- return () => optsAllowMethods;
15023
- } else {
15024
- return () => [];
15025
- }
15026
- })(opts.allowMethods);
15027
- return async function cors2(c, next) {
15028
- function set(key, value) {
15029
- c.res.headers.set(key, value);
15030
- }
15031
- const allowOrigin = await findAllowOrigin(c.req.header("origin") || "", c);
15032
- if (allowOrigin) {
15033
- set("Access-Control-Allow-Origin", allowOrigin);
15034
- }
15035
- if (opts.credentials) {
15036
- set("Access-Control-Allow-Credentials", "true");
15037
- }
15038
- if (opts.exposeHeaders?.length) {
15039
- set("Access-Control-Expose-Headers", opts.exposeHeaders.join(","));
15040
- }
15041
- if (c.req.method === "OPTIONS") {
15042
- if (opts.origin !== "*" || opts.credentials) {
15043
- set("Vary", "Origin");
15044
- }
15045
- if (opts.maxAge != null) {
15046
- set("Access-Control-Max-Age", opts.maxAge.toString());
15047
- }
15048
- const allowMethods = await findAllowMethods(c.req.header("origin") || "", c);
15049
- if (allowMethods.length) {
15050
- set("Access-Control-Allow-Methods", allowMethods.join(","));
15051
- }
15052
- let headers = opts.allowHeaders;
15053
- if (!headers?.length) {
15054
- const requestHeaders = c.req.header("Access-Control-Request-Headers");
15055
- if (requestHeaders) {
15056
- headers = requestHeaders.split(/\s*,\s*/);
15057
- }
15058
- }
15059
- if (headers?.length) {
15060
- set("Access-Control-Allow-Headers", headers.join(","));
15061
- c.res.headers.append("Vary", "Access-Control-Request-Headers");
15062
- }
15063
- c.res.headers.delete("Content-Length");
15064
- c.res.headers.delete("Content-Type");
15065
- return new Response(null, {
15066
- headers: c.res.headers,
15067
- status: 204,
15068
- statusText: "No Content"
15069
- });
15070
- }
15071
- await next();
15072
- if (opts.origin !== "*" || opts.credentials) {
15073
- c.header("Vary", "Origin", { append: true });
15074
- }
15075
- };
15076
- };
15077
-
15078
14990
  // node_modules/.bun/hono@4.12.10/node_modules/hono/dist/adapter/bun/serve-static.js
15079
14991
  import { stat as stat3 } from "fs/promises";
15080
14992
  import { join as join10 } from "path";
@@ -15347,6 +15259,40 @@ var upgradeWebSocket = defineWebSocketHelper((c, events2) => {
15347
15259
  });
15348
15260
 
15349
15261
  // packages/web/server/server-core.ts
15262
+ var LOOPBACK_IPV4_HOST_PATTERN = /^127(?:\.\d{1,3}){3}$/;
15263
+ function normalizeHostname(hostname2) {
15264
+ const trimmed = hostname2.trim().toLowerCase();
15265
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
15266
+ return trimmed.slice(1, -1);
15267
+ }
15268
+ return trimmed;
15269
+ }
15270
+ function isTrustedLoopbackHostname(hostname2) {
15271
+ const normalized = normalizeHostname(hostname2);
15272
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "::1" || LOOPBACK_IPV4_HOST_PATTERN.test(normalized);
15273
+ }
15274
+ function isTrustedApiRequest(c) {
15275
+ const requestUrl = new URL(c.req.url);
15276
+ if (!isTrustedLoopbackHostname(requestUrl.hostname)) {
15277
+ return false;
15278
+ }
15279
+ const origin = c.req.header("origin");
15280
+ if (origin) {
15281
+ try {
15282
+ const originUrl = new URL(origin);
15283
+ if (!isTrustedLoopbackHostname(originUrl.hostname) || originUrl.origin !== requestUrl.origin) {
15284
+ return false;
15285
+ }
15286
+ } catch {
15287
+ return false;
15288
+ }
15289
+ }
15290
+ const fetchSite = c.req.header("sec-fetch-site")?.trim().toLowerCase();
15291
+ if (fetchSite && fetchSite !== "same-origin" && fetchSite !== "same-site" && fetchSite !== "none") {
15292
+ return false;
15293
+ }
15294
+ return true;
15295
+ }
15350
15296
  function createCachedSnapshot(load, ttlMs) {
15351
15297
  let inflight = null;
15352
15298
  let cached = null;
@@ -15388,8 +15334,12 @@ function createCachedSnapshot(load, ttlMs) {
15388
15334
  };
15389
15335
  }
15390
15336
  function installScoutApiMiddleware(app, label = "api") {
15391
- app.use("/*", cors());
15392
15337
  app.use("/api/*", async (c, next) => {
15338
+ if (!isTrustedApiRequest(c)) {
15339
+ return c.json({ error: "forbidden" }, 403);
15340
+ }
15341
+ c.header("Cross-Origin-Resource-Policy", "same-origin");
15342
+ c.header("X-Content-Type-Options", "nosniff");
15393
15343
  try {
15394
15344
  await next();
15395
15345
  } catch (error) {
@@ -15612,6 +15562,22 @@ function coerceNumber(value) {
15612
15562
  }
15613
15563
  return null;
15614
15564
  }
15565
+ function sqlJoinClauses(clauses, operator = "AND") {
15566
+ return clauses.filter((clause) => Boolean(clause)).join(` ${operator} `);
15567
+ }
15568
+ function sqlWhereClause(clauses, operator = "AND") {
15569
+ const joined = sqlJoinClauses(clauses, operator);
15570
+ return joined ? `WHERE ${joined}` : "";
15571
+ }
15572
+ function sqlPlaceholders(count) {
15573
+ return Array.from({ length: count }, () => "?").join(", ");
15574
+ }
15575
+ function sqlQuoteLiteral(value) {
15576
+ return `'${value.replaceAll("'", "''")}'`;
15577
+ }
15578
+ function sqlStringList(values) {
15579
+ return `(${values.map(sqlQuoteLiteral).join(",")})`;
15580
+ }
15615
15581
  var LATEST_AGENT_ENDPOINT_JOIN = `LEFT JOIN agent_endpoints ep ON ep.id = (
15616
15582
  SELECT ep2.id
15617
15583
  FROM agent_endpoints ep2
@@ -15639,6 +15605,8 @@ function normalizeTimestampMs(value) {
15639
15605
  }
15640
15606
  return numeric < 1000000000000 ? numeric * 1000 : numeric;
15641
15607
  }
15608
+ var ACTIVE_FLIGHT_STATES_SQL = sqlStringList(["running", "waking", "waiting", "queued"]);
15609
+ var ACTIVE_WORK_STATES_SQL = sqlStringList(["open", "working", "waiting", "review"]);
15642
15610
  function isDuplicateActivityFeedItem(previous, next) {
15643
15611
  if (!previous) {
15644
15612
  return false;
@@ -15835,10 +15803,10 @@ function queryActivity(limit = 60) {
15835
15803
  }
15836
15804
  function queryRecentMessages(limit = 80, opts) {
15837
15805
  const conversationIds = opts?.conversationId ? conversationIdAliases(opts.conversationId) : [];
15838
- const where = [
15806
+ const where = sqlJoinClauses([
15839
15807
  transientBrokerWorkingStatusPredicate("m"),
15840
- conversationIds.length > 0 ? `m.conversation_id IN (${conversationIds.map(() => "?").join(", ")})` : null
15841
- ].filter(Boolean).join(" AND ");
15808
+ conversationIds.length > 0 ? `m.conversation_id IN (${sqlPlaceholders(conversationIds.length)})` : null
15809
+ ]);
15842
15810
  const rows = db().prepare(`SELECT
15843
15811
  m.id,
15844
15812
  m.conversation_id,
@@ -15873,14 +15841,13 @@ function queryRecentMessages(limit = 80, opts) {
15873
15841
  });
15874
15842
  }
15875
15843
  function queryFlights(opts) {
15876
- const activeStates = "('running','waking','waiting','queued')";
15877
15844
  const conversationIds = opts?.conversationId ? conversationIdAliases(opts.conversationId) : [];
15878
- const where = [
15879
- opts?.activeOnly ? `f.state IN ${activeStates}` : null,
15845
+ const where = sqlJoinClauses([
15846
+ opts?.activeOnly ? `f.state IN ${ACTIVE_FLIGHT_STATES_SQL}` : null,
15880
15847
  opts?.agentId ? `f.target_agent_id = ?` : null,
15881
- conversationIds.length > 0 ? `inv.conversation_id IN (${conversationIds.map(() => "?").join(", ")})` : null,
15848
+ conversationIds.length > 0 ? `inv.conversation_id IN (${sqlPlaceholders(conversationIds.length)})` : null,
15882
15849
  opts?.collaborationRecordId ? `inv.collaboration_record_id = ?` : null
15883
- ].filter(Boolean).join(" AND ");
15850
+ ]);
15884
15851
  const sql = `SELECT
15885
15852
  f.id,
15886
15853
  f.invocation_id,
@@ -15895,7 +15862,7 @@ function queryFlights(opts) {
15895
15862
  FROM flights f
15896
15863
  JOIN invocations inv ON inv.id = f.invocation_id
15897
15864
  LEFT JOIN actors ac ON ac.id = f.target_agent_id
15898
- ${where ? `WHERE ${where}` : ""}
15865
+ ${sqlWhereClause([where])}
15899
15866
  ORDER BY f.started_at DESC NULLS LAST
15900
15867
  LIMIT 100`;
15901
15868
  const params = [];
@@ -15920,7 +15887,6 @@ function queryFlights(opts) {
15920
15887
  }));
15921
15888
  }
15922
15889
  function queryWorkItemById(id) {
15923
- const activeStates = "('open','working','waiting','review')";
15924
15890
  const sql = `SELECT
15925
15891
  cr.id,
15926
15892
  cr.title,
@@ -15943,7 +15909,7 @@ function queryWorkItemById(id) {
15943
15909
  FROM collaboration_records child
15944
15910
  WHERE child.parent_id = cr.id
15945
15911
  AND child.kind = 'work_item'
15946
- AND child.state IN ${activeStates}
15912
+ AND child.state IN ${ACTIVE_WORK_STATES_SQL}
15947
15913
  ) AS active_child_work_count,
15948
15914
  (
15949
15915
  SELECT COUNT(*)
@@ -16131,12 +16097,11 @@ function queryWorkTimeline(workId) {
16131
16097
  return items.slice(0, 80);
16132
16098
  }
16133
16099
  function queryWorkItems(opts) {
16134
- const activeStates = "('open','working','waiting','review')";
16135
- const where = [
16100
+ const where = sqlJoinClauses([
16136
16101
  "cr.kind = 'work_item'",
16137
- opts?.activeOnly !== false ? `cr.state IN ${activeStates}` : null,
16102
+ opts?.activeOnly !== false ? `cr.state IN ${ACTIVE_WORK_STATES_SQL}` : null,
16138
16103
  opts?.agentId ? "(cr.owner_id = ? OR cr.next_move_owner_id = ?)" : null
16139
- ].filter(Boolean).join(" AND ");
16104
+ ]);
16140
16105
  const sql = `SELECT
16141
16106
  cr.id,
16142
16107
  cr.title,
@@ -16156,7 +16121,7 @@ function queryWorkItems(opts) {
16156
16121
  FROM collaboration_records child
16157
16122
  WHERE child.parent_id = cr.id
16158
16123
  AND child.kind = 'work_item'
16159
- AND child.state IN ${activeStates}
16124
+ AND child.state IN ${ACTIVE_WORK_STATES_SQL}
16160
16125
  ) AS active_child_work_count,
16161
16126
  (
16162
16127
  SELECT COUNT(*)
@@ -16234,7 +16199,7 @@ function queryWorkItems(opts) {
16234
16199
  FROM collaboration_records cr
16235
16200
  LEFT JOIN actors owner ON owner.id = cr.owner_id
16236
16201
  LEFT JOIN actors next ON next.id = cr.next_move_owner_id
16237
- ${where ? `WHERE ${where}` : ""}
16202
+ ${sqlWhereClause([where])}
16238
16203
  ORDER BY sort_ts DESC, cr.updated_at DESC
16239
16204
  LIMIT ?`;
16240
16205
  const limit = opts?.limit ?? 50;
@@ -16565,7 +16530,7 @@ function queryFleetActivity(opts) {
16565
16530
  ai.session_id
16566
16531
  FROM activity_items ai
16567
16532
  LEFT JOIN actors ac ON ac.id = ai.actor_id
16568
- ${filters.length ? `WHERE ${filters.join(" OR ")}` : ""}
16533
+ ${sqlWhereClause(filters, "OR")}
16569
16534
  ORDER BY ai.ts DESC
16570
16535
  LIMIT ?`;
16571
16536
  const rows = db().prepare(sql).all(...params, opts?.limit ?? 80);
@@ -16592,7 +16557,7 @@ function fleetStatusLabel(status) {
16592
16557
  }
16593
16558
  }
16594
16559
  function queryFleetAskRows(requesterIds, limit) {
16595
- const requesterClause = requesterIds.map(() => "?").join(", ");
16560
+ const requesterClause = sqlPlaceholders(requesterIds.length);
16596
16561
  return db().prepare(`SELECT
16597
16562
  inv.id AS invocation_id,
16598
16563
  inv.requester_id,
@@ -16689,7 +16654,7 @@ function projectFleetAsk(row, requesterIdSet) {
16689
16654
  };
16690
16655
  }
16691
16656
  function queryFleetAttentionRows(requesterIds, limit) {
16692
- const requesterClause = requesterIds.map(() => "?").join(", ");
16657
+ const requesterClause = sqlPlaceholders(requesterIds.length);
16693
16658
  return db().prepare(`SELECT
16694
16659
  cr.kind AS record_kind,
16695
16660
  cr.id AS record_id,
@@ -20071,6 +20036,9 @@ class ThreadEventPlane {
20071
20036
  // packages/runtime/src/mobile-push.ts
20072
20037
  init_support_paths();
20073
20038
  // packages/web/server/core/observe/service.ts
20039
+ var HISTORY_SNAPSHOT_CACHE_LIMIT = 128;
20040
+ var historySnapshotCache = new Map;
20041
+ var OBSERVE_SUMMARY_TAIL_SIZE = 8;
20074
20042
  function activeEndpoint(snapshot, agentId) {
20075
20043
  const candidates = Object.values(snapshot.endpoints ?? {}).filter((endpoint) => endpoint.agentId === agentId);
20076
20044
  const rank = (state) => {
@@ -20179,16 +20147,39 @@ function readHistorySnapshot(candidate) {
20179
20147
  return null;
20180
20148
  }
20181
20149
  const stat5 = statSync4(candidate.path);
20150
+ const cached = historySnapshotCache.get(candidate.path);
20151
+ if (cached && cached.adapterType === candidate.adapterType && cached.mtimeMs === stat5.mtimeMs && cached.size === stat5.size) {
20152
+ return {
20153
+ historyPath: cached.historyPath,
20154
+ snapshot: cached.snapshot,
20155
+ timedEvents: cached.timedEvents
20156
+ };
20157
+ }
20182
20158
  const replay = createHistorySessionSnapshot({
20183
20159
  path: candidate.path,
20184
20160
  content: readFileSync7(candidate.path, "utf8"),
20185
20161
  adapterType: candidate.adapterType,
20186
20162
  baseTimestampMs: stat5.mtimeMs
20187
20163
  });
20188
- return {
20164
+ const nextEntry = {
20189
20165
  historyPath: candidate.path,
20190
20166
  snapshot: replay.snapshot,
20191
- timedEvents: normalizeTimedEvents(replay.events)
20167
+ timedEvents: normalizeTimedEvents(replay.events),
20168
+ adapterType: candidate.adapterType,
20169
+ mtimeMs: stat5.mtimeMs,
20170
+ size: stat5.size
20171
+ };
20172
+ historySnapshotCache.set(candidate.path, nextEntry);
20173
+ if (historySnapshotCache.size > HISTORY_SNAPSHOT_CACHE_LIMIT) {
20174
+ const oldestKey = historySnapshotCache.keys().next().value;
20175
+ if (typeof oldestKey === "string") {
20176
+ historySnapshotCache.delete(oldestKey);
20177
+ }
20178
+ }
20179
+ return {
20180
+ historyPath: nextEntry.historyPath,
20181
+ snapshot: nextEntry.snapshot,
20182
+ timedEvents: nextEntry.timedEvents
20192
20183
  };
20193
20184
  }
20194
20185
  async function readLiveSnapshot(endpoint) {
@@ -20570,8 +20561,7 @@ function unavailableObserveData(agent) {
20570
20561
  live: false
20571
20562
  };
20572
20563
  }
20573
- async function resolveSnapshotSource(agent) {
20574
- const broker2 = await loadScoutBrokerContext();
20564
+ async function resolveSnapshotSource(agent, broker2) {
20575
20565
  const endpoint = broker2 ? activeEndpoint(broker2.snapshot, agent.id) : null;
20576
20566
  const liveSnapshot = await readLiveSnapshot(endpoint);
20577
20567
  const live = Boolean(liveSnapshot && (liveSnapshot.currentTurnId || liveSnapshot.session.status === "active"));
@@ -20615,15 +20605,11 @@ async function resolveSnapshotSource(agent) {
20615
20605
  sessionId: null
20616
20606
  };
20617
20607
  }
20618
- async function loadAgentObservePayload(agentId) {
20619
- const agent = queryAgents(200).find((entry) => entry.id === agentId) ?? null;
20620
- if (!agent) {
20621
- return null;
20622
- }
20623
- const source = await resolveSnapshotSource(agent);
20608
+ async function buildAgentObservePayload(agent, broker2) {
20609
+ const source = await resolveSnapshotSource(agent, broker2);
20624
20610
  if (source.source === "unavailable") {
20625
20611
  return {
20626
- agentId,
20612
+ agentId: agent.id,
20627
20613
  source: "unavailable",
20628
20614
  fidelity: "synthetic",
20629
20615
  historyPath: null,
@@ -20634,7 +20620,7 @@ async function loadAgentObservePayload(agentId) {
20634
20620
  }
20635
20621
  const fidelity = source.timedEvents.length > 0 ? "timestamped" : "synthetic";
20636
20622
  return {
20637
- agentId,
20623
+ agentId: agent.id,
20638
20624
  source: source.source,
20639
20625
  fidelity,
20640
20626
  historyPath: source.historyPath,
@@ -20643,6 +20629,33 @@ async function loadAgentObservePayload(agentId) {
20643
20629
  data: buildObserveDataFromSnapshot(source.snapshot, source.timedEvents, source.live)
20644
20630
  };
20645
20631
  }
20632
+ async function loadAgentObservePayloadsInternal(agentIds) {
20633
+ const agents = queryAgents(200);
20634
+ const filteredAgents = agentIds && agentIds.length > 0 ? agents.filter((agent) => agentIds.includes(agent.id)) : agents;
20635
+ if (filteredAgents.length === 0) {
20636
+ return [];
20637
+ }
20638
+ const broker2 = await loadScoutBrokerContext();
20639
+ return await Promise.all(filteredAgents.map((agent) => buildAgentObservePayload(agent, broker2)));
20640
+ }
20641
+ function summarizeAgentObservePayload(payload) {
20642
+ return {
20643
+ ...payload,
20644
+ data: {
20645
+ ...payload.data,
20646
+ events: payload.data.events.slice(-OBSERVE_SUMMARY_TAIL_SIZE),
20647
+ files: []
20648
+ }
20649
+ };
20650
+ }
20651
+ async function loadAgentObserveSummaries(agentIds) {
20652
+ const payloads = await loadAgentObservePayloadsInternal(agentIds);
20653
+ return payloads.map(summarizeAgentObservePayload);
20654
+ }
20655
+ async function loadAgentObservePayload(agentId) {
20656
+ const payloads = await loadAgentObservePayloadsInternal([agentId]);
20657
+ return payloads[0] ?? null;
20658
+ }
20646
20659
 
20647
20660
  // packages/web/server/core/mesh/service.ts
20648
20661
  await init_broker_service();
@@ -20994,6 +21007,10 @@ async function createOpenScoutWebServer(options) {
20994
21007
  app.get("/api/shell-state", async (c) => c.json(await shellStateCache.get()));
20995
21008
  app.get("/api/shell-state/refresh", async (c) => c.json(await shellStateCache.refresh()));
20996
21009
  app.get("/api/agents", (c) => c.json(queryAgents()));
21010
+ app.get("/api/observe/agents", async (c) => {
21011
+ const ids = c.req.query("ids")?.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
21012
+ return c.json(await loadAgentObserveSummaries(ids));
21013
+ });
20997
21014
  app.get("/api/agents/:id/observe", async (c) => {
20998
21015
  const payload = await loadAgentObservePayload(c.req.param("id"));
20999
21016
  return payload ? c.json(payload) : c.json({ error: "not found" }, 404);
@@ -21059,7 +21076,22 @@ async function createOpenScoutWebServer(options) {
21059
21076
  }
21060
21077
  });
21061
21078
  app.get("/api/user", (c) => {
21062
- return c.json({ name: resolveOperatorName() });
21079
+ const config = loadUserConfig();
21080
+ return c.json({
21081
+ name: resolveOperatorName(),
21082
+ handle: config.handle ?? "",
21083
+ pronouns: config.pronouns ?? "",
21084
+ hue: config.hue ?? 195,
21085
+ bio: config.bio ?? "",
21086
+ timezone: config.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
21087
+ workingHours: config.workingHours ?? "08:00 \u2013 18:00",
21088
+ interruptThreshold: config.interruptThreshold ?? "blocking-only",
21089
+ batchWindow: config.batchWindow ?? 15,
21090
+ channel: config.channel ?? "here+mobile",
21091
+ verbosity: config.verbosity ?? "terse",
21092
+ tone: config.tone ?? "direct",
21093
+ quietHours: config.quietHours ?? "22:00 \u2013 07:00"
21094
+ });
21063
21095
  });
21064
21096
  app.get("/api/onboarding/state", async (c) => {
21065
21097
  const hasLocalConfig = localConfigExists();
@@ -21135,15 +21167,53 @@ async function createOpenScoutWebServer(options) {
21135
21167
  });
21136
21168
  });
21137
21169
  app.post("/api/user", async (c) => {
21138
- const { name } = await c.req.json();
21170
+ const body = await c.req.json();
21139
21171
  const config = loadUserConfig();
21140
- if (name?.trim()) {
21141
- config.name = name.trim();
21142
- } else {
21143
- delete config.name;
21172
+ const stringFields = [
21173
+ "name",
21174
+ "handle",
21175
+ "pronouns",
21176
+ "bio",
21177
+ "timezone",
21178
+ "workingHours",
21179
+ "interruptThreshold",
21180
+ "channel",
21181
+ "verbosity",
21182
+ "tone",
21183
+ "quietHours"
21184
+ ];
21185
+ for (const key of stringFields) {
21186
+ if (key in body) {
21187
+ const val = body[key];
21188
+ if (typeof val === "string" && val.trim()) {
21189
+ config[key] = val.trim();
21190
+ } else {
21191
+ delete config[key];
21192
+ }
21193
+ }
21194
+ }
21195
+ if ("hue" in body && typeof body.hue === "number") {
21196
+ config.hue = body.hue;
21197
+ }
21198
+ if ("batchWindow" in body && typeof body.batchWindow === "number") {
21199
+ config.batchWindow = body.batchWindow;
21144
21200
  }
21145
21201
  saveUserConfig(config);
21146
- return c.json({ name: resolveOperatorName() });
21202
+ return c.json({
21203
+ name: resolveOperatorName(),
21204
+ handle: config.handle ?? "",
21205
+ pronouns: config.pronouns ?? "",
21206
+ hue: config.hue ?? 195,
21207
+ bio: config.bio ?? "",
21208
+ timezone: config.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
21209
+ workingHours: config.workingHours ?? "08:00 \u2013 18:00",
21210
+ interruptThreshold: config.interruptThreshold ?? "blocking-only",
21211
+ batchWindow: config.batchWindow ?? 15,
21212
+ channel: config.channel ?? "here+mobile",
21213
+ verbosity: config.verbosity ?? "terse",
21214
+ tone: config.tone ?? "direct",
21215
+ quietHours: config.quietHours ?? "22:00 \u2013 07:00"
21216
+ });
21147
21217
  });
21148
21218
  app.post("/api/agents/:agentId/interrupt", async (c) => {
21149
21219
  const agentId = c.req.param("agentId");
@@ -21623,6 +21693,7 @@ function destroyAllRelaySessions() {
21623
21693
 
21624
21694
  // packages/web/server/index.ts
21625
21695
  var port = Number(process.env.OPENSCOUT_WEB_PORT ?? process.env.SCOUT_WEB_PORT ?? "3200");
21696
+ var hostname2 = process.env.OPENSCOUT_WEB_HOST?.trim() || process.env.SCOUT_WEB_HOST?.trim() || "127.0.0.1";
21626
21697
  var currentDirectory = process.env.OPENSCOUT_SETUP_CWD?.trim() || process.cwd();
21627
21698
  var shellStateCacheTtlMs = Number.parseInt(process.env.OPENSCOUT_WEB_SHELL_CACHE_TTL_MS ?? "15000", 10);
21628
21699
  function resolveStaticRoot2() {
@@ -21654,6 +21725,7 @@ var { app, warmupCaches } = await createOpenScoutWebServer({
21654
21725
  var honoFetch = app.fetch;
21655
21726
  var server = Bun.serve({
21656
21727
  port,
21728
+ hostname: hostname2,
21657
21729
  idleTimeout: idleTimeoutSeconds,
21658
21730
  fetch(req, server2) {
21659
21731
  const url = new URL(req.url);
@@ -21680,6 +21752,6 @@ var shutdown = () => {
21680
21752
  };
21681
21753
  process.on("SIGINT", shutdown);
21682
21754
  process.on("SIGTERM", shutdown);
21683
- console.log(`OpenScout Web -> http://localhost:${server.port}`);
21684
- console.log(`Relay WebSocket -> ws://localhost:${server.port}`);
21755
+ console.log(`OpenScout Web -> http://${hostname2}:${server.port}`);
21756
+ console.log(`Relay WebSocket -> ws://${hostname2}:${server.port}`);
21685
21757
  warmupCaches();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openscout/scout",
3
- "version": "0.2.56",
3
+ "version": "0.2.57",
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.56"
26
+ "@openscout/runtime": "0.2.57"
27
27
  }
28
28
  }