@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.
- package/README.md +25 -0
- package/dist/client/assets/{arc.es-B2XMIUBi.js → arc.es-DglF81f4.js} +1 -1
- package/dist/client/assets/index-BRvY3oC-.js +159 -0
- package/dist/client/assets/index-CUNnl56m.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/main.mjs +1084 -1091
- package/dist/pair-supervisor.mjs +39 -10
- package/dist/scout-control-plane-web.mjs +168 -16
- package/dist/scout-web-server.mjs +168 -16
- package/package.json +2 -2
- package/dist/client/assets/index-DB4dwQr2.css +0 -1
- package/dist/client/assets/index-DiSMCren.js +0 -158
- package/dist/control-plane-client/assets/addon-fit-DX4qG4td.js +0 -1
- package/dist/control-plane-client/assets/addon-webgl-DCtw1yLn.js +0 -64
- package/dist/control-plane-client/assets/arc.es-CcKhIPRw.js +0 -188
- package/dist/control-plane-client/assets/index-BGcMYoYJ.css +0 -1
- package/dist/control-plane-client/assets/index-BkEXBsPk.js +0 -140
- package/dist/control-plane-client/assets/xterm-B-qIQCd3.js +0 -16
- package/dist/control-plane-client/index.html +0 -16
- package/dist/control-plane-client/openscout-icon.png +0 -0
- /package/dist/{control-plane-client → client}/assets/index-TVkH_WDG.js +0 -0
package/dist/pair-supervisor.mjs
CHANGED
|
@@ -8321,10 +8321,12 @@ function scopesMatch(left, right) {
|
|
|
8321
8321
|
if (left.sessionId !== right.sessionId) {
|
|
8322
8322
|
return false;
|
|
8323
8323
|
}
|
|
8324
|
-
|
|
8325
|
-
|
|
8324
|
+
switch (left.kind) {
|
|
8325
|
+
case "session":
|
|
8326
|
+
return true;
|
|
8327
|
+
case "file_change":
|
|
8328
|
+
return right.kind === "file_change" && left.turnId === right.turnId && left.blockId === right.blockId;
|
|
8326
8329
|
}
|
|
8327
|
-
return left.turnId === right.turnId && left.blockId === right.blockId;
|
|
8328
8330
|
}
|
|
8329
8331
|
function pathForWebHandoffScope(scope) {
|
|
8330
8332
|
switch (scope.kind) {
|
|
@@ -13392,6 +13394,7 @@ class CodexAppServerSession {
|
|
|
13392
13394
|
key;
|
|
13393
13395
|
threadIdPath;
|
|
13394
13396
|
statePath;
|
|
13397
|
+
replyContextPath;
|
|
13395
13398
|
stdoutLogPath;
|
|
13396
13399
|
stderrLogPath;
|
|
13397
13400
|
process = null;
|
|
@@ -13409,6 +13412,7 @@ class CodexAppServerSession {
|
|
|
13409
13412
|
this.key = sessionKey2(options);
|
|
13410
13413
|
this.threadIdPath = join13(options.runtimeDirectory, "codex-thread-id.txt");
|
|
13411
13414
|
this.statePath = join13(options.runtimeDirectory, "state.json");
|
|
13415
|
+
this.replyContextPath = join13(options.runtimeDirectory, "scout-reply-context.json");
|
|
13412
13416
|
this.stdoutLogPath = join13(options.logsDirectory, "stdout.log");
|
|
13413
13417
|
this.stderrLogPath = join13(options.logsDirectory, "stderr.log");
|
|
13414
13418
|
this.lastConfigSignature = this.configSignature(options);
|
|
@@ -13432,7 +13436,7 @@ class CodexAppServerSession {
|
|
|
13432
13436
|
threadId: this.threadId
|
|
13433
13437
|
};
|
|
13434
13438
|
}
|
|
13435
|
-
async invoke(prompt, timeoutMs = 5 * 60000) {
|
|
13439
|
+
async invoke(prompt, timeoutMs = 5 * 60000, replyContext) {
|
|
13436
13440
|
return this.enqueue(async () => {
|
|
13437
13441
|
await this.ensureStarted();
|
|
13438
13442
|
if (!this.threadId) {
|
|
@@ -13441,6 +13445,7 @@ class CodexAppServerSession {
|
|
|
13441
13445
|
const output = await new Promise(async (resolve4, reject) => {
|
|
13442
13446
|
const turn = this.createActiveTurn(resolve4, reject, timeoutMs);
|
|
13443
13447
|
try {
|
|
13448
|
+
await this.writeReplyContext(replyContext ?? null);
|
|
13444
13449
|
const response = await this.request("turn/start", {
|
|
13445
13450
|
threadId: this.threadId,
|
|
13446
13451
|
cwd: this.options.cwd,
|
|
@@ -13455,6 +13460,7 @@ class CodexAppServerSession {
|
|
|
13455
13460
|
turn.turnId = response.turn.id;
|
|
13456
13461
|
await this.persistState();
|
|
13457
13462
|
} catch (error) {
|
|
13463
|
+
await this.clearReplyContext();
|
|
13458
13464
|
this.clearActiveTurn(turn);
|
|
13459
13465
|
reject(error instanceof Error ? error : new Error(errorMessage3(error)));
|
|
13460
13466
|
}
|
|
@@ -13603,6 +13609,7 @@ class CodexAppServerSession {
|
|
|
13603
13609
|
currentDirectory: this.options.cwd,
|
|
13604
13610
|
baseEnv: process.env
|
|
13605
13611
|
});
|
|
13612
|
+
env.OPENSCOUT_REPLY_CONTEXT_FILE = this.replyContextPath;
|
|
13606
13613
|
const child = spawn3(codexExecutable, [
|
|
13607
13614
|
"app-server",
|
|
13608
13615
|
...buildScoutMcpCodexLaunchArgs({
|
|
@@ -13845,6 +13852,20 @@ class CodexAppServerSession {
|
|
|
13845
13852
|
error: buildUnsupportedServerRequestError2(message)
|
|
13846
13853
|
});
|
|
13847
13854
|
}
|
|
13855
|
+
async writeReplyContext(context) {
|
|
13856
|
+
if (!context) {
|
|
13857
|
+
await this.clearReplyContext();
|
|
13858
|
+
return;
|
|
13859
|
+
}
|
|
13860
|
+
await mkdir5(this.options.runtimeDirectory, { recursive: true });
|
|
13861
|
+
await writeFile5(this.replyContextPath, `${JSON.stringify(context, null, 2)}
|
|
13862
|
+
`, "utf8");
|
|
13863
|
+
}
|
|
13864
|
+
async clearReplyContext() {
|
|
13865
|
+
await rm4(this.replyContextPath, { force: true }).catch(() => {
|
|
13866
|
+
return;
|
|
13867
|
+
});
|
|
13868
|
+
}
|
|
13848
13869
|
handleNotification(message) {
|
|
13849
13870
|
const method = message.method;
|
|
13850
13871
|
const params = message.params ?? {};
|
|
@@ -13899,6 +13920,7 @@ class CodexAppServerSession {
|
|
|
13899
13920
|
return;
|
|
13900
13921
|
}
|
|
13901
13922
|
if (method === "turn/completed") {
|
|
13923
|
+
this.clearReplyContext();
|
|
13902
13924
|
this.completeTurn(params);
|
|
13903
13925
|
return;
|
|
13904
13926
|
}
|
|
@@ -16488,7 +16510,11 @@ var scoutBrokerPaths = {
|
|
|
16488
16510
|
deliver: "/v1/deliver",
|
|
16489
16511
|
activity: "/v1/activity",
|
|
16490
16512
|
collaborationRecords: "/v1/collaboration/records",
|
|
16491
|
-
collaborationEvents: "/v1/collaboration/events"
|
|
16513
|
+
collaborationEvents: "/v1/collaboration/events",
|
|
16514
|
+
pairingAttach: "/v1/pairing/attach",
|
|
16515
|
+
pairingDetach: "/v1/pairing/detach",
|
|
16516
|
+
localSessionsAttach: "/v1/local-sessions/attach",
|
|
16517
|
+
localSessionsDetach: "/v1/local-sessions/detach"
|
|
16492
16518
|
}
|
|
16493
16519
|
};
|
|
16494
16520
|
|
|
@@ -16845,7 +16871,10 @@ async function sendScoutDirectMessage(input) {
|
|
|
16845
16871
|
body: input.body.trim(),
|
|
16846
16872
|
intent: "consult",
|
|
16847
16873
|
replyToMessageId: input.replyToMessageId ?? undefined,
|
|
16848
|
-
execution:
|
|
16874
|
+
execution: {
|
|
16875
|
+
...input.executionHarness ? { harness: input.executionHarness } : {},
|
|
16876
|
+
session: "new"
|
|
16877
|
+
},
|
|
16849
16878
|
ensureAwake: true,
|
|
16850
16879
|
messageMetadata: {
|
|
16851
16880
|
source,
|
|
@@ -36238,7 +36267,7 @@ function deleteMobilePushRegistrationByToken(pushToken) {
|
|
|
36238
36267
|
writeDb().query(`DELETE FROM mobile_push_registrations WHERE push_token = ?1`).run(token);
|
|
36239
36268
|
}
|
|
36240
36269
|
function base64UrlEncode(input) {
|
|
36241
|
-
const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : input;
|
|
36270
|
+
const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.from(Array.from(input));
|
|
36242
36271
|
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
36243
36272
|
}
|
|
36244
36273
|
function loadApnsCredentials() {
|
|
@@ -36283,7 +36312,7 @@ function apnsJwt(credentials) {
|
|
|
36283
36312
|
}));
|
|
36284
36313
|
const unsignedToken = `${header}.${claims}`;
|
|
36285
36314
|
const privateKey = createPrivateKey(credentials.privateKeyPem);
|
|
36286
|
-
const signature = signWithKey("sha256",
|
|
36315
|
+
const signature = signWithKey("sha256", new TextEncoder().encode(unsignedToken), privateKey);
|
|
36287
36316
|
const token = `${unsignedToken}.${base64UrlEncode(signature)}`;
|
|
36288
36317
|
cachedApnsJwt = {
|
|
36289
36318
|
cacheKey,
|
|
@@ -36339,11 +36368,11 @@ async function sendApnsAlertToRegistration(registration, alert) {
|
|
|
36339
36368
|
apnsId = typeof headers["apns-id"] === "string" ? headers["apns-id"] : null;
|
|
36340
36369
|
});
|
|
36341
36370
|
request.on("data", (chunk) => {
|
|
36342
|
-
chunks.push(
|
|
36371
|
+
chunks.push(chunk);
|
|
36343
36372
|
});
|
|
36344
36373
|
request.on("end", () => {
|
|
36345
36374
|
client.close();
|
|
36346
|
-
const rawBody = chunks.
|
|
36375
|
+
const rawBody = chunks.join("");
|
|
36347
36376
|
let reason = null;
|
|
36348
36377
|
if (rawBody.trim().length > 0) {
|
|
36349
36378
|
try {
|
|
@@ -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
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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();
|