@openscout/scout 0.2.63 → 0.2.64

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.
@@ -10827,6 +10827,7 @@ class CodexAppServerSession {
10827
10827
  key;
10828
10828
  threadIdPath;
10829
10829
  statePath;
10830
+ replyContextPath;
10830
10831
  stdoutLogPath;
10831
10832
  stderrLogPath;
10832
10833
  process = null;
@@ -10844,6 +10845,7 @@ class CodexAppServerSession {
10844
10845
  this.key = sessionKey2(options);
10845
10846
  this.threadIdPath = join16(options.runtimeDirectory, "codex-thread-id.txt");
10846
10847
  this.statePath = join16(options.runtimeDirectory, "state.json");
10848
+ this.replyContextPath = join16(options.runtimeDirectory, "scout-reply-context.json");
10847
10849
  this.stdoutLogPath = join16(options.logsDirectory, "stdout.log");
10848
10850
  this.stderrLogPath = join16(options.logsDirectory, "stderr.log");
10849
10851
  this.lastConfigSignature = this.configSignature(options);
@@ -10867,7 +10869,7 @@ class CodexAppServerSession {
10867
10869
  threadId: this.threadId
10868
10870
  };
10869
10871
  }
10870
- async invoke(prompt, timeoutMs = 5 * 60000) {
10872
+ async invoke(prompt, timeoutMs = 5 * 60000, replyContext) {
10871
10873
  return this.enqueue(async () => {
10872
10874
  await this.ensureStarted();
10873
10875
  if (!this.threadId) {
@@ -10876,6 +10878,7 @@ class CodexAppServerSession {
10876
10878
  const output = await new Promise(async (resolve7, reject) => {
10877
10879
  const turn = this.createActiveTurn(resolve7, reject, timeoutMs);
10878
10880
  try {
10881
+ await this.writeReplyContext(replyContext ?? null);
10879
10882
  const response = await this.request("turn/start", {
10880
10883
  threadId: this.threadId,
10881
10884
  cwd: this.options.cwd,
@@ -10890,6 +10893,7 @@ class CodexAppServerSession {
10890
10893
  turn.turnId = response.turn.id;
10891
10894
  await this.persistState();
10892
10895
  } catch (error) {
10896
+ await this.clearReplyContext();
10893
10897
  this.clearActiveTurn(turn);
10894
10898
  reject(error instanceof Error ? error : new Error(errorMessage3(error)));
10895
10899
  }
@@ -11038,6 +11042,7 @@ class CodexAppServerSession {
11038
11042
  currentDirectory: this.options.cwd,
11039
11043
  baseEnv: process.env
11040
11044
  });
11045
+ env.OPENSCOUT_REPLY_CONTEXT_FILE = this.replyContextPath;
11041
11046
  const child = spawn4(codexExecutable, [
11042
11047
  "app-server",
11043
11048
  ...buildScoutMcpCodexLaunchArgs({
@@ -11280,6 +11285,20 @@ class CodexAppServerSession {
11280
11285
  error: buildUnsupportedServerRequestError2(message)
11281
11286
  });
11282
11287
  }
11288
+ async writeReplyContext(context) {
11289
+ if (!context) {
11290
+ await this.clearReplyContext();
11291
+ return;
11292
+ }
11293
+ await mkdir5(this.options.runtimeDirectory, { recursive: true });
11294
+ await writeFile5(this.replyContextPath, `${JSON.stringify(context, null, 2)}
11295
+ `, "utf8");
11296
+ }
11297
+ async clearReplyContext() {
11298
+ await rm4(this.replyContextPath, { force: true }).catch(() => {
11299
+ return;
11300
+ });
11301
+ }
11283
11302
  handleNotification(message) {
11284
11303
  const method = message.method;
11285
11304
  const params = message.params ?? {};
@@ -11334,6 +11353,7 @@ class CodexAppServerSession {
11334
11353
  return;
11335
11354
  }
11336
11355
  if (method === "turn/completed") {
11356
+ this.clearReplyContext();
11337
11357
  this.completeTurn(params);
11338
11358
  return;
11339
11359
  }
@@ -11503,7 +11523,7 @@ async function ensureCodexAppServerAgentOnline(options) {
11503
11523
  async function invokeCodexAppServerAgent(options) {
11504
11524
  const session = getOrCreateSession2(options);
11505
11525
  session.update(options);
11506
- return session.invoke(options.prompt, options.timeoutMs);
11526
+ return session.invoke(options.prompt, options.timeoutMs, options.replyContext);
11507
11527
  }
11508
11528
  async function sendCodexAppServerAgent(options) {
11509
11529
  const session = getOrCreateSession2(options);
@@ -11511,7 +11531,7 @@ async function sendCodexAppServerAgent(options) {
11511
11531
  if (session.hasActiveTurn()) {
11512
11532
  return session.steerAndWait(options.prompt, options.timeoutMs);
11513
11533
  }
11514
- return session.invoke(options.prompt, options.timeoutMs);
11534
+ return session.invoke(options.prompt, options.timeoutMs, options.replyContext);
11515
11535
  }
11516
11536
  function isCodexAppServerAgentAlive(options) {
11517
11537
  const session = sessions2.get(sessionKey2(options));
@@ -11697,6 +11717,7 @@ __export(exports_local_agents, {
11697
11717
  ensureLocalSessionEndpointOnline: () => ensureLocalSessionEndpointOnline,
11698
11718
  ensureLocalAgentBindingOnline: () => ensureLocalAgentBindingOnline,
11699
11719
  buildTmuxLaunchShellCommand: () => buildTmuxLaunchShellCommand,
11720
+ buildScoutReplyContext: () => buildScoutReplyContext,
11700
11721
  buildLocalAgentSystemPromptTemplate: () => buildLocalAgentSystemPromptTemplate,
11701
11722
  buildLocalAgentSystemPrompt: () => buildLocalAgentSystemPrompt,
11702
11723
  buildLocalAgentNudge: () => buildLocalAgentNudge,
@@ -12589,7 +12610,7 @@ function endpointInvocationPrompt(endpoint, agentName, invocation) {
12589
12610
  const externalSource = endpointMetadataString(endpoint, "externalSource");
12590
12611
  const attachedTransport = endpointMetadataString(endpoint, "attachedTransport");
12591
12612
  const sessionBacked = endpoint.metadata?.sessionBacked === true;
12592
- if (invocation.action === "consult" && (sessionBacked || source === "local-session" || externalSource === "local-session" || attachedTransport === "codex_app_server" || attachedTransport === "claude_stream_json")) {
12613
+ if (invocation.action === "consult" && !invocation.conversationId && !invocation.messageId && (sessionBacked || source === "local-session" || externalSource === "local-session" || attachedTransport === "codex_app_server" || attachedTransport === "claude_stream_json")) {
12593
12614
  return invocation.task;
12594
12615
  }
12595
12616
  if (sessionBacked) {
@@ -12727,6 +12748,46 @@ function buildInvocationHeadline(agentName, invocation) {
12727
12748
  summarizeInvocationTask(invocation.task)
12728
12749
  ].join(" ");
12729
12750
  }
12751
+ function buildScoutReplyContext(agentName, invocation) {
12752
+ if (!invocation.conversationId || !invocation.messageId) {
12753
+ return null;
12754
+ }
12755
+ return {
12756
+ mode: "broker_reply",
12757
+ fromAgentId: invocation.requesterId,
12758
+ toAgentId: agentName,
12759
+ conversationId: invocation.conversationId,
12760
+ messageId: invocation.messageId,
12761
+ replyToMessageId: invocation.messageId,
12762
+ replyPath: "final_response",
12763
+ action: invocation.action
12764
+ };
12765
+ }
12766
+ function buildScoutReplyContextPrompt(context) {
12767
+ const header = [
12768
+ "SCOUT BROKER REPLY MODE",
12769
+ "",
12770
+ "You are answering a Scout ask. Your final assistant message will be delivered back through the Scout broker.",
12771
+ "Do not call scout send, messages_send, or invocations_ask to answer this request.",
12772
+ "Only use Scout tools if you need to ask or delegate to another agent."
12773
+ ];
12774
+ if (!context) {
12775
+ return header;
12776
+ }
12777
+ return [
12778
+ ...header,
12779
+ "",
12780
+ "ScoutReplyContext:",
12781
+ `- mode: ${context.mode}`,
12782
+ `- fromAgentId: ${context.fromAgentId}`,
12783
+ `- toAgentId: ${context.toAgentId}`,
12784
+ `- conversationId: ${context.conversationId}`,
12785
+ `- messageId: ${context.messageId}`,
12786
+ `- replyToMessageId: ${context.replyToMessageId}`,
12787
+ `- replyPath: ${context.replyPath}`,
12788
+ ...context.action ? [`- action: ${context.action}`] : []
12789
+ ];
12790
+ }
12730
12791
  function buildInvocationMetadataLines(agentName, invocation) {
12731
12792
  const referenceParts = [
12732
12793
  invocation.conversationId ? `convo=${invocation.conversationId}` : undefined,
@@ -12742,13 +12803,16 @@ function buildLocalAgentDirectInvocationPrompt(agentName, invocation) {
12742
12803
  const collaborationContract = buildCollaborationContractPrompt(agentName);
12743
12804
  const collaborationContext = buildInvocationCollaborationContextPrompt(invocation);
12744
12805
  const actionRules = invocation.action === "execute" ? "You may inspect and modify the workspace when needed. End with the concise broker-visible reply for the requester." : "Do not modify files unless the request explicitly requires it. End with the concise broker-visible reply for the requester.";
12806
+ const replyContext = buildScoutReplyContext(agentName, invocation);
12745
12807
  return [
12808
+ ...buildScoutReplyContextPrompt(replyContext),
12809
+ "",
12746
12810
  buildInvocationHeadline(agentName, invocation),
12747
12811
  ...buildInvocationMetadataLines(agentName, invocation),
12748
12812
  "",
12749
12813
  actionRules,
12750
12814
  collaborationContract,
12751
- "Return only the reply that should be delivered back through the broker.",
12815
+ "Return only the broker-visible reply for the requester.",
12752
12816
  "",
12753
12817
  collaborationContext,
12754
12818
  contextLines.length > 0 ? `Context:
@@ -12761,10 +12825,13 @@ ${contextLines.join(`
12761
12825
  }
12762
12826
  function buildAttachedSessionInvocationPrompt(invocation, agentName = invocation.targetAgentId) {
12763
12827
  const contextLines = Object.entries(invocation.context ?? {}).map(([key, value]) => `- ${key}: ${String(value)}`);
12828
+ const replyContext = buildScoutReplyContext(agentName, invocation);
12764
12829
  return [
12830
+ ...buildScoutReplyContextPrompt(replyContext),
12831
+ "",
12765
12832
  buildInvocationHeadline(agentName, invocation),
12766
12833
  ...buildInvocationMetadataLines(agentName, invocation),
12767
- "Treat this as a direct message to the current session and reply normally as yourself in this session.",
12834
+ "Treat this as a direct message to the current session, but return only the broker-visible reply for Scout delivery.",
12768
12835
  contextLines.length > 0 ? `Context:
12769
12836
  ${contextLines.join(`
12770
12837
  `)}` : undefined,
@@ -13601,6 +13668,7 @@ async function invokeLocalAgentEndpoint(endpoint, invocation) {
13601
13668
  const agentRuntimeId = endpoint.agentId;
13602
13669
  const definitionId = String(endpoint.metadata?.definitionId ?? endpoint.metadata?.agentName ?? endpoint.agentId);
13603
13670
  const prompt = endpointInvocationPrompt(endpoint, definitionId, invocation);
13671
+ const replyContext = buildScoutReplyContext(agentRuntimeId, invocation);
13604
13672
  const registry = await readLocalAgentRegistry();
13605
13673
  const existing = registry[agentRuntimeId];
13606
13674
  if (!existing && endpoint.transport === "codex_app_server") {
@@ -13609,7 +13677,8 @@ async function invokeLocalAgentEndpoint(endpoint, invocation) {
13609
13677
  const result = await invoke({
13610
13678
  ...buildCodexEndpointSessionOptions(endpoint),
13611
13679
  prompt,
13612
- timeoutMs: invocation.timeoutMs
13680
+ timeoutMs: invocation.timeoutMs,
13681
+ replyContext
13613
13682
  });
13614
13683
  return {
13615
13684
  output: result.output
@@ -13658,7 +13727,8 @@ async function invokeLocalAgentEndpoint(endpoint, invocation) {
13658
13727
  const result = await invokeCodexAppServerAgent({
13659
13728
  ...buildCodexAgentSessionOptions(agentRuntimeId, onlineRecord, renderLocalAgentSystemPromptTemplate(onlineRecord.systemPrompt || buildLocalAgentSystemPromptTemplate(), buildLocalAgentTemplateContext(definitionId, onlineRecord.project, onlineRecord.cwd), { transport: "codex_app_server" })),
13660
13729
  prompt,
13661
- timeoutMs: invocation.timeoutMs
13730
+ timeoutMs: invocation.timeoutMs,
13731
+ replyContext
13662
13732
  });
13663
13733
  return {
13664
13734
  output: result.output
@@ -13730,7 +13800,7 @@ import {
13730
13800
  renameSync,
13731
13801
  writeFileSync
13732
13802
  } from "fs";
13733
- import { homedir } from "os";
13803
+ import { homedir, hostname as osHostname } from "os";
13734
13804
  import { dirname, join } from "path";
13735
13805
  var LOCAL_CONFIG_VERSION = 1;
13736
13806
  var DEFAULT_LOCAL_CONFIG = {
@@ -13742,6 +13812,14 @@ var DEFAULT_LOCAL_CONFIG = {
13742
13812
  pairing: 7888
13743
13813
  }
13744
13814
  };
13815
+ function normalizeLocalHostnameLabel(value) {
13816
+ const firstLabel = value?.trim().replace(/\.local\.?$/i, "").split(".").find((part) => part.trim().length > 0)?.trim();
13817
+ const normalized = firstLabel?.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
13818
+ return normalized || "localhost";
13819
+ }
13820
+ function resolveScoutWebMdnsHostname(hostname = osHostname()) {
13821
+ return `scout.${normalizeLocalHostnameLabel(hostname)}.local`;
13822
+ }
13745
13823
  function localConfigHome() {
13746
13824
  return process.env.OPENSCOUT_HOME ?? join(homedir(), ".openscout");
13747
13825
  }
@@ -16314,16 +16392,32 @@ function isTrustedLoopbackHostname(hostname2) {
16314
16392
  const normalized = normalizeHostname(hostname2);
16315
16393
  return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "::1" || LOOPBACK_IPV4_HOST_PATTERN.test(normalized);
16316
16394
  }
16317
- function isTrustedApiRequest(c) {
16395
+ function normalizeOrigin(origin) {
16396
+ try {
16397
+ return new URL(origin).origin.toLowerCase();
16398
+ } catch {
16399
+ return null;
16400
+ }
16401
+ }
16402
+ function trustedHostSet(options) {
16403
+ return new Set((options.trustedHosts ?? []).map(normalizeHostname).filter(Boolean));
16404
+ }
16405
+ function trustedOriginSet(options) {
16406
+ return new Set((options.trustedOrigins ?? []).map(normalizeOrigin).filter((origin) => Boolean(origin)));
16407
+ }
16408
+ function isTrustedApiHostname(hostname2, options) {
16409
+ return isTrustedLoopbackHostname(hostname2) || trustedHostSet(options).has(normalizeHostname(hostname2));
16410
+ }
16411
+ function isTrustedApiRequest(c, options) {
16318
16412
  const requestUrl = new URL(c.req.url);
16319
- if (!isTrustedLoopbackHostname(requestUrl.hostname)) {
16413
+ if (!isTrustedApiHostname(requestUrl.hostname, options)) {
16320
16414
  return false;
16321
16415
  }
16322
16416
  const origin = c.req.header("origin");
16323
16417
  if (origin) {
16324
16418
  try {
16325
16419
  const originUrl = new URL(origin);
16326
- if (!isTrustedLoopbackHostname(originUrl.hostname) || originUrl.origin !== requestUrl.origin) {
16420
+ if (!isTrustedApiHostname(originUrl.hostname, options) || originUrl.origin !== requestUrl.origin && !trustedOriginSet(options).has(originUrl.origin.toLowerCase())) {
16327
16421
  return false;
16328
16422
  }
16329
16423
  } catch {
@@ -16376,9 +16470,9 @@ function createCachedSnapshot(load, ttlMs) {
16376
16470
  peek: () => cached?.value ?? null
16377
16471
  };
16378
16472
  }
16379
- function installScoutApiMiddleware(app, label = "api") {
16473
+ function installScoutApiMiddleware(app, label = "api", options = {}) {
16380
16474
  app.use("/api/*", async (c, next) => {
16381
- if (!isTrustedApiRequest(c)) {
16475
+ if (!isTrustedApiRequest(c, options)) {
16382
16476
  return c.json({ error: "forbidden" }, 403);
16383
16477
  }
16384
16478
  c.header("Cross-Origin-Resource-Policy", "same-origin");
@@ -21035,7 +21129,10 @@ async function createOpenScoutWebServer(options) {
21035
21129
  ensureOpenScoutVoxOrigins();
21036
21130
  const app = new Hono2;
21037
21131
  const shellStateCache = createCachedSnapshot(loadOpenScoutWebShellState, shellTtl);
21038
- installScoutApiMiddleware(app, "openscout-web api");
21132
+ installScoutApiMiddleware(app, "openscout-web api", {
21133
+ trustedHosts: options.trustedHosts,
21134
+ trustedOrigins: options.trustedOrigins
21135
+ });
21039
21136
  app.get(routes.bootstrapScriptPath, (c) => new Response(serializeOpenScoutWebBootstrap(process.env), {
21040
21137
  headers: {
21041
21138
  "cache-control": "no-store",
@@ -21045,7 +21142,9 @@ async function createOpenScoutWebServer(options) {
21045
21142
  app.get(routes.healthPath, (c) => c.json({
21046
21143
  ok: true,
21047
21144
  surface: "openscout-web",
21048
- currentDirectory
21145
+ currentDirectory,
21146
+ advertisedHost: options.advertisedHost,
21147
+ publicOrigin: options.publicOrigin
21049
21148
  }));
21050
21149
  app.get(routes.terminalRelayHealthPath, async (c) => {
21051
21150
  const ok = await (options.terminalRelayHealthcheck?.() ?? Promise.resolve(false));
@@ -21482,6 +21581,53 @@ async function createOpenScoutWebServer(options) {
21482
21581
  return { app, warmupCaches };
21483
21582
  }
21484
21583
 
21584
+ // packages/web/server/app-server-origin.ts
21585
+ import { hostname as osHostname2 } from "os";
21586
+ function splitList(value) {
21587
+ return (value ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
21588
+ }
21589
+ function hostFromOrigin(value) {
21590
+ if (!value?.trim()) {
21591
+ return null;
21592
+ }
21593
+ try {
21594
+ return new URL(value).hostname;
21595
+ } catch {
21596
+ return null;
21597
+ }
21598
+ }
21599
+ function uniq(values) {
21600
+ const seen = new Set;
21601
+ const out = [];
21602
+ for (const value of values) {
21603
+ const normalized = value?.trim().toLowerCase();
21604
+ if (!normalized || seen.has(normalized)) {
21605
+ continue;
21606
+ }
21607
+ seen.add(normalized);
21608
+ out.push(normalized);
21609
+ }
21610
+ return out;
21611
+ }
21612
+ function resolveOpenScoutWebApplicationServerIdentity(env = process.env, machineHostname = osHostname2()) {
21613
+ const advertisedHost = env.OPENSCOUT_WEB_ADVERTISED_HOST?.trim() || resolveScoutWebMdnsHostname(machineHostname);
21614
+ const publicOrigin = env.OPENSCOUT_WEB_PUBLIC_ORIGIN?.trim() || undefined;
21615
+ const publicOriginHost = hostFromOrigin(publicOrigin);
21616
+ return {
21617
+ advertisedHost,
21618
+ publicOrigin,
21619
+ trustedHosts: uniq([
21620
+ advertisedHost,
21621
+ publicOriginHost,
21622
+ ...splitList(env.OPENSCOUT_WEB_TRUSTED_HOSTS)
21623
+ ]),
21624
+ trustedOrigins: uniq([
21625
+ publicOrigin,
21626
+ ...splitList(env.OPENSCOUT_WEB_TRUSTED_ORIGINS)
21627
+ ])
21628
+ };
21629
+ }
21630
+
21485
21631
  // packages/web/server/relay.ts
21486
21632
  import { mkdir as mkdir7 } from "fs/promises";
21487
21633
  import { join as join22 } from "path";
@@ -21765,6 +21911,7 @@ function resolveStaticRoot2() {
21765
21911
  var staticRoot = resolveStaticRoot2();
21766
21912
  var viteDevUrl = process.env.OPENSCOUT_WEB_VITE_URL?.trim() || undefined;
21767
21913
  var useViteProxy = Boolean(viteDevUrl) || !staticRoot;
21914
+ var applicationServerIdentity = resolveOpenScoutWebApplicationServerIdentity(process.env);
21768
21915
  var idleTimeoutSeconds = Number.parseInt(process.env.OPENSCOUT_WEB_IDLE_TIMEOUT_SECONDS?.trim() || (useViteProxy ? "180" : "30"), 10);
21769
21916
  function toWebSocketUrl(httpUrl, pathname, search = "") {
21770
21917
  const target = new URL(pathname, httpUrl);
@@ -21788,6 +21935,10 @@ var { app, warmupCaches } = await createOpenScoutWebServer({
21788
21935
  assetMode: useViteProxy ? "vite-proxy" : "static",
21789
21936
  viteDevUrl,
21790
21937
  staticRoot,
21938
+ advertisedHost: applicationServerIdentity.advertisedHost,
21939
+ publicOrigin: applicationServerIdentity.publicOrigin,
21940
+ trustedHosts: applicationServerIdentity.trustedHosts,
21941
+ trustedOrigins: applicationServerIdentity.trustedOrigins,
21791
21942
  runTerminalCommand: terminalRelay?.queueCommand,
21792
21943
  terminalRelayHealthcheck: terminalRelay?.healthcheck
21793
21944
  });
@@ -21850,5 +22001,6 @@ var shutdown = () => {
21850
22001
  process.on("SIGINT", shutdown);
21851
22002
  process.on("SIGTERM", shutdown);
21852
22003
  console.log(`OpenScout Web -> http://${hostname2}:${server.port}`);
22004
+ console.log(`OpenScout LAN -> ${applicationServerIdentity.publicOrigin ?? `http://${applicationServerIdentity.advertisedHost}:${server.port}`}`);
21853
22005
  console.log(`Relay WebSocket -> ws://${hostname2}:${server.port}${routes.terminalRelayPath}`);
21854
22006
  warmupCaches();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openscout/scout",
3
- "version": "0.2.63",
3
+ "version": "0.2.64",
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.63"
26
+ "@openscout/runtime": "0.2.64"
27
27
  }
28
28
  }