@agentproto/runtime 3.4.0 → 3.6.1
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/dist/catalog-models.d.ts +1 -1
- package/dist/catalog-models.mjs.map +1 -1
- package/dist/config.d.ts +24 -2
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.ts +45 -6
- package/dist/index.mjs +868 -269
- package/dist/index.mjs.map +1 -1
- package/dist/resume-strategies.d.ts +33 -4
- package/dist/resume-strategies.mjs +38 -9
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/session-presence.mjs.map +1 -1
- package/dist/{spawn-defaults-D4W661ui.d.ts → spawn-defaults-CwI9Puwy.d.ts} +8 -0
- package/package.json +11 -11
package/dist/index.mjs
CHANGED
|
@@ -21,18 +21,18 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
|
|
|
21
21
|
import { createServer as createServer$1 } from 'net';
|
|
22
22
|
import { defineTool, catchErrors, ToolError, pageParamsShape, paginated, paginate, toolText } from '@agentproto/tool';
|
|
23
23
|
import { CatalogProviderSchema, getModelsByProvider, getStaticModelProvider } from '@agentproto/model-catalog';
|
|
24
|
+
import { APP_UI_DISCOVERY_TOOLS, RUNNER_SELECT_SCRIPT } from '@agentproto/app-client/runner-select';
|
|
25
|
+
import { SESSION_CHAT_APP_ID, liveSessionApp, makeLiveSessionApp, sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, workBoardApp, makeSessionsPanelApp, makeAgentsOverviewApp, makeBureauSessionsApp, makeSessionStoryPanelApp, makeSessionChatApp, makeWorkBoardApp, sessionChatApp } from '@agentproto/apps';
|
|
24
26
|
import { inferLegacyModeKind, parseModelSwitchCommand, isModelSwitchAcknowledgement } from '@agentproto/driver-agent-cli';
|
|
25
27
|
import { loadSandboxConfig, resolveCommandSandbox, COMMAND_SANDBOX_MODE_ENV } from '@agentproto/command-sandbox';
|
|
26
28
|
import { createBrainManager, parseKnowledgeConfig } from '@agentproto/workspace-brain';
|
|
27
29
|
import { defineHttpDriver } from '@agentproto/driver-http';
|
|
28
|
-
import { liveSessionApp, makeLiveSessionApp, sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, workBoardApp, makeSessionsPanelApp, makeAgentsOverviewApp, makeBureauSessionsApp, makeSessionStoryPanelApp, makeSessionChatApp, makeWorkBoardApp, SESSION_CHAT_APP_ID, sessionChatApp } from '@agentproto/apps';
|
|
29
30
|
import matter2 from 'gray-matter';
|
|
30
31
|
import { createServer } from 'http';
|
|
31
32
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
32
33
|
import { WebSocketServer } from 'ws';
|
|
33
34
|
import { loadAppHandle } from '@agentproto/app-kit';
|
|
34
35
|
import { loadAgent } from '@agentproto/agent';
|
|
35
|
-
import { APP_UI_DISCOVERY_TOOLS, RUNNER_SELECT_SCRIPT } from '@agentproto/app-client/runner-select';
|
|
36
36
|
import { normalizeToolId } from '@agentproto/driver';
|
|
37
37
|
import { createIngestionClient } from '@agentproto/telemetry-langfuse';
|
|
38
38
|
import { resolveRedactor } from '@agentproto/redaction';
|
|
@@ -1666,8 +1666,8 @@ async function discoverOpenCodeSessions(cwd, since, expectedId) {
|
|
|
1666
1666
|
const sinceMs = since ? Date.parse(since) : NaN;
|
|
1667
1667
|
return rows.filter((r) => {
|
|
1668
1668
|
if (!Number.isFinite(sinceMs)) return true;
|
|
1669
|
-
|
|
1670
|
-
return
|
|
1669
|
+
if (r.time_created === void 0) return false;
|
|
1670
|
+
return r.time_created >= sinceMs - 1e3;
|
|
1671
1671
|
}).map(openCodeRowToCandidate);
|
|
1672
1672
|
} finally {
|
|
1673
1673
|
db.close();
|
|
@@ -2098,6 +2098,9 @@ function extractFirstText(content) {
|
|
|
2098
2098
|
}
|
|
2099
2099
|
return void 0;
|
|
2100
2100
|
}
|
|
2101
|
+
function isClaudeControlText(text10) {
|
|
2102
|
+
return /^<(?:local-command-caveat|local-command-stdout|command-name|task-notification)>/i.test(text10);
|
|
2103
|
+
}
|
|
2101
2104
|
async function scanClaudeJsonl(filePath) {
|
|
2102
2105
|
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
2103
2106
|
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -2126,7 +2129,7 @@ async function scanClaudeJsonl(filePath) {
|
|
|
2126
2129
|
messageCount += 1;
|
|
2127
2130
|
if (preview2 === void 0 && entry.type === "user") {
|
|
2128
2131
|
const text10 = extractFirstText(entry.message?.content);
|
|
2129
|
-
if (text10 !== void 0) {
|
|
2132
|
+
if (text10 !== void 0 && !isClaudeControlText(text10)) {
|
|
2130
2133
|
preview2 = text10.length > 120 ? text10.slice(0, 120) : text10;
|
|
2131
2134
|
}
|
|
2132
2135
|
}
|
|
@@ -2232,7 +2235,43 @@ async function readPi(conversationId) {
|
|
|
2232
2235
|
const { exportPiSession: exportPiSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
|
|
2233
2236
|
return exportPiSession2(conversationId);
|
|
2234
2237
|
}
|
|
2235
|
-
|
|
2238
|
+
function argvBasename(path) {
|
|
2239
|
+
const idx = path.lastIndexOf("/");
|
|
2240
|
+
return idx === -1 ? path : path.slice(idx + 1);
|
|
2241
|
+
}
|
|
2242
|
+
function conversationTerminalSlugFor(subject) {
|
|
2243
|
+
if (subject.adapterSlug && (CONVERSATION_STORES[subject.adapterSlug] || NATIVE_LAUNCH_ARGV[subject.adapterSlug])) {
|
|
2244
|
+
return subject.adapterSlug;
|
|
2245
|
+
}
|
|
2246
|
+
const argv = subject.argv;
|
|
2247
|
+
if (!argv || argv.length === 0 || !argv[0]) return void 0;
|
|
2248
|
+
const bin = argvBasename(argv[0]);
|
|
2249
|
+
for (const [slug, bins] of Object.entries(BARE_LAUNCH_BINS)) {
|
|
2250
|
+
if (bins.some((b) => argvBasename(b) === bin)) return slug;
|
|
2251
|
+
}
|
|
2252
|
+
for (const [slug, launch] of Object.entries(NATIVE_LAUNCH_ARGV)) {
|
|
2253
|
+
const launchBin = launch[0];
|
|
2254
|
+
if (!launchBin || argvBasename(launchBin) !== bin) continue;
|
|
2255
|
+
if (launchBin === "npx") {
|
|
2256
|
+
if (launch.every((arg, i) => argv[i] === arg)) return slug;
|
|
2257
|
+
continue;
|
|
2258
|
+
}
|
|
2259
|
+
return slug;
|
|
2260
|
+
}
|
|
2261
|
+
return void 0;
|
|
2262
|
+
}
|
|
2263
|
+
function isConversationTerminal(subject) {
|
|
2264
|
+
if (subject.kind !== "terminal") return false;
|
|
2265
|
+
if (conversationTerminalSlugFor(subject) !== void 0) return true;
|
|
2266
|
+
const meta = subject.resumeMetadata;
|
|
2267
|
+
if (meta) {
|
|
2268
|
+
for (const key of CONVERSATION_RESUME_KEYS) {
|
|
2269
|
+
if (meta[key]) return true;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
return false;
|
|
2273
|
+
}
|
|
2274
|
+
var CONVERSATION_STORES, NATIVE_LAUNCH_ARGV, BARE_LAUNCH_BINS, CONVERSATION_RESUME_KEYS;
|
|
2236
2275
|
var init_conversation_store = __esm({
|
|
2237
2276
|
"src/conversation-store.ts"() {
|
|
2238
2277
|
CONVERSATION_STORES = {
|
|
@@ -2242,6 +2281,7 @@ var init_conversation_store = __esm({
|
|
|
2242
2281
|
// (default). Example: `claude --resume 0e483f81-1a44-4bec-9667-b37158450296`
|
|
2243
2282
|
outputHint: /claude\s+--resume\s+([0-9a-f-]{8,})/i,
|
|
2244
2283
|
attachArgv: (conversationId) => ["claude", "--resume", conversationId],
|
|
2284
|
+
transcriptPath: (cwd, conversationId, configDir) => join(claudeCodeProjectDir(cwd, configDir), `${conversationId}.jsonl`),
|
|
2245
2285
|
configDirEnvVar: "CLAUDE_CONFIG_DIR",
|
|
2246
2286
|
discover: discoverClaudeCode,
|
|
2247
2287
|
read: readClaudeCode
|
|
@@ -2266,6 +2306,9 @@ var init_conversation_store = __esm({
|
|
|
2266
2306
|
},
|
|
2267
2307
|
opencode: {
|
|
2268
2308
|
storeAs: "openCodeResumeId",
|
|
2309
|
+
// Match the adapter's portable npx installation: a direct `opencode`
|
|
2310
|
+
// binary is not guaranteed to be on the daemon's PATH on every host.
|
|
2311
|
+
attachArgv: (conversationId) => ["npx", "-y", "opencode-ai", "-s", conversationId],
|
|
2269
2312
|
discover: discoverOpenCode,
|
|
2270
2313
|
read: readOpenCode
|
|
2271
2314
|
},
|
|
@@ -2280,6 +2323,36 @@ var init_conversation_store = __esm({
|
|
|
2280
2323
|
read: readPi
|
|
2281
2324
|
}
|
|
2282
2325
|
};
|
|
2326
|
+
NATIVE_LAUNCH_ARGV = {
|
|
2327
|
+
"claude-code": ["claude"],
|
|
2328
|
+
hermes: ["hermes", "--tui"],
|
|
2329
|
+
// Bare arm of the same npx package the adapter spawns IS the TUI.
|
|
2330
|
+
opencode: ["npx", "-y", "opencode-ai"],
|
|
2331
|
+
mastracode: ["npx", "-y", "mastracode"],
|
|
2332
|
+
// The adapter bundles the codex-acp wrapper (ACP-only); the interactive
|
|
2333
|
+
// Codex TUI lives in @openai/codex, fetched by npx on first use.
|
|
2334
|
+
codex: ["npx", "-y", "@openai/codex"],
|
|
2335
|
+
// ACP arms add flags/subcommands to these binaries; bare is interactive.
|
|
2336
|
+
gemini: ["gemini"],
|
|
2337
|
+
"grok-cli": ["grok"],
|
|
2338
|
+
pi: ["pi"],
|
|
2339
|
+
jcode: ["jcode"],
|
|
2340
|
+
// `openclaw chat` = "Open a local terminal UI (alias for tui --local)".
|
|
2341
|
+
openclaw: ["openclaw", "chat"],
|
|
2342
|
+
// Generic ACP catalog CLIs whose ACP arm is a separate binary/subcommand;
|
|
2343
|
+
// the bare bin is the interactive TUI.
|
|
2344
|
+
"gemini-cli": ["gemini"],
|
|
2345
|
+
"qwen-code": ["qwen"],
|
|
2346
|
+
"mistral-vibe": ["vibe"],
|
|
2347
|
+
"kimi-cli": ["kimi"]
|
|
2348
|
+
};
|
|
2349
|
+
BARE_LAUNCH_BINS = {
|
|
2350
|
+
opencode: ["opencode"],
|
|
2351
|
+
mastracode: ["mastracode"]
|
|
2352
|
+
};
|
|
2353
|
+
CONVERSATION_RESUME_KEYS = new Set(
|
|
2354
|
+
Object.values(CONVERSATION_STORES).map((s) => s.storeAs)
|
|
2355
|
+
);
|
|
2283
2356
|
}
|
|
2284
2357
|
});
|
|
2285
2358
|
|
|
@@ -2512,6 +2585,7 @@ var RESUME_STRATEGIES = Object.fromEntries(
|
|
|
2512
2585
|
outputHint: store.outputHint,
|
|
2513
2586
|
storeAs: store.storeAs,
|
|
2514
2587
|
...store.configDirEnvVar ? { configDirEnvVar: store.configDirEnvVar } : {},
|
|
2588
|
+
...store.transcriptPath ? { transcriptPath: store.transcriptPath } : {},
|
|
2515
2589
|
fsProbe: async (cwd, prevStartedAt, expectedId, configDir) => {
|
|
2516
2590
|
const candidates = await s.discover({
|
|
2517
2591
|
cwd,
|
|
@@ -2528,7 +2602,8 @@ var RESUME_STRATEGIES = Object.fromEntries(
|
|
|
2528
2602
|
);
|
|
2529
2603
|
function decideRestartStrategy(prev, opts = {}) {
|
|
2530
2604
|
const mayPreferNative = prev.pty === true || opts.preferNativeTerminal === true;
|
|
2531
|
-
|
|
2605
|
+
const hasNativeCapability = prev.nativeTerminalResume === true || prev.pty === true && prev.nativeTerminalResume === void 0;
|
|
2606
|
+
if (prev.adapterSlug && hasNativeCapability && mayPreferNative) {
|
|
2532
2607
|
const strategy = RESUME_STRATEGIES[prev.adapterSlug];
|
|
2533
2608
|
const id = strategy?.storeAs ? prev.resumeMetadata?.[strategy.storeAs] : void 0;
|
|
2534
2609
|
if (strategy?.spawnArgs && id) {
|
|
@@ -2583,13 +2658,30 @@ async function augmentWithFsResume(prev) {
|
|
|
2583
2658
|
}
|
|
2584
2659
|
};
|
|
2585
2660
|
}
|
|
2661
|
+
async function probeNativeTranscript(prev) {
|
|
2662
|
+
if (!prev.adapterSlug || !prev.cwd) return void 0;
|
|
2663
|
+
const strategy = RESUME_STRATEGIES[prev.adapterSlug];
|
|
2664
|
+
if (!strategy?.transcriptPath) return void 0;
|
|
2665
|
+
const id = prev.resumeMetadata?.[strategy.storeAs] ?? prev.adapterSessionId;
|
|
2666
|
+
if (!id) return void 0;
|
|
2667
|
+
const path = strategy.transcriptPath(prev.cwd, id, prev.adapterConfigDir);
|
|
2668
|
+
let exists = false;
|
|
2669
|
+
try {
|
|
2670
|
+
exists = (await promises.stat(path)).isFile();
|
|
2671
|
+
} catch {
|
|
2672
|
+
exists = false;
|
|
2673
|
+
}
|
|
2674
|
+
return { dir: dirname(path), path, exists };
|
|
2675
|
+
}
|
|
2586
2676
|
function describeResumePath(prev, opts = {}) {
|
|
2587
2677
|
const mayPreferNative = prev.pty === true || opts.preferNativeTerminal === true;
|
|
2588
|
-
|
|
2678
|
+
const hasNativeCapability = prev.nativeTerminalResume === true || prev.pty === true && prev.nativeTerminalResume === void 0;
|
|
2679
|
+
if (prev.adapterSlug && hasNativeCapability && mayPreferNative) {
|
|
2589
2680
|
const s = RESUME_STRATEGIES[prev.adapterSlug];
|
|
2590
2681
|
if (s?.spawnArgs && s.storeAs && prev.resumeMetadata?.[s.storeAs]) {
|
|
2591
|
-
const sample = s.spawnArgs("\u2026")
|
|
2592
|
-
|
|
2682
|
+
const sample = s.spawnArgs("\u2026");
|
|
2683
|
+
const idIndex = sample.indexOf("\u2026");
|
|
2684
|
+
return `resumed via ${idIndex > 0 ? sample.slice(0, idIndex).join(" ") : sample[0] ?? prev.adapterSlug}`;
|
|
2593
2685
|
}
|
|
2594
2686
|
}
|
|
2595
2687
|
if (prev.adapterSlug && prev.adapterSessionId) {
|
|
@@ -2628,6 +2720,9 @@ function tokenizeCommand(s) {
|
|
|
2628
2720
|
return out;
|
|
2629
2721
|
}
|
|
2630
2722
|
|
|
2723
|
+
// src/sessions.ts
|
|
2724
|
+
init_conversation_store();
|
|
2725
|
+
|
|
2631
2726
|
// src/command-log.ts
|
|
2632
2727
|
init_transcript_writer();
|
|
2633
2728
|
function writeCommandLogEntry(sessionId, entry, baseDir) {
|
|
@@ -5280,6 +5375,9 @@ async function resolveWorkspaceRules(slug, deps2 = {}) {
|
|
|
5280
5375
|
|
|
5281
5376
|
// src/session-title.ts
|
|
5282
5377
|
var MAX_LENGTH = 72;
|
|
5378
|
+
function isInternalDerivedTitle(title) {
|
|
5379
|
+
return /^<(?:local-command-caveat|local-command-stdout|command-name|task-notification)>/i.test(title);
|
|
5380
|
+
}
|
|
5283
5381
|
function isRecord3(value) {
|
|
5284
5382
|
return typeof value === "object" && value !== null;
|
|
5285
5383
|
}
|
|
@@ -5306,6 +5404,7 @@ function deriveSessionTitle(message) {
|
|
|
5306
5404
|
if (raw === void 0) return void 0;
|
|
5307
5405
|
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
5308
5406
|
if (collapsed === "") return void 0;
|
|
5407
|
+
if (isInternalDerivedTitle(collapsed)) return void 0;
|
|
5309
5408
|
const sentence = collapsed.replace(/[.?!](\s.*)?$/, "").trim();
|
|
5310
5409
|
if (sentence === "" || !/[\p{L}\p{N}]/u.test(sentence)) return void 0;
|
|
5311
5410
|
return Array.from(sentence).length > MAX_LENGTH ? truncate2(sentence, MAX_LENGTH) : sentence;
|
|
@@ -8205,8 +8304,175 @@ var sandboxSpecWithReuseSchema = z.object({
|
|
|
8205
8304
|
"Reconnect to an existing box instead of booting a new one. Accepts a prior session's exact `sandboxId`, a ledger `label` (exact match), or an unambiguous prefix of a ledger sandboxId \u2014 resolved against the local sandbox ledger (`~/.agentproto/sandboxes.json`, see `agentproto sandbox list`). A prefix matching more than one ledger entry fails with `sandbox_reuse_ambiguous` listing the candidates. Requires the provider to support reconnect (e.g. e2b); omit to boot fresh (default)."
|
|
8206
8305
|
)
|
|
8207
8306
|
}).strict();
|
|
8208
|
-
|
|
8209
|
-
|
|
8307
|
+
function appUiToolId(appId) {
|
|
8308
|
+
const slug = appId.replace(/^@[^/]+\//, "").replace(/[^a-z0-9]/g, "_");
|
|
8309
|
+
return `app_ui_${slug}`;
|
|
8310
|
+
}
|
|
8311
|
+
var MCP_APP_BRIDGE_SCRIPT = `<script>
|
|
8312
|
+
(function () {
|
|
8313
|
+
window.__AGENTPROTO_UI_TRANSPORT__ = "mcp";
|
|
8314
|
+
if (window.McpApp) return;
|
|
8315
|
+
var nextId = 1, pending = {}, teardownCbs = [];
|
|
8316
|
+
function post(m) { window.parent.postMessage(m, "*"); }
|
|
8317
|
+
window.addEventListener("message", function (e) {
|
|
8318
|
+
var m = e.data;
|
|
8319
|
+
if (typeof m === "string") { try { m = JSON.parse(m); } catch (_) { return; } }
|
|
8320
|
+
if (!m || m.jsonrpc !== "2.0") return;
|
|
8321
|
+
if (m.method === "ui/resource-teardown") {
|
|
8322
|
+
for (var i = 0; i < teardownCbs.length; i++) teardownCbs[i]();
|
|
8323
|
+
post({ jsonrpc: "2.0", id: m.id, result: {} });
|
|
8324
|
+
return;
|
|
8325
|
+
}
|
|
8326
|
+
if (m.id != null && pending[m.id]) { var cb = pending[m.id]; delete pending[m.id]; cb(m.result, m.error); }
|
|
8327
|
+
});
|
|
8328
|
+
function request(method, params) {
|
|
8329
|
+
return new Promise(function (resolve, reject) {
|
|
8330
|
+
var id = nextId++;
|
|
8331
|
+
pending[id] = function (r, err) { if (err) reject(new Error((err && err.message) || String(err))); else resolve(r); };
|
|
8332
|
+
post({ jsonrpc: "2.0", id: id, method: method, params: params });
|
|
8333
|
+
});
|
|
8334
|
+
}
|
|
8335
|
+
window.McpApp = {
|
|
8336
|
+
connect: function () {
|
|
8337
|
+
if (window.parent === window) return Promise.reject(new Error("no host (standalone)"));
|
|
8338
|
+
return request("ui/initialize", {
|
|
8339
|
+
appInfo: { name: "agentproto-app", version: "1.0" },
|
|
8340
|
+
appCapabilities: { availableDisplayModes: ["inline", "fullscreen"] },
|
|
8341
|
+
protocolVersion: "2026-01-26"
|
|
8342
|
+
}).then(function () {
|
|
8343
|
+
post({ jsonrpc: "2.0", method: "ui/notifications/initialized", params: {} });
|
|
8344
|
+
return {
|
|
8345
|
+
callTool: function (name, args) { return request("tools/call", { name: name, arguments: args || {} }); },
|
|
8346
|
+
sendMessage: function (p) {
|
|
8347
|
+
var params = typeof p === "string" ? { content: p } : (p || {});
|
|
8348
|
+
var content = typeof params.content === "string" ? [{ type: "text", text: params.content }] : (params.content || []);
|
|
8349
|
+
return request("ui/message", { role: "user", content: content });
|
|
8350
|
+
},
|
|
8351
|
+
updateModelContext: function (p) { return request("ui/update-model-context", p || {}); },
|
|
8352
|
+
openLink: function (url) { return request("ui/open-link", { url: url }); },
|
|
8353
|
+
onTeardown: function (cb) { teardownCbs.push(cb); }
|
|
8354
|
+
};
|
|
8355
|
+
});
|
|
8356
|
+
}
|
|
8357
|
+
};
|
|
8358
|
+
})();
|
|
8359
|
+
</script>
|
|
8360
|
+
`;
|
|
8361
|
+
function injectAfterStructuralTag(html, script) {
|
|
8362
|
+
for (const tag of ["head", "body", "html"]) {
|
|
8363
|
+
const match = html.match(new RegExp(`<${tag}[^>]*>`, "i"));
|
|
8364
|
+
if (match?.index !== void 0) {
|
|
8365
|
+
const insertAt = match.index + match[0].length;
|
|
8366
|
+
return html.slice(0, insertAt) + script + html.slice(insertAt);
|
|
8367
|
+
}
|
|
8368
|
+
}
|
|
8369
|
+
return script + html;
|
|
8370
|
+
}
|
|
8371
|
+
function injectMcpAppBridge(html) {
|
|
8372
|
+
const hasBridge = /window\.McpApp\s*=/.test(html);
|
|
8373
|
+
const hasRunnerSelect = /window\.AgentprotoUI\s*=/.test(html);
|
|
8374
|
+
if (hasBridge && hasRunnerSelect) return html;
|
|
8375
|
+
const script = (hasBridge ? "" : MCP_APP_BRIDGE_SCRIPT) + (hasRunnerSelect ? "" : RUNNER_SELECT_SCRIPT);
|
|
8376
|
+
return injectAfterStructuralTag(html, script);
|
|
8377
|
+
}
|
|
8378
|
+
var STANDALONE_REST_BRIDGE_SCRIPT = `<script>
|
|
8379
|
+
(function () {
|
|
8380
|
+
window.__AGENTPROTO_UI_TRANSPORT__ = "http";
|
|
8381
|
+
if (window.McpApp) return;
|
|
8382
|
+
window.McpApp = {
|
|
8383
|
+
connect: function () {
|
|
8384
|
+
return Promise.resolve({
|
|
8385
|
+
callTool: function (name, args) {
|
|
8386
|
+
return fetch("./tool-call", {
|
|
8387
|
+
method: "POST",
|
|
8388
|
+
headers: { "content-type": "application/json" },
|
|
8389
|
+
body: JSON.stringify({ tool: name, args: args || {} })
|
|
8390
|
+
}).then(function (res) {
|
|
8391
|
+
return res.json().catch(function () {
|
|
8392
|
+
throw new Error("tool-call failed: HTTP " + res.status);
|
|
8393
|
+
}).then(function (body) {
|
|
8394
|
+
if (!res.ok) throw new Error((body && (body.message || body.error)) || ("tool-call failed: HTTP " + res.status));
|
|
8395
|
+
return body;
|
|
8396
|
+
});
|
|
8397
|
+
});
|
|
8398
|
+
},
|
|
8399
|
+
sendMessage: function () {
|
|
8400
|
+
return Promise.reject(new Error("sendMessage: no chat host (standalone mode)"));
|
|
8401
|
+
},
|
|
8402
|
+
updateModelContext: function () {
|
|
8403
|
+
return Promise.reject(new Error("updateModelContext: no host (standalone mode)"));
|
|
8404
|
+
},
|
|
8405
|
+
openLink: function (url) {
|
|
8406
|
+
window.open(url, "_blank");
|
|
8407
|
+
return Promise.resolve({});
|
|
8408
|
+
},
|
|
8409
|
+
onTeardown: function () {}
|
|
8410
|
+
});
|
|
8411
|
+
}
|
|
8412
|
+
};
|
|
8413
|
+
})();
|
|
8414
|
+
</script>
|
|
8415
|
+
`;
|
|
8416
|
+
function injectStandaloneAppBridge(html, baseUrl) {
|
|
8417
|
+
const hasRunnerSelect = /window\.AgentprotoUI\s*=/.test(html);
|
|
8418
|
+
const baseUrlScript = baseUrl ? `<script>window.__AGENTPROTO_BASEURL__=${JSON.stringify(baseUrl).replace(/</g, "\\u003c")};</script>` : "";
|
|
8419
|
+
const script = baseUrlScript + STANDALONE_REST_BRIDGE_SCRIPT + (hasRunnerSelect ? "" : RUNNER_SELECT_SCRIPT);
|
|
8420
|
+
return injectAfterStructuralTag(html, script);
|
|
8421
|
+
}
|
|
8422
|
+
function createUiHtmlCache() {
|
|
8423
|
+
const cache = /* @__PURE__ */ new Map();
|
|
8424
|
+
return {
|
|
8425
|
+
async get(path, version) {
|
|
8426
|
+
const cached = cache.get(path);
|
|
8427
|
+
if (cached && cached.version === version) return cached.html;
|
|
8428
|
+
const raw = await readFile(path, "utf8");
|
|
8429
|
+
const html = injectMcpAppBridge(raw);
|
|
8430
|
+
cache.set(path, { version, html });
|
|
8431
|
+
return html;
|
|
8432
|
+
}
|
|
8433
|
+
};
|
|
8434
|
+
}
|
|
8435
|
+
async function makeInstalledAppUiApps(appRegistry, cache, existingToolNames) {
|
|
8436
|
+
const apps = [];
|
|
8437
|
+
const seen = new Set(existingToolNames);
|
|
8438
|
+
for (const app of appRegistry.listApps()) {
|
|
8439
|
+
const ui = app.ui;
|
|
8440
|
+
if (!ui) continue;
|
|
8441
|
+
const toolId = appUiToolId(app.appId);
|
|
8442
|
+
if (seen.has(toolId)) {
|
|
8443
|
+
console.warn(
|
|
8444
|
+
`[app-ui-apps] skipping UI panel for app "${app.appId}": tool id "${toolId}" collides with an existing tool or another installed app's panel.`
|
|
8445
|
+
);
|
|
8446
|
+
continue;
|
|
8447
|
+
}
|
|
8448
|
+
let html;
|
|
8449
|
+
try {
|
|
8450
|
+
html = await cache.get(ui.path, app.updatedAt);
|
|
8451
|
+
} catch (err) {
|
|
8452
|
+
console.warn(
|
|
8453
|
+
`[app-ui-apps] skipping UI panel for app "${app.appId}": could not read "${ui.path}": ${err instanceof Error ? err.message : String(err)}`
|
|
8454
|
+
);
|
|
8455
|
+
continue;
|
|
8456
|
+
}
|
|
8457
|
+
seen.add(toolId);
|
|
8458
|
+
apps.push({
|
|
8459
|
+
id: toolId,
|
|
8460
|
+
title: ui.title ?? app.name ?? app.appId,
|
|
8461
|
+
...ui.description ? { description: ui.description } : {},
|
|
8462
|
+
inputSchema: z.object({}),
|
|
8463
|
+
execute: async () => ({ appId: app.appId, tools: ui.tools ?? [] }),
|
|
8464
|
+
html,
|
|
8465
|
+
...ui.csp ? {
|
|
8466
|
+
csp: {
|
|
8467
|
+
...ui.csp.connectDomains ? { connectDomains: [...ui.csp.connectDomains] } : {},
|
|
8468
|
+
...ui.csp.resourceDomains ? { resourceDomains: [...ui.csp.resourceDomains] } : {},
|
|
8469
|
+
...ui.csp.frameDomains ? { frameDomains: [...ui.csp.frameDomains] } : {}
|
|
8470
|
+
}
|
|
8471
|
+
} : {}
|
|
8472
|
+
});
|
|
8473
|
+
}
|
|
8474
|
+
return apps;
|
|
8475
|
+
}
|
|
8210
8476
|
function stripAnsi(s) {
|
|
8211
8477
|
let result = s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
|
|
8212
8478
|
result = result.replace(/\r\n/g, "\n");
|
|
@@ -8261,8 +8527,12 @@ function registerAgentTools(server, opts) {
|
|
|
8261
8527
|
resolveSandboxProvider: resolveSandboxProvider2,
|
|
8262
8528
|
provisionWorktree,
|
|
8263
8529
|
resolveWorktreeIsolation,
|
|
8264
|
-
supervisor
|
|
8530
|
+
supervisor,
|
|
8531
|
+
defaultAgentPromptInterrupt,
|
|
8532
|
+
isSessionChatInstalled
|
|
8265
8533
|
} = opts;
|
|
8534
|
+
const interruptDefault = defaultAgentPromptInterrupt ?? false;
|
|
8535
|
+
const sessionChatResourceUri = isSessionChatInstalled?.() ? `ui://${appUiToolId(SESSION_CHAT_APP_ID)}/view` : "ui://agentproto_session_chat/view";
|
|
8266
8536
|
server.registerTool(
|
|
8267
8537
|
"agent_start",
|
|
8268
8538
|
{
|
|
@@ -8498,14 +8768,25 @@ function registerAgentTools(server, opts) {
|
|
|
8498
8768
|
"Isolate this session in its OWN git worktree instead of spawning directly in `cwd` \u2014 so a parallel agent can't collide on the working tree. `true` provisions a worktree on a fresh branch `wt/<slug>` cut from origin/main (slug auto-minted from `label`); pass `{ slug, base, async }` to pin either, or opt into an early return (`async`, see that field's own description). The daemon boots the worktree (git worktree add + the repo's agentproto.json setup hooks) and spawns `adapter` THERE; the session's cwd, and every path it edits, live inside the worktree. Honoured only for a ROOT spawn (a spawn made THROUGH an orchestrator inherits its parent's tree \u2014 no second worktree; an EXPLICIT `worktree` on such a nested spawn is REJECTED, not silently ignored \u2014 use `sandbox` to isolate a child) and only when `cwd` is inside a git repo (nothing to isolate otherwise \u21D2 spawns plain, no error). The daemon's `worktrees.isolation` policy may force this ON for every root spawn (`always`) or OFF (`never`, which REJECTS an explicit `worktree`). Ignored for a `sandbox` spawn (the box already isolates). The worktree is NOT auto-removed on session close \u2014 it holds the agent's work; tear it down with `agentproto worktree rm|archive|gc`."
|
|
8499
8769
|
)
|
|
8500
8770
|
},
|
|
8501
|
-
//
|
|
8502
|
-
//
|
|
8503
|
-
// the tool-definition level — same mechanism
|
|
8504
|
-
// mcp-apps-adapter.ts).
|
|
8505
|
-
//
|
|
8771
|
+
// Session-chat widget: rendering agent_start's result auto-mounts the
|
|
8772
|
+
// session-chat launcher for the new session (ext-apps
|
|
8773
|
+
// `_meta.ui.resourceUri` at the tool-definition level — same mechanism
|
|
8774
|
+
// as the panel apps in mcp-apps-adapter.ts). The widget reads the
|
|
8775
|
+
// spawned `{ id: "sess_…" }` off the host's tool-result notification
|
|
8776
|
+
// and deep-links the installed `@agentik/session-chat` app straight
|
|
8777
|
+
// into that session (apps/src/session-chat/panel.ts). The older
|
|
8778
|
+
// `ui://live_session/view` resource stays registered for its own
|
|
8779
|
+
// `live_session` tool and other consumers — only this binding moved.
|
|
8780
|
+
// `visibility:["model","app"]` keeps agent_start fully usable by the
|
|
8781
|
+
// model AND lets the widget re-call it if needed. `resourceUri` binds
|
|
8782
|
+
// to the native `app_ui_session_chat` MCP Apps tool once
|
|
8783
|
+
// `@agentik/session-chat` is installed (`isSessionChatInstalled`
|
|
8784
|
+
// above) — the loopback-HTTP `agentproto_session_chat` launcher this
|
|
8785
|
+
// otherwise binds to is unreachable under a strict-CSP host (Codex),
|
|
8786
|
+
// and builtin-apps.ts stops mounting it once the native tool exists.
|
|
8506
8787
|
_meta: {
|
|
8507
8788
|
ui: {
|
|
8508
|
-
resourceUri:
|
|
8789
|
+
resourceUri: sessionChatResourceUri,
|
|
8509
8790
|
visibility: ["model", "app"]
|
|
8510
8791
|
}
|
|
8511
8792
|
}
|
|
@@ -8618,7 +8899,7 @@ function registerAgentTools(server, opts) {
|
|
|
8618
8899
|
id: sessionIdAliasField,
|
|
8619
8900
|
prompt: z.string().min(1).describe("The next user turn (plain text)."),
|
|
8620
8901
|
interrupt: z.boolean().optional().describe(
|
|
8621
|
-
"When true and the session is mid-turn, cancel the in-flight turn and deliver this prompt on the same session instead of
|
|
8902
|
+
"When true and the session is mid-turn, cancel the in-flight turn and deliver this prompt on the same session immediately instead of queueing it behind the current turn. No-op when the session is already idle. UNSET falls back to the daemon default `defaults.agentPromptInterrupt` in config.json (false unless an operator changed it) \u2014 so a mid-turn target queues by default; pass `interrupt: true` explicitly to cut now."
|
|
8622
8903
|
),
|
|
8623
8904
|
queue: z.boolean().optional().describe(
|
|
8624
8905
|
"When the session is mid-turn, queue this prompt (FIFO) and dispatch it automatically once the current turn ends instead of rejecting. Default true. Explicit false restores the old reject-when-busy behavior."
|
|
@@ -8629,8 +8910,9 @@ function registerAgentTools(server, opts) {
|
|
|
8629
8910
|
if (!sessionId) return missingSessionIdError("agent_prompt");
|
|
8630
8911
|
try {
|
|
8631
8912
|
const promptSource = callerScope?.ownerSessionId ?? callerSessionId;
|
|
8632
|
-
|
|
8633
|
-
|
|
8913
|
+
const effectiveInterrupt = input.interrupt ?? interruptDefault;
|
|
8914
|
+
const { queued } = await registry.enqueuePrompt(sessionId, input.prompt, {
|
|
8915
|
+
interrupt: effectiveInterrupt,
|
|
8634
8916
|
// Queue by default: a mid-turn session holds the prompt in its
|
|
8635
8917
|
// FIFO queue and dispatches it at turn end, so callers never
|
|
8636
8918
|
// lose a prompt to the busy rejection. Explicit `queue: false`
|
|
@@ -8638,12 +8920,21 @@ function registerAgentTools(server, opts) {
|
|
|
8638
8920
|
queue: input.queue ?? true,
|
|
8639
8921
|
...promptSource ? { source: `agent:${promptSource}` } : {}
|
|
8640
8922
|
});
|
|
8923
|
+
const hintQueued = queued && input.interrupt === void 0;
|
|
8641
8924
|
return {
|
|
8642
8925
|
content: [
|
|
8643
8926
|
{
|
|
8644
8927
|
type: "text",
|
|
8645
8928
|
text: JSON.stringify(
|
|
8646
|
-
{
|
|
8929
|
+
{
|
|
8930
|
+
ok: true,
|
|
8931
|
+
sessionId,
|
|
8932
|
+
queued: true,
|
|
8933
|
+
...hintQueued ? {
|
|
8934
|
+
delivery: "queued-mid-turn",
|
|
8935
|
+
hint: "Message queued \u2014 it will only be delivered when the target's CURRENT turn ends. If it's urgent, re-send with interrupt: true (cancels the in-flight turn and redirects the session onto this prompt now)."
|
|
8936
|
+
} : {}
|
|
8937
|
+
},
|
|
8647
8938
|
null,
|
|
8648
8939
|
2
|
|
8649
8940
|
)
|
|
@@ -8672,9 +8963,12 @@ function registerAgentTools(server, opts) {
|
|
|
8672
8963
|
);
|
|
8673
8964
|
server.tool(
|
|
8674
8965
|
"message_parent",
|
|
8675
|
-
"Report a message UP to the session that spawned you (your parent/supervisor) \u2014 a result, a progress update, or a blocker. No session id needed: the daemon resolves your recorded parent from your own session identity (also visible as the AGENTPROTO_PARENT_SESSION_ID env var). Delivered as a prompt when the parent is idle, or queued onto its next turn when it's mid-turn (never interrupts). Errors if this session has no recorded parent or the parent is gone.",
|
|
8966
|
+
"Report a message UP to the session that spawned you (your parent/supervisor) \u2014 a result, a progress update, or a blocker. No session id needed: the daemon resolves your recorded parent from your own session identity (also visible as the AGENTPROTO_PARENT_SESSION_ID env var). Delivered as a prompt when the parent is idle, or queued onto its next turn when it's mid-turn (never interrupts, by default). Pass `interrupt: true` to CUT a mid-turn parent immediately \u2014 cancel its in-flight turn and redirect it onto this message now (same-context cancel, like `agent_prompt`'s `interrupt`) \u2014 for a genuinely urgent report the parent must act on before it finishes what it's doing. Errors if this session has no recorded parent or the parent is gone.",
|
|
8676
8967
|
{
|
|
8677
|
-
message: z.string().min(1).describe("The message to deliver to your parent session (plain text).")
|
|
8968
|
+
message: z.string().min(1).describe("The message to deliver to your parent session (plain text)."),
|
|
8969
|
+
interrupt: z.boolean().optional().describe(
|
|
8970
|
+
"When true and the parent is mid-turn, cancel its in-flight turn and deliver this message immediately instead of queueing it onto the parent's next turn. No-op when the parent is idle (delivered as a normal prompt either way). UNSET falls back to the daemon default `defaults.agentPromptInterrupt` in config.json (false unless an operator changed it) \u2014 so a report never interrupts by default; pass `interrupt: true` explicitly to cut."
|
|
8971
|
+
)
|
|
8678
8972
|
},
|
|
8679
8973
|
async (input) => {
|
|
8680
8974
|
const fail = (text10) => ({
|
|
@@ -8708,11 +9002,12 @@ function registerAgentTools(server, opts) {
|
|
|
8708
9002
|
}
|
|
8709
9003
|
const who = self.label ?? selfId;
|
|
8710
9004
|
const notice = `[child-message] ${who} (${selfId}): ${input.message}`;
|
|
8711
|
-
const
|
|
9005
|
+
const effectiveInterrupt = input.interrupt ?? interruptDefault;
|
|
9006
|
+
const done = (delivery, extra) => ({
|
|
8712
9007
|
content: [
|
|
8713
9008
|
{
|
|
8714
9009
|
type: "text",
|
|
8715
|
-
text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery })
|
|
9010
|
+
text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery, ...extra })
|
|
8716
9011
|
}
|
|
8717
9012
|
]
|
|
8718
9013
|
});
|
|
@@ -8723,12 +9018,27 @@ function registerAgentTools(server, opts) {
|
|
|
8723
9018
|
} catch {
|
|
8724
9019
|
}
|
|
8725
9020
|
}
|
|
9021
|
+
if (effectiveInterrupt) {
|
|
9022
|
+
try {
|
|
9023
|
+
await registry.enqueuePrompt(parentId, notice, {
|
|
9024
|
+
interrupt: true,
|
|
9025
|
+
origin: `child:${who}`
|
|
9026
|
+
});
|
|
9027
|
+
return done("interrupted");
|
|
9028
|
+
} catch {
|
|
9029
|
+
}
|
|
9030
|
+
}
|
|
8726
9031
|
if (!registry.stampPendingChildCrashNotice(parentId, notice)) {
|
|
8727
9032
|
return fail(
|
|
8728
9033
|
`message_parent: parent session "${parentId}" vanished mid-delivery.`
|
|
8729
9034
|
);
|
|
8730
9035
|
}
|
|
8731
|
-
return done(
|
|
9036
|
+
return done(
|
|
9037
|
+
"queued-next-turn",
|
|
9038
|
+
input.interrupt === void 0 ? {
|
|
9039
|
+
hint: "Message queued \u2014 it will only reach the parent when its CURRENT turn ends. If it's urgent, re-send with interrupt: true (cancels the parent's in-flight turn and redirects it onto this message now)."
|
|
9040
|
+
} : void 0
|
|
9041
|
+
);
|
|
8732
9042
|
}
|
|
8733
9043
|
);
|
|
8734
9044
|
server.tool(
|
|
@@ -10316,6 +10626,149 @@ function filterSessionObserver(inner, shouldObserve) {
|
|
|
10316
10626
|
init_tool_presenter();
|
|
10317
10627
|
init_transcript_writer();
|
|
10318
10628
|
|
|
10629
|
+
// src/session-titler.ts
|
|
10630
|
+
init_config();
|
|
10631
|
+
init_transcript_writer();
|
|
10632
|
+
var DEFAULT_TITLER_MODEL = "z-ai/glm-5.2@openrouter";
|
|
10633
|
+
var DEFAULT_LABELS = /* @__PURE__ */ new Set(["chat-starter", "chat-starter-autoprompt", "agentproto"]);
|
|
10634
|
+
var MIN_TURN_CHARS = 200;
|
|
10635
|
+
var titledSessions = /* @__PURE__ */ new Set();
|
|
10636
|
+
async function resolveTitlerConfig() {
|
|
10637
|
+
const cfg = await loadConfig();
|
|
10638
|
+
const t = cfg.titler;
|
|
10639
|
+
return { enabled: t?.enabled === true, model: t?.model ?? DEFAULT_TITLER_MODEL };
|
|
10640
|
+
}
|
|
10641
|
+
function isDefaultLabel(desc) {
|
|
10642
|
+
const label = desc.label?.trim() ?? "";
|
|
10643
|
+
if (label === "") return true;
|
|
10644
|
+
if (desc.title !== void 0 && label === desc.title) return true;
|
|
10645
|
+
return DEFAULT_LABELS.has(label);
|
|
10646
|
+
}
|
|
10647
|
+
function fallbackTitle(userText) {
|
|
10648
|
+
const firstLine = userText.split(/\r?\n/).find((l) => l.trim().length > 0)?.trim() ?? "";
|
|
10649
|
+
return firstLine.split(/\s+/).filter(Boolean).slice(0, 6).join(" ");
|
|
10650
|
+
}
|
|
10651
|
+
function sanitizeTitle(raw) {
|
|
10652
|
+
if (!raw) return void 0;
|
|
10653
|
+
const cleaned = raw.replace(/[`*_"“”'']/g, "").split(/\r?\n/)[0]?.trim();
|
|
10654
|
+
if (!cleaned) return void 0;
|
|
10655
|
+
const words = cleaned.split(/\s+/).map((w) => w.replace(/[.,;:!?]+$/u, "")).filter((w) => /[\p{L}\p{N}]/u.test(w)).slice(0, 8);
|
|
10656
|
+
const title = words.join(" ");
|
|
10657
|
+
return title.length > 0 ? title : void 0;
|
|
10658
|
+
}
|
|
10659
|
+
async function readFirstTurn(sessionId) {
|
|
10660
|
+
let stream;
|
|
10661
|
+
try {
|
|
10662
|
+
stream = createReadStream(sessionEventsPath(sessionId), { encoding: "utf8" });
|
|
10663
|
+
await new Promise((resolve31, reject) => {
|
|
10664
|
+
stream.once("error", reject);
|
|
10665
|
+
stream.once("open", resolve31);
|
|
10666
|
+
});
|
|
10667
|
+
} catch {
|
|
10668
|
+
return null;
|
|
10669
|
+
}
|
|
10670
|
+
let userText = "";
|
|
10671
|
+
let assistantText = "";
|
|
10672
|
+
let sawUserPrompt = false;
|
|
10673
|
+
try {
|
|
10674
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
10675
|
+
for await (const line of rl) {
|
|
10676
|
+
const trimmed = line.trim();
|
|
10677
|
+
if (!trimmed) continue;
|
|
10678
|
+
let rec;
|
|
10679
|
+
try {
|
|
10680
|
+
rec = JSON.parse(trimmed);
|
|
10681
|
+
} catch {
|
|
10682
|
+
continue;
|
|
10683
|
+
}
|
|
10684
|
+
if (rec.kind === "user-prompt" && typeof rec.text === "string") {
|
|
10685
|
+
userText = rec.text;
|
|
10686
|
+
sawUserPrompt = true;
|
|
10687
|
+
} else if (rec.kind === "text-delta" && typeof rec.text === "string") {
|
|
10688
|
+
assistantText += rec.text;
|
|
10689
|
+
} else if (rec.kind === "turn-end") {
|
|
10690
|
+
break;
|
|
10691
|
+
}
|
|
10692
|
+
}
|
|
10693
|
+
} catch {
|
|
10694
|
+
return null;
|
|
10695
|
+
}
|
|
10696
|
+
if (!sawUserPrompt) return null;
|
|
10697
|
+
return { userText, assistantText };
|
|
10698
|
+
}
|
|
10699
|
+
async function generateTitleViaLlm(model, input) {
|
|
10700
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
10701
|
+
if (!apiKey) return void 0;
|
|
10702
|
+
const excerpt = (s, n) => s.length <= n ? s : `${s.slice(0, n)}\u2026`;
|
|
10703
|
+
const body = {
|
|
10704
|
+
model,
|
|
10705
|
+
max_tokens: 64,
|
|
10706
|
+
messages: [
|
|
10707
|
+
{
|
|
10708
|
+
role: "system",
|
|
10709
|
+
content: "You write short prosaic titles for agent sessions. Reply with ONLY the title: 4-8 plain words, no quotes, no punctuation at the end, describing what the session is doing."
|
|
10710
|
+
},
|
|
10711
|
+
{
|
|
10712
|
+
role: "user",
|
|
10713
|
+
content: `User asked: ${excerpt(input.userText, 2e3)}
|
|
10714
|
+
Assistant worked on: ${excerpt(input.assistantText, 2e3)}
|
|
10715
|
+
Title:`
|
|
10716
|
+
}
|
|
10717
|
+
]
|
|
10718
|
+
};
|
|
10719
|
+
try {
|
|
10720
|
+
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
10721
|
+
method: "POST",
|
|
10722
|
+
headers: {
|
|
10723
|
+
"content-type": "application/json",
|
|
10724
|
+
authorization: `Bearer ${apiKey}`
|
|
10725
|
+
},
|
|
10726
|
+
body: JSON.stringify(body),
|
|
10727
|
+
signal: AbortSignal.timeout(15e3)
|
|
10728
|
+
});
|
|
10729
|
+
if (!res.ok) return void 0;
|
|
10730
|
+
const parsed = await res.json();
|
|
10731
|
+
const content = parsed.choices?.[0]?.message?.content;
|
|
10732
|
+
return typeof content === "string" ? content : void 0;
|
|
10733
|
+
} catch {
|
|
10734
|
+
return void 0;
|
|
10735
|
+
}
|
|
10736
|
+
}
|
|
10737
|
+
async function maybeTitleSession(registry, sessionId, opts = {}) {
|
|
10738
|
+
try {
|
|
10739
|
+
if (titledSessions.has(sessionId)) return null;
|
|
10740
|
+
titledSessions.add(sessionId);
|
|
10741
|
+
const desc = registry.get(sessionId);
|
|
10742
|
+
if (!desc || desc.kind !== "agent-cli") return null;
|
|
10743
|
+
if (desc.renamedByUser === true) return null;
|
|
10744
|
+
if (!isDefaultLabel(desc)) return null;
|
|
10745
|
+
const generate = opts.generate ?? (async (t) => {
|
|
10746
|
+
const { enabled, model } = await resolveTitlerConfig();
|
|
10747
|
+
if (!enabled) return void 0;
|
|
10748
|
+
return generateTitleViaLlm(model, t);
|
|
10749
|
+
});
|
|
10750
|
+
if (!opts.generate) {
|
|
10751
|
+
const { enabled } = await resolveTitlerConfig();
|
|
10752
|
+
if (!enabled) return null;
|
|
10753
|
+
}
|
|
10754
|
+
const read = opts.readFirstTurn ?? readFirstTurn;
|
|
10755
|
+
const turn = await read(sessionId);
|
|
10756
|
+
if (!turn) return null;
|
|
10757
|
+
const minChars = opts.minChars ?? MIN_TURN_CHARS;
|
|
10758
|
+
if (turn.userText.length + turn.assistantText.length < minChars) return null;
|
|
10759
|
+
const generated = sanitizeTitle(await generate(turn));
|
|
10760
|
+
const title = generated ?? fallbackTitle(turn.userText);
|
|
10761
|
+
if (!title) return null;
|
|
10762
|
+
registry.renameSession(sessionId, { label: title });
|
|
10763
|
+
return title;
|
|
10764
|
+
} catch (err) {
|
|
10765
|
+
console.warn(
|
|
10766
|
+
`[session-titler] maybeTitleSession(${sessionId}) failed: ${err instanceof Error ? err.message : String(err)}`
|
|
10767
|
+
);
|
|
10768
|
+
return null;
|
|
10769
|
+
}
|
|
10770
|
+
}
|
|
10771
|
+
|
|
10319
10772
|
// src/resume-context-digest.ts
|
|
10320
10773
|
init_transcript_export();
|
|
10321
10774
|
var DIGEST_CHAR_BUDGET = 7e3;
|
|
@@ -10915,6 +11368,9 @@ function promptOriginLabel(item) {
|
|
|
10915
11368
|
if (code.startsWith("child:")) return `child ${code.slice("child:".length)}`;
|
|
10916
11369
|
return code;
|
|
10917
11370
|
}
|
|
11371
|
+
function isShellOnlyRow(desc) {
|
|
11372
|
+
return desc.kind === "command" || desc.kind === "terminal" && !isConversationTerminal(desc);
|
|
11373
|
+
}
|
|
10918
11374
|
function toSessionSummary(desc) {
|
|
10919
11375
|
return {
|
|
10920
11376
|
id: desc.id,
|
|
@@ -11308,6 +11764,7 @@ function createSessionsRegistry(opts) {
|
|
|
11308
11764
|
}
|
|
11309
11765
|
const emitExited = (rt) => {
|
|
11310
11766
|
if (rt.exitedEmitted) return;
|
|
11767
|
+
rt.linkProbeStop?.();
|
|
11311
11768
|
cancelPendingPermissionsForSession(rt);
|
|
11312
11769
|
if (runWorktreeAutoReclaim && rt.desc.worktreeAutoProvisioned && rt.desc.worktreePath) {
|
|
11313
11770
|
const worktreePath = rt.desc.worktreePath;
|
|
@@ -11489,8 +11946,9 @@ function createSessionsRegistry(opts) {
|
|
|
11489
11946
|
const recordConversationLink = (rt, adapterSessionIdOverride) => {
|
|
11490
11947
|
if (!persist || !partitioned) return;
|
|
11491
11948
|
const desc = rt.desc;
|
|
11492
|
-
|
|
11493
|
-
|
|
11949
|
+
const isConvTerminal = desc.kind === "terminal" && isConversationTerminal(desc);
|
|
11950
|
+
if (desc.kind !== "agent-cli" && !isConvTerminal) return;
|
|
11951
|
+
const adapterSlug = desc.adapterSlug ?? (isConvTerminal ? conversationTerminalSlugFor(desc) : void 0);
|
|
11494
11952
|
const adapterSessionId = adapterSessionIdOverride ?? desc.adapterSessionId;
|
|
11495
11953
|
const cwd = desc.cwd;
|
|
11496
11954
|
if (!adapterSlug || !adapterSessionId || !cwd) return;
|
|
@@ -11534,6 +11992,81 @@ function createSessionsRegistry(opts) {
|
|
|
11534
11992
|
void persistSnapshot();
|
|
11535
11993
|
}, PERSIST_DEBOUNCE_MS2);
|
|
11536
11994
|
};
|
|
11995
|
+
const conversationLinkProbeMs = opts?.conversationLinkProbeMs ?? {
|
|
11996
|
+
initialMs: 3e3,
|
|
11997
|
+
intervalMs: 15e3
|
|
11998
|
+
};
|
|
11999
|
+
const startConversationTerminalLinkProbe = (rt) => {
|
|
12000
|
+
const desc = rt.desc;
|
|
12001
|
+
const slug = desc.adapterSlug ?? conversationTerminalSlugFor(desc);
|
|
12002
|
+
const store = slug ? CONVERSATION_STORES[slug] : void 0;
|
|
12003
|
+
const cwd = desc.cwd;
|
|
12004
|
+
if (!store || !cwd) return;
|
|
12005
|
+
let inFlight = false;
|
|
12006
|
+
let stopped = false;
|
|
12007
|
+
const timers = [];
|
|
12008
|
+
const stop = () => {
|
|
12009
|
+
stopped = true;
|
|
12010
|
+
for (const t of timers) clearTimeout(t);
|
|
12011
|
+
timers.length = 0;
|
|
12012
|
+
};
|
|
12013
|
+
rt.linkProbeStop = stop;
|
|
12014
|
+
const tick = async () => {
|
|
12015
|
+
if (stopped || inFlight) return;
|
|
12016
|
+
if (desc.status !== "running") {
|
|
12017
|
+
stop();
|
|
12018
|
+
return;
|
|
12019
|
+
}
|
|
12020
|
+
if (!desc.lastOutputAt) return;
|
|
12021
|
+
if (desc.adapterSessionId) {
|
|
12022
|
+
stop();
|
|
12023
|
+
return;
|
|
12024
|
+
}
|
|
12025
|
+
inFlight = true;
|
|
12026
|
+
try {
|
|
12027
|
+
const candidates = await store.discover({
|
|
12028
|
+
cwd,
|
|
12029
|
+
since: desc.startedAt,
|
|
12030
|
+
// A PTY writes through the provider's native arm, never ACP.
|
|
12031
|
+
attachmentMode: "native"
|
|
12032
|
+
// No configDir on purpose: nothing threaded CLAUDE_CONFIG_DIR
|
|
12033
|
+
// into this PTY, so the global store is where the file is.
|
|
12034
|
+
});
|
|
12035
|
+
if (candidates.length === 1) {
|
|
12036
|
+
const candidate = candidates[0];
|
|
12037
|
+
desc.adapterSessionId = candidate.conversationId;
|
|
12038
|
+
desc.resumeMetadata = {
|
|
12039
|
+
...desc.resumeMetadata ?? {},
|
|
12040
|
+
[store.storeAs]: candidate.conversationId
|
|
12041
|
+
};
|
|
12042
|
+
delete desc.linkStatus;
|
|
12043
|
+
if (!desc.title && candidate.preview) {
|
|
12044
|
+
const derived = deriveSessionTitle(candidate.preview);
|
|
12045
|
+
if (derived) desc.title = derived;
|
|
12046
|
+
}
|
|
12047
|
+
schedulePersist();
|
|
12048
|
+
recordConversationLink(rt, candidate.conversationId);
|
|
12049
|
+
stop();
|
|
12050
|
+
} else if (candidates.length > 1) {
|
|
12051
|
+
desc.linkStatus = "ambiguous";
|
|
12052
|
+
schedulePersist();
|
|
12053
|
+
}
|
|
12054
|
+
} catch {
|
|
12055
|
+
} finally {
|
|
12056
|
+
inFlight = false;
|
|
12057
|
+
}
|
|
12058
|
+
};
|
|
12059
|
+
const schedule = (delayMs) => {
|
|
12060
|
+
if (stopped) return;
|
|
12061
|
+
const t = setTimeout(() => {
|
|
12062
|
+
void tick().finally(() => schedule(conversationLinkProbeMs.intervalMs));
|
|
12063
|
+
}, delayMs);
|
|
12064
|
+
t.unref?.();
|
|
12065
|
+
timers.length = 0;
|
|
12066
|
+
timers.push(t);
|
|
12067
|
+
};
|
|
12068
|
+
schedule(conversationLinkProbeMs.initialMs);
|
|
12069
|
+
};
|
|
11537
12070
|
const snapshotRows = () => Array.from(sessions.values()).map((s) => {
|
|
11538
12071
|
const {
|
|
11539
12072
|
processAlive: _processAlive,
|
|
@@ -11617,8 +12150,9 @@ function createSessionsRegistry(opts) {
|
|
|
11617
12150
|
rt.emitter.emit("line", { line, stream });
|
|
11618
12151
|
};
|
|
11619
12152
|
const sniffResumeHints = (rt, line) => {
|
|
11620
|
-
if (rt.desc.kind !== "agent-cli") return;
|
|
11621
|
-
const
|
|
12153
|
+
if (rt.desc.kind !== "agent-cli" && rt.conversationTerminal !== true) return;
|
|
12154
|
+
const strategySlug = rt.desc.adapterSlug ?? conversationTerminalSlugFor(rt.desc);
|
|
12155
|
+
const strategy = strategySlug ? RESUME_STRATEGIES[strategySlug] : void 0;
|
|
11622
12156
|
if (!strategy?.outputHint) return;
|
|
11623
12157
|
const plain = stripAnsiCodes(line);
|
|
11624
12158
|
const m = plain.match(strategy.outputHint);
|
|
@@ -11631,6 +12165,14 @@ function createSessionsRegistry(opts) {
|
|
|
11631
12165
|
recordConversationLink(rt, m[1]);
|
|
11632
12166
|
}
|
|
11633
12167
|
};
|
|
12168
|
+
const sniffConversationTerminalChunk = (rt, chunk) => {
|
|
12169
|
+
const text10 = (rt.sniffCarry ?? "") + chunk.toString("utf8");
|
|
12170
|
+
const lines = text10.split(/\r?\n|\r/);
|
|
12171
|
+
rt.sniffCarry = (lines.pop() ?? "").slice(-4096);
|
|
12172
|
+
for (const line of lines) {
|
|
12173
|
+
if (line) sniffResumeHints(rt, line);
|
|
12174
|
+
}
|
|
12175
|
+
};
|
|
11634
12176
|
const appendBytes = (rt, chunk) => {
|
|
11635
12177
|
terminalTranscriptWriter.appendChunk(rt.desc.id, chunk);
|
|
11636
12178
|
rt.recentBytes.push(chunk);
|
|
@@ -11640,6 +12182,7 @@ function createSessionsRegistry(opts) {
|
|
|
11640
12182
|
if (dropped) rt.recentBytesSize -= dropped.byteLength;
|
|
11641
12183
|
}
|
|
11642
12184
|
rt.desc.lastOutputAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12185
|
+
if (rt.conversationTerminal === true) sniffConversationTerminalChunk(rt, chunk);
|
|
11643
12186
|
rt.emitter.emit("data", chunk);
|
|
11644
12187
|
};
|
|
11645
12188
|
const reconcilePtySize = (rt) => {
|
|
@@ -12506,6 +13049,9 @@ ${message}`;
|
|
|
12506
13049
|
});
|
|
12507
13050
|
}
|
|
12508
13051
|
if (overBudget) emitExited(rt);
|
|
13052
|
+
if ((rt.desc.turnsCompleted ?? 0) === 1) {
|
|
13053
|
+
void maybeTitleSession(registry, rt.desc.id);
|
|
13054
|
+
}
|
|
12509
13055
|
} else {
|
|
12510
13056
|
rt.desc.turnsCompleted = (rt.desc.turnsCompleted ?? 0) + 1;
|
|
12511
13057
|
if (rt.desc.lastTurnEmpty !== void 0) delete rt.desc.lastTurnEmpty;
|
|
@@ -13010,6 +13556,19 @@ ${message}`;
|
|
|
13010
13556
|
rows: input.rows
|
|
13011
13557
|
});
|
|
13012
13558
|
const priorCommandSessionId = findPriorCommandSessionId(sessions, input.cwd);
|
|
13559
|
+
const conversationSlug = conversationTerminalSlugFor({ argv: input.argv });
|
|
13560
|
+
const conversationStore = conversationSlug ? CONVERSATION_STORES[conversationSlug] : void 0;
|
|
13561
|
+
const resumeArgvId = (() => {
|
|
13562
|
+
if (!conversationStore) return void 0;
|
|
13563
|
+
for (let i = 0; i < input.argv.length - 1; i++) {
|
|
13564
|
+
if (input.argv[i] !== "--resume" && input.argv[i] !== "-r" && !(conversationSlug === "opencode" && (input.argv[i] === "--session" || input.argv[i] === "-s"))) continue;
|
|
13565
|
+
const raw = input.argv[i + 1];
|
|
13566
|
+
const base = raw.slice(raw.lastIndexOf("/") + 1);
|
|
13567
|
+
return base.endsWith(".jsonl") ? base.slice(0, -".jsonl".length) : raw;
|
|
13568
|
+
}
|
|
13569
|
+
return void 0;
|
|
13570
|
+
})();
|
|
13571
|
+
const conversationConfigDir = conversationStore?.configDirEnvVar && input.env ? input.env[conversationStore.configDirEnvVar] : void 0;
|
|
13013
13572
|
const desc = {
|
|
13014
13573
|
id,
|
|
13015
13574
|
kind: "terminal",
|
|
@@ -13024,6 +13583,16 @@ ${message}`;
|
|
|
13024
13583
|
...worktreeFields(input.cwd),
|
|
13025
13584
|
...input.name ? { name: input.name } : {},
|
|
13026
13585
|
...input.label ? { label: input.label } : {},
|
|
13586
|
+
// Same rule as spawnAgent: a NEW spawn's label is a slug, not a
|
|
13587
|
+
// human rename — let a later derived title outrank it.
|
|
13588
|
+
...input.label ? { renamedByUser: false } : {},
|
|
13589
|
+
...conversationSlug ? { adapterSlug: conversationSlug } : {},
|
|
13590
|
+
...conversationSlug && RESUME_STRATEGIES[conversationSlug]?.spawnArgs ? { nativeTerminalResume: true } : {},
|
|
13591
|
+
...conversationConfigDir ? { adapterConfigDir: conversationConfigDir } : {},
|
|
13592
|
+
...resumeArgvId && conversationStore ? {
|
|
13593
|
+
adapterSessionId: resumeArgvId,
|
|
13594
|
+
resumeMetadata: { [conversationStore.storeAs]: resumeArgvId }
|
|
13595
|
+
} : {},
|
|
13027
13596
|
...priorCommandSessionId ? { priorCommandSessionId } : {},
|
|
13028
13597
|
// Parent attribution + depth (orchestrator WP4) — same recording
|
|
13029
13598
|
// rule as spawnAgent above: depth always set so subtree/depth
|
|
@@ -13050,7 +13619,8 @@ ${message}`;
|
|
|
13050
13619
|
emitter: new EventEmitter(),
|
|
13051
13620
|
busy: false,
|
|
13052
13621
|
textBuf: "",
|
|
13053
|
-
thoughtBuf: ""
|
|
13622
|
+
thoughtBuf: "",
|
|
13623
|
+
...isConversationTerminal(desc) ? { conversationTerminal: true } : {}
|
|
13054
13624
|
};
|
|
13055
13625
|
rt.emitter.setMaxListeners(50);
|
|
13056
13626
|
sessions.set(id, rt);
|
|
@@ -13062,6 +13632,10 @@ ${message}`;
|
|
|
13062
13632
|
depth: desc.depth ?? 0,
|
|
13063
13633
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
13064
13634
|
});
|
|
13635
|
+
if (rt.conversationTerminal) {
|
|
13636
|
+
if (desc.adapterSessionId) recordConversationLink(rt);
|
|
13637
|
+
else startConversationTerminalLinkProbe(rt);
|
|
13638
|
+
}
|
|
13065
13639
|
pty.onData((chunk) => {
|
|
13066
13640
|
const buf = Buffer.from(chunk, "utf8");
|
|
13067
13641
|
rt.bracketedPaste = scanBracketedPaste(
|
|
@@ -13272,14 +13846,14 @@ ${message}`;
|
|
|
13272
13846
|
};
|
|
13273
13847
|
rtPre.desc.promptQueue = opts2.force ? [item, ...rtPre.desc.promptQueue ?? []] : [...rtPre.desc.promptQueue ?? [], item];
|
|
13274
13848
|
schedulePersist();
|
|
13275
|
-
return;
|
|
13849
|
+
return { queued: true };
|
|
13276
13850
|
}
|
|
13277
13851
|
await maybeResumeAgent(rtPre);
|
|
13278
13852
|
const rt = validateAgentTurn(id, "enqueuePrompt");
|
|
13279
13853
|
const structuredAnswer = matchStructuredQuestionAnswer(rt, message);
|
|
13280
13854
|
if (structuredAnswer) {
|
|
13281
13855
|
await answerStructuredQuestion(rt, structuredAnswer);
|
|
13282
|
-
return;
|
|
13856
|
+
return { queued: false };
|
|
13283
13857
|
}
|
|
13284
13858
|
void runAgentTurn(rt, message, opts2?.source ? { promptSource: opts2.source } : void 0).catch((err) => {
|
|
13285
13859
|
appendLine(
|
|
@@ -13288,6 +13862,7 @@ ${message}`;
|
|
|
13288
13862
|
"stderr"
|
|
13289
13863
|
);
|
|
13290
13864
|
});
|
|
13865
|
+
return { queued: false };
|
|
13291
13866
|
},
|
|
13292
13867
|
removeQueuedPrompt(id, queueId) {
|
|
13293
13868
|
const rt = sessions.get(id);
|
|
@@ -13575,7 +14150,7 @@ ${message}`;
|
|
|
13575
14150
|
};
|
|
13576
14151
|
const all = Array.from(sessions.values()).filter((rt) => includeArchived || !rt.desc.archived).filter((rt) => {
|
|
13577
14152
|
if (!lane) return true;
|
|
13578
|
-
if (rt.desc
|
|
14153
|
+
if (isShellOnlyRow(rt.desc)) return false;
|
|
13579
14154
|
const machine = hasMachineLineage(rt);
|
|
13580
14155
|
return lane === "auto" ? machine : !machine;
|
|
13581
14156
|
}).sort((a, b) => b.desc.startedAt.localeCompare(a.desc.startedAt));
|
|
@@ -13588,7 +14163,11 @@ ${message}`;
|
|
|
13588
14163
|
stampWatchers(desc);
|
|
13589
14164
|
desc.childrenBusy = childrenBusy.get(desc.id) ?? 0;
|
|
13590
14165
|
desc.queuedPrompts = desc.promptQueue?.length ?? 0;
|
|
13591
|
-
|
|
14166
|
+
const summary = toSessionSummary(desc);
|
|
14167
|
+
if (!isShellOnlyRow(desc)) {
|
|
14168
|
+
summary.lane = hasMachineLineage(rt) ? "auto" : "agents";
|
|
14169
|
+
}
|
|
14170
|
+
return summary;
|
|
13592
14171
|
});
|
|
13593
14172
|
return { summaries, total: all.length };
|
|
13594
14173
|
},
|
|
@@ -16432,6 +17011,10 @@ var mcpBool2 = z.preprocess(
|
|
|
16432
17011
|
(v) => v === "true" ? true : v === "false" ? false : v,
|
|
16433
17012
|
z.boolean()
|
|
16434
17013
|
);
|
|
17014
|
+
var publicSessionDescriptor = (session) => {
|
|
17015
|
+
const { ptyResumeEnv: _privateResumeEnv, ...publicDescriptor } = session;
|
|
17016
|
+
return publicDescriptor;
|
|
17017
|
+
};
|
|
16435
17018
|
var compactSessionItem = (s) => ({
|
|
16436
17019
|
id: s.id,
|
|
16437
17020
|
kind: s.kind,
|
|
@@ -16568,7 +17151,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
16568
17151
|
(s) => s.status === "running" || s.status === "starting"
|
|
16569
17152
|
);
|
|
16570
17153
|
}
|
|
16571
|
-
return rows;
|
|
17154
|
+
return rows.map(publicSessionDescriptor);
|
|
16572
17155
|
},
|
|
16573
17156
|
transformers: [
|
|
16574
17157
|
paginated({
|
|
@@ -17025,7 +17608,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17025
17608
|
(s) => s.status === "running" || s.status === "starting"
|
|
17026
17609
|
);
|
|
17027
17610
|
}
|
|
17028
|
-
return rows;
|
|
17611
|
+
return rows.map(publicSessionDescriptor);
|
|
17029
17612
|
},
|
|
17030
17613
|
project: compactSessionItemWithProvenance,
|
|
17031
17614
|
keyOf: (s) => s.id,
|
|
@@ -17060,7 +17643,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17060
17643
|
(s) => s.status === "running" || s.status === "starting"
|
|
17061
17644
|
);
|
|
17062
17645
|
}
|
|
17063
|
-
return rows;
|
|
17646
|
+
return rows.map(publicSessionDescriptor);
|
|
17064
17647
|
},
|
|
17065
17648
|
project: compactSessionItemWithProvenance,
|
|
17066
17649
|
keyOf: (s) => s.id,
|
|
@@ -17899,9 +18482,27 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17899
18482
|
try {
|
|
17900
18483
|
if (strategy.kind === "pty-native" || strategy.kind === "pty-plain") {
|
|
17901
18484
|
if (!ptyEnabled) return ptyNotConfigured("session_restart");
|
|
17902
|
-
|
|
18485
|
+
let argv = strategy.kind === "pty-native" ? strategy.argv : Array.isArray(prev.argv) && prev.argv.length > 0 ? [...prev.argv] : tokenizeCommand(prev.command);
|
|
18486
|
+
if (strategy.kind === "pty-native" && prev.adapterSlug) {
|
|
18487
|
+
const probe = await probeNativeTranscript(augmented);
|
|
18488
|
+
if (probe?.exists) {
|
|
18489
|
+
const upgraded = RESUME_STRATEGIES[prev.adapterSlug]?.spawnArgs?.(probe.path);
|
|
18490
|
+
if (upgraded) {
|
|
18491
|
+
argv = upgraded;
|
|
18492
|
+
console.log(
|
|
18493
|
+
`[session_restart] ${prev.id} pty-native resume upgraded to absolute transcript path ${probe.path}`
|
|
18494
|
+
);
|
|
18495
|
+
}
|
|
18496
|
+
}
|
|
18497
|
+
}
|
|
17903
18498
|
const envVarName = strategy.kind === "pty-native" && prev.adapterSlug ? RESUME_STRATEGIES[prev.adapterSlug]?.configDirEnvVar : void 0;
|
|
17904
|
-
const ptyEnv = strategy.kind === "pty-native" ? envVarName && augmented.adapterConfigDir ? { [envVarName]: augmented.adapterConfigDir } :
|
|
18499
|
+
const ptyEnv = strategy.kind === "pty-native" ? envVarName && augmented.adapterConfigDir ? { [envVarName]: augmented.adapterConfigDir } : (
|
|
18500
|
+
// A pty-native restart of a row that is ITSELF a restarted
|
|
18501
|
+
// PTY (conversation terminal): no adapterConfigDir, but the
|
|
18502
|
+
// env the earlier hop recorded still names the isolated
|
|
18503
|
+
// store — replay it rather than dropping to the global one.
|
|
18504
|
+
prev.ptyResumeEnv
|
|
18505
|
+
) : prev.ptyResumeEnv;
|
|
17905
18506
|
if (strategy.kind === "pty-native") {
|
|
17906
18507
|
console.log(
|
|
17907
18508
|
`[session_restart] ${prev.id} pty-native env: ` + (ptyEnv ? `${Object.keys(ptyEnv).join(",")} threaded from adapterConfigDir` : envVarName ? "no adapterConfigDir on descriptor \u2014 resume may miss the isolated store" : `adapter "${prev.adapterSlug}" declares no configDirEnvVar \u2014 resume uses the global store`)
|
|
@@ -17956,7 +18557,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17956
18557
|
content: [
|
|
17957
18558
|
{
|
|
17958
18559
|
type: "text",
|
|
17959
|
-
text: JSON.stringify(desc)
|
|
18560
|
+
text: JSON.stringify(publicSessionDescriptor(desc))
|
|
17960
18561
|
}
|
|
17961
18562
|
]
|
|
17962
18563
|
};
|
|
@@ -17983,6 +18584,15 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17983
18584
|
isError: true
|
|
17984
18585
|
};
|
|
17985
18586
|
}
|
|
18587
|
+
let nativeResumeDecline;
|
|
18588
|
+
if (input.preferNativeTerminal === true && prev.adapterSlug && RESUME_STRATEGIES[prev.adapterSlug]?.spawnArgs) {
|
|
18589
|
+
if (prev.nativeTerminalResume !== true) {
|
|
18590
|
+
nativeResumeDecline = { reason: "capability-missing" };
|
|
18591
|
+
} else {
|
|
18592
|
+
const probe = await probeNativeTranscript(augmented);
|
|
18593
|
+
nativeResumeDecline = probe ? { reason: "transcript-not-found", probedDir: probe.dir } : { reason: "no-resume-id" };
|
|
18594
|
+
}
|
|
18595
|
+
}
|
|
17986
18596
|
const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev, {
|
|
17987
18597
|
...opts.resolveSandboxProvider ? { resolveSandboxProvider: opts.resolveSandboxProvider } : {}
|
|
17988
18598
|
});
|
|
@@ -17992,10 +18602,11 @@ function registerSessionTools(rawServer, opts) {
|
|
|
17992
18602
|
type: "text",
|
|
17993
18603
|
text: JSON.stringify(
|
|
17994
18604
|
{
|
|
17995
|
-
...restarted.desc,
|
|
18605
|
+
...publicSessionDescriptor(restarted.desc),
|
|
17996
18606
|
resumedFrom: restarted.resumedFrom,
|
|
17997
18607
|
resumeVia: restarted.resumeVia,
|
|
17998
|
-
...restarted.resumeFallback ? { resumeFallback: true } : {}
|
|
18608
|
+
...restarted.resumeFallback ? { resumeFallback: true } : {},
|
|
18609
|
+
...nativeResumeDecline ? { nativeResumeDecline } : {}
|
|
17999
18610
|
},
|
|
18000
18611
|
null,
|
|
18001
18612
|
2
|
|
@@ -18062,7 +18673,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
18062
18673
|
try {
|
|
18063
18674
|
const desc = registry.archiveSession(prev.id);
|
|
18064
18675
|
return {
|
|
18065
|
-
content: [{ type: "text", text: JSON.stringify(desc) }]
|
|
18676
|
+
content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
|
|
18066
18677
|
};
|
|
18067
18678
|
} catch (err) {
|
|
18068
18679
|
return {
|
|
@@ -18144,7 +18755,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
18144
18755
|
try {
|
|
18145
18756
|
const desc = registry.unarchiveSession(prev.id);
|
|
18146
18757
|
return {
|
|
18147
|
-
content: [{ type: "text", text: JSON.stringify(desc) }]
|
|
18758
|
+
content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
|
|
18148
18759
|
};
|
|
18149
18760
|
} catch (err) {
|
|
18150
18761
|
return {
|
|
@@ -18229,7 +18840,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
18229
18840
|
reason: input.reason
|
|
18230
18841
|
});
|
|
18231
18842
|
return {
|
|
18232
|
-
content: [{ type: "text", text: JSON.stringify(desc) }]
|
|
18843
|
+
content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
|
|
18233
18844
|
};
|
|
18234
18845
|
} catch (err) {
|
|
18235
18846
|
return {
|
|
@@ -18313,7 +18924,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
18313
18924
|
...input.label !== void 0 ? { label: input.label } : {}
|
|
18314
18925
|
});
|
|
18315
18926
|
return {
|
|
18316
|
-
content: [{ type: "text", text: JSON.stringify(desc) }]
|
|
18927
|
+
content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
|
|
18317
18928
|
};
|
|
18318
18929
|
} catch (err) {
|
|
18319
18930
|
return {
|
|
@@ -18375,7 +18986,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
18375
18986
|
try {
|
|
18376
18987
|
const desc = registry.setKeepAlive(prev.id, input.keepAlive);
|
|
18377
18988
|
return {
|
|
18378
|
-
content: [{ type: "text", text: JSON.stringify(desc) }]
|
|
18989
|
+
content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
|
|
18379
18990
|
};
|
|
18380
18991
|
} catch (err) {
|
|
18381
18992
|
return {
|
|
@@ -18437,7 +19048,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
18437
19048
|
try {
|
|
18438
19049
|
const desc = registry.setPinned(prev.id, input.pinned);
|
|
18439
19050
|
return {
|
|
18440
|
-
content: [{ type: "text", text: JSON.stringify(desc) }]
|
|
19051
|
+
content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
|
|
18441
19052
|
};
|
|
18442
19053
|
} catch (err) {
|
|
18443
19054
|
return {
|
|
@@ -18553,7 +19164,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
18553
19164
|
} : {}
|
|
18554
19165
|
});
|
|
18555
19166
|
return {
|
|
18556
|
-
content: [{ type: "text", text: JSON.stringify(desc) }]
|
|
19167
|
+
content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
|
|
18557
19168
|
};
|
|
18558
19169
|
} catch (err) {
|
|
18559
19170
|
return {
|
|
@@ -20289,13 +20900,34 @@ function registerWebSearchTools(server, opts = {}) {
|
|
|
20289
20900
|
transformers: [catchErrors(), enrichResolverRejections()]
|
|
20290
20901
|
});
|
|
20291
20902
|
}
|
|
20903
|
+
var embedTokens = /* @__PURE__ */ new Map();
|
|
20904
|
+
function mintAppEmbedToken(appId) {
|
|
20905
|
+
const token = randomBytes(24).toString("base64url");
|
|
20906
|
+
embedTokens.set(token, appId);
|
|
20907
|
+
return token;
|
|
20908
|
+
}
|
|
20909
|
+
var stableTokens = /* @__PURE__ */ new Map();
|
|
20910
|
+
function stableAppEmbedToken(appId) {
|
|
20911
|
+
const existing = stableTokens.get(appId);
|
|
20912
|
+
if (existing) return existing;
|
|
20913
|
+
const token = mintAppEmbedToken(appId);
|
|
20914
|
+
stableTokens.set(appId, token);
|
|
20915
|
+
return token;
|
|
20916
|
+
}
|
|
20917
|
+
function isValidAppEmbedToken(token) {
|
|
20918
|
+
return typeof token === "string" && token.length > 0 && embedTokens.has(token);
|
|
20919
|
+
}
|
|
20292
20920
|
|
|
20293
20921
|
// src/mcp-apps-adapter.ts
|
|
20294
20922
|
var MIME_TYPE = "text/html;profile=mcp-app";
|
|
20923
|
+
var EMBED_TOKEN_PLACEHOLDER = '"__AGENPROTO_EMBED_TOKEN__"';
|
|
20295
20924
|
function registerMcpApps(server, apps) {
|
|
20296
20925
|
for (const app of apps) {
|
|
20297
20926
|
const resourceUri = `ui://${app.id}/view`;
|
|
20298
|
-
|
|
20927
|
+
let html = typeof app.html === "string" ? app.html : app.html({});
|
|
20928
|
+
if (html.includes(EMBED_TOKEN_PLACEHOLDER)) {
|
|
20929
|
+
html = html.split(EMBED_TOKEN_PLACEHOLDER).join(JSON.stringify(mintAppEmbedToken(app.id)));
|
|
20930
|
+
}
|
|
20299
20931
|
const resourceMeta = {
|
|
20300
20932
|
ui: {
|
|
20301
20933
|
prefersBorder: true,
|
|
@@ -20352,6 +20984,7 @@ function registerMcpApps(server, apps) {
|
|
|
20352
20984
|
}
|
|
20353
20985
|
}
|
|
20354
20986
|
function makeBuiltinPanelApps(ops) {
|
|
20987
|
+
const sessionChatNativeInstalled = ops.isSessionChatInstalled();
|
|
20355
20988
|
return [
|
|
20356
20989
|
makeSessionsPanelApp({ listSessions: ops.listSessions }),
|
|
20357
20990
|
makeAgentsOverviewApp({ listSessions: ops.listSessions }),
|
|
@@ -20361,13 +20994,26 @@ function makeBuiltinPanelApps(ops) {
|
|
|
20361
20994
|
// `agent_start` via _meta.ui.resourceUri (agent-tools.ts) so a launch
|
|
20362
20995
|
// auto-renders it.
|
|
20363
20996
|
makeLiveSessionApp({ httpBaseUrl: ops.httpBaseUrl }),
|
|
20364
|
-
// Session-chat widget — thin launcher for the installed
|
|
20997
|
+
// Session-chat widget — thin loopback-HTTP launcher for the installed
|
|
20365
20998
|
// `@agentik/session-chat` app's standalone UI (deep-linked iframe when
|
|
20366
20999
|
// installed, install notice otherwise; see apps/src/session-chat).
|
|
20367
|
-
|
|
20368
|
-
|
|
20369
|
-
|
|
20370
|
-
|
|
21000
|
+
// Mounted ONLY when the native app isn't installed: once it is, its own
|
|
21001
|
+
// MCP Apps tool (`app_ui_session_chat`, app-ui-apps.ts) is the one and
|
|
21002
|
+
// only launch path — a strict-CSP host (Codex) can't fetch this
|
|
21003
|
+
// widget's loopback HTTP calls, so offering both would just add a
|
|
21004
|
+
// broken option next to the working one. `agent_start`'s launch-card
|
|
21005
|
+
// binding (agent-tools.ts) makes the matching choice.
|
|
21006
|
+
...sessionChatNativeInstalled ? [] : [
|
|
21007
|
+
makeSessionChatApp({
|
|
21008
|
+
httpBaseUrl: ops.httpBaseUrl,
|
|
21009
|
+
isSessionChatInstalled: ops.isSessionChatInstalled,
|
|
21010
|
+
// Hand every result a live embed token so a host-cached widget
|
|
21011
|
+
// (Claude Desktop's per-conversation srcdoc) re-arms itself
|
|
21012
|
+
// after a daemon restart instead of 403ing on its dead baked
|
|
21013
|
+
// token.
|
|
21014
|
+
mintEmbedToken: () => stableAppEmbedToken("agentproto_session_chat")
|
|
21015
|
+
})
|
|
21016
|
+
],
|
|
20371
21017
|
// Work-board widget — kanban over the Task ledger (see apps/src/
|
|
20372
21018
|
// work-board). Read path only; writes go through task_claim/
|
|
20373
21019
|
// task_update/task_create over the bridge, same as every other caller.
|
|
@@ -20420,6 +21066,39 @@ function builtinPanelCatalogEntries() {
|
|
|
20420
21066
|
};
|
|
20421
21067
|
});
|
|
20422
21068
|
}
|
|
21069
|
+
|
|
21070
|
+
// src/public-origins.ts
|
|
21071
|
+
var HTTP_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
21072
|
+
var WS_PROTOCOLS = /* @__PURE__ */ new Set(["ws:", "wss:"]);
|
|
21073
|
+
function normalizeOrigin(value, protocols) {
|
|
21074
|
+
const candidate = value?.trim().replace(/\/+$/, "");
|
|
21075
|
+
if (!candidate) return void 0;
|
|
21076
|
+
try {
|
|
21077
|
+
const url = new URL(candidate);
|
|
21078
|
+
if (!protocols.has(url.protocol)) return void 0;
|
|
21079
|
+
if (url.username || url.password || url.search || url.hash) return void 0;
|
|
21080
|
+
if (url.pathname !== "/") return void 0;
|
|
21081
|
+
return url.origin;
|
|
21082
|
+
} catch {
|
|
21083
|
+
return void 0;
|
|
21084
|
+
}
|
|
21085
|
+
}
|
|
21086
|
+
function resolveRequestHttpBaseUrl(headers, configuredOrigin = process.env.AGENTPROTO_PUBLIC_HTTP_ORIGIN) {
|
|
21087
|
+
const configured = normalizeOrigin(configuredOrigin, HTTP_PROTOCOLS);
|
|
21088
|
+
if (configured) return configured;
|
|
21089
|
+
const forwarded = headers["x-forwarded-proto"];
|
|
21090
|
+
const firstForwarded = (Array.isArray(forwarded) ? forwarded[0] : forwarded)?.split(",")[0]?.trim().toLowerCase();
|
|
21091
|
+
const protocol = firstForwarded === "https" ? "https" : "http";
|
|
21092
|
+
return normalizeOrigin(`${protocol}://${headers.host ?? "127.0.0.1"}`, HTTP_PROTOCOLS) ?? `${protocol}://127.0.0.1`;
|
|
21093
|
+
}
|
|
21094
|
+
function resolvePublicAppOrigins(port, configuredHttpOrigin = process.env.AGENTPROTO_PUBLIC_HTTP_ORIGIN, configuredWsOrigin = process.env.AGENTPROTO_PUBLIC_WS_ORIGIN) {
|
|
21095
|
+
const httpOrigin = normalizeOrigin(configuredHttpOrigin, HTTP_PROTOCOLS) ?? `http://127.0.0.1:${port}`;
|
|
21096
|
+
const configuredWs = normalizeOrigin(configuredWsOrigin, WS_PROTOCOLS);
|
|
21097
|
+
if (configuredWs) return { httpOrigin, wsOrigin: configuredWs };
|
|
21098
|
+
const http = new URL(httpOrigin);
|
|
21099
|
+
http.protocol = http.protocol === "https:" ? "wss:" : "ws:";
|
|
21100
|
+
return { httpOrigin, wsOrigin: http.origin };
|
|
21101
|
+
}
|
|
20423
21102
|
function summarizeSession(desc, lines, nowMs) {
|
|
20424
21103
|
return {
|
|
20425
21104
|
sessionId: desc.id,
|
|
@@ -22846,172 +23525,6 @@ function registerAppTools(server, opts) {
|
|
|
22846
23525
|
}
|
|
22847
23526
|
);
|
|
22848
23527
|
}
|
|
22849
|
-
function appUiToolId(appId) {
|
|
22850
|
-
const slug = appId.replace(/^@[^/]+\//, "").replace(/[^a-z0-9]/g, "_");
|
|
22851
|
-
return `app_ui_${slug}`;
|
|
22852
|
-
}
|
|
22853
|
-
var MCP_APP_BRIDGE_SCRIPT = `<script>
|
|
22854
|
-
(function () {
|
|
22855
|
-
if (window.McpApp) return;
|
|
22856
|
-
var nextId = 1, pending = {}, teardownCbs = [];
|
|
22857
|
-
function post(m) { window.parent.postMessage(m, "*"); }
|
|
22858
|
-
window.addEventListener("message", function (e) {
|
|
22859
|
-
var m = e.data;
|
|
22860
|
-
if (typeof m === "string") { try { m = JSON.parse(m); } catch (_) { return; } }
|
|
22861
|
-
if (!m || m.jsonrpc !== "2.0") return;
|
|
22862
|
-
if (m.method === "ui/resource-teardown") {
|
|
22863
|
-
for (var i = 0; i < teardownCbs.length; i++) teardownCbs[i]();
|
|
22864
|
-
post({ jsonrpc: "2.0", id: m.id, result: {} });
|
|
22865
|
-
return;
|
|
22866
|
-
}
|
|
22867
|
-
if (m.id != null && pending[m.id]) { var cb = pending[m.id]; delete pending[m.id]; cb(m.result, m.error); }
|
|
22868
|
-
});
|
|
22869
|
-
function request(method, params) {
|
|
22870
|
-
return new Promise(function (resolve, reject) {
|
|
22871
|
-
var id = nextId++;
|
|
22872
|
-
pending[id] = function (r, err) { if (err) reject(new Error((err && err.message) || String(err))); else resolve(r); };
|
|
22873
|
-
post({ jsonrpc: "2.0", id: id, method: method, params: params });
|
|
22874
|
-
});
|
|
22875
|
-
}
|
|
22876
|
-
window.McpApp = {
|
|
22877
|
-
connect: function () {
|
|
22878
|
-
if (window.parent === window) return Promise.reject(new Error("no host (standalone)"));
|
|
22879
|
-
return request("ui/initialize", {
|
|
22880
|
-
appInfo: { name: "agentproto-app", version: "1.0" },
|
|
22881
|
-
appCapabilities: { availableDisplayModes: ["inline", "fullscreen"] },
|
|
22882
|
-
protocolVersion: "2026-01-26"
|
|
22883
|
-
}).then(function () {
|
|
22884
|
-
post({ jsonrpc: "2.0", method: "ui/notifications/initialized", params: {} });
|
|
22885
|
-
return {
|
|
22886
|
-
callTool: function (name, args) { return request("tools/call", { name: name, arguments: args || {} }); },
|
|
22887
|
-
sendMessage: function (p) {
|
|
22888
|
-
var params = typeof p === "string" ? { content: p } : (p || {});
|
|
22889
|
-
var content = typeof params.content === "string" ? [{ type: "text", text: params.content }] : (params.content || []);
|
|
22890
|
-
return request("ui/message", { role: "user", content: content });
|
|
22891
|
-
},
|
|
22892
|
-
updateModelContext: function (p) { return request("ui/update-model-context", p || {}); },
|
|
22893
|
-
openLink: function (url) { return request("ui/open-link", { url: url }); },
|
|
22894
|
-
onTeardown: function (cb) { teardownCbs.push(cb); }
|
|
22895
|
-
};
|
|
22896
|
-
});
|
|
22897
|
-
}
|
|
22898
|
-
};
|
|
22899
|
-
})();
|
|
22900
|
-
</script>
|
|
22901
|
-
`;
|
|
22902
|
-
function injectAfterStructuralTag(html, script) {
|
|
22903
|
-
for (const tag of ["head", "body", "html"]) {
|
|
22904
|
-
const match = html.match(new RegExp(`<${tag}[^>]*>`, "i"));
|
|
22905
|
-
if (match?.index !== void 0) {
|
|
22906
|
-
const insertAt = match.index + match[0].length;
|
|
22907
|
-
return html.slice(0, insertAt) + script + html.slice(insertAt);
|
|
22908
|
-
}
|
|
22909
|
-
}
|
|
22910
|
-
return script + html;
|
|
22911
|
-
}
|
|
22912
|
-
function injectMcpAppBridge(html) {
|
|
22913
|
-
const hasBridge = /window\.McpApp\s*=/.test(html);
|
|
22914
|
-
const hasRunnerSelect = /window\.AgentprotoUI\s*=/.test(html);
|
|
22915
|
-
if (hasBridge && hasRunnerSelect) return html;
|
|
22916
|
-
const script = (hasBridge ? "" : MCP_APP_BRIDGE_SCRIPT) + (hasRunnerSelect ? "" : RUNNER_SELECT_SCRIPT);
|
|
22917
|
-
return injectAfterStructuralTag(html, script);
|
|
22918
|
-
}
|
|
22919
|
-
var STANDALONE_REST_BRIDGE_SCRIPT = `<script>
|
|
22920
|
-
(function () {
|
|
22921
|
-
if (window.McpApp) return;
|
|
22922
|
-
window.McpApp = {
|
|
22923
|
-
connect: function () {
|
|
22924
|
-
return Promise.resolve({
|
|
22925
|
-
callTool: function (name, args) {
|
|
22926
|
-
return fetch("./tool-call", {
|
|
22927
|
-
method: "POST",
|
|
22928
|
-
headers: { "content-type": "application/json" },
|
|
22929
|
-
body: JSON.stringify({ tool: name, args: args || {} })
|
|
22930
|
-
}).then(function (res) {
|
|
22931
|
-
return res.json().catch(function () {
|
|
22932
|
-
throw new Error("tool-call failed: HTTP " + res.status);
|
|
22933
|
-
}).then(function (body) {
|
|
22934
|
-
if (!res.ok) throw new Error((body && (body.message || body.error)) || ("tool-call failed: HTTP " + res.status));
|
|
22935
|
-
return body;
|
|
22936
|
-
});
|
|
22937
|
-
});
|
|
22938
|
-
},
|
|
22939
|
-
sendMessage: function () {
|
|
22940
|
-
return Promise.reject(new Error("sendMessage: no chat host (standalone mode)"));
|
|
22941
|
-
},
|
|
22942
|
-
updateModelContext: function () {
|
|
22943
|
-
return Promise.reject(new Error("updateModelContext: no host (standalone mode)"));
|
|
22944
|
-
},
|
|
22945
|
-
openLink: function (url) {
|
|
22946
|
-
window.open(url, "_blank");
|
|
22947
|
-
return Promise.resolve({});
|
|
22948
|
-
},
|
|
22949
|
-
onTeardown: function () {}
|
|
22950
|
-
});
|
|
22951
|
-
}
|
|
22952
|
-
};
|
|
22953
|
-
})();
|
|
22954
|
-
</script>
|
|
22955
|
-
`;
|
|
22956
|
-
function injectStandaloneAppBridge(html) {
|
|
22957
|
-
const hasRunnerSelect = /window\.AgentprotoUI\s*=/.test(html);
|
|
22958
|
-
const script = STANDALONE_REST_BRIDGE_SCRIPT + (hasRunnerSelect ? "" : RUNNER_SELECT_SCRIPT);
|
|
22959
|
-
return injectAfterStructuralTag(html, script);
|
|
22960
|
-
}
|
|
22961
|
-
function createUiHtmlCache() {
|
|
22962
|
-
const cache = /* @__PURE__ */ new Map();
|
|
22963
|
-
return {
|
|
22964
|
-
async get(path, version) {
|
|
22965
|
-
const cached = cache.get(path);
|
|
22966
|
-
if (cached && cached.version === version) return cached.html;
|
|
22967
|
-
const raw = await readFile(path, "utf8");
|
|
22968
|
-
const html = injectMcpAppBridge(raw);
|
|
22969
|
-
cache.set(path, { version, html });
|
|
22970
|
-
return html;
|
|
22971
|
-
}
|
|
22972
|
-
};
|
|
22973
|
-
}
|
|
22974
|
-
async function makeInstalledAppUiApps(appRegistry, cache, existingToolNames) {
|
|
22975
|
-
const apps = [];
|
|
22976
|
-
const seen = new Set(existingToolNames);
|
|
22977
|
-
for (const app of appRegistry.listApps()) {
|
|
22978
|
-
const ui = app.ui;
|
|
22979
|
-
if (!ui) continue;
|
|
22980
|
-
const toolId = appUiToolId(app.appId);
|
|
22981
|
-
if (seen.has(toolId)) {
|
|
22982
|
-
console.warn(
|
|
22983
|
-
`[app-ui-apps] skipping UI panel for app "${app.appId}": tool id "${toolId}" collides with an existing tool or another installed app's panel.`
|
|
22984
|
-
);
|
|
22985
|
-
continue;
|
|
22986
|
-
}
|
|
22987
|
-
let html;
|
|
22988
|
-
try {
|
|
22989
|
-
html = await cache.get(ui.path, app.updatedAt);
|
|
22990
|
-
} catch (err) {
|
|
22991
|
-
console.warn(
|
|
22992
|
-
`[app-ui-apps] skipping UI panel for app "${app.appId}": could not read "${ui.path}": ${err instanceof Error ? err.message : String(err)}`
|
|
22993
|
-
);
|
|
22994
|
-
continue;
|
|
22995
|
-
}
|
|
22996
|
-
seen.add(toolId);
|
|
22997
|
-
apps.push({
|
|
22998
|
-
id: toolId,
|
|
22999
|
-
title: ui.title ?? app.name ?? app.appId,
|
|
23000
|
-
...ui.description ? { description: ui.description } : {},
|
|
23001
|
-
inputSchema: z.object({}),
|
|
23002
|
-
execute: async () => ({ appId: app.appId, tools: ui.tools ?? [] }),
|
|
23003
|
-
html,
|
|
23004
|
-
...ui.csp ? {
|
|
23005
|
-
csp: {
|
|
23006
|
-
...ui.csp.connectDomains ? { connectDomains: [...ui.csp.connectDomains] } : {},
|
|
23007
|
-
...ui.csp.resourceDomains ? { resourceDomains: [...ui.csp.resourceDomains] } : {},
|
|
23008
|
-
...ui.csp.frameDomains ? { frameDomains: [...ui.csp.frameDomains] } : {}
|
|
23009
|
-
}
|
|
23010
|
-
} : {}
|
|
23011
|
-
});
|
|
23012
|
-
}
|
|
23013
|
-
return apps;
|
|
23014
|
-
}
|
|
23015
23528
|
function paginatedLegacyList3(opts) {
|
|
23016
23529
|
return {
|
|
23017
23530
|
name: "paginatedLegacyList",
|
|
@@ -23241,6 +23754,22 @@ async function sendOutbound(provider, input, deps2) {
|
|
|
23241
23754
|
return { ok: false, error: "unsupported_provider" };
|
|
23242
23755
|
}
|
|
23243
23756
|
}
|
|
23757
|
+
function unwrapMcpToolResult(result) {
|
|
23758
|
+
if (!result || typeof result !== "object") return {};
|
|
23759
|
+
const envelope = result;
|
|
23760
|
+
if (envelope.structuredContent && typeof envelope.structuredContent === "object") {
|
|
23761
|
+
return envelope.structuredContent;
|
|
23762
|
+
}
|
|
23763
|
+
const text10 = envelope.content?.find((c) => c.type === "text")?.text;
|
|
23764
|
+
if (typeof text10 === "string") {
|
|
23765
|
+
try {
|
|
23766
|
+
const parsed = JSON.parse(text10);
|
|
23767
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
23768
|
+
} catch {
|
|
23769
|
+
}
|
|
23770
|
+
}
|
|
23771
|
+
return result;
|
|
23772
|
+
}
|
|
23244
23773
|
async function sendAgentpush(input, deps2) {
|
|
23245
23774
|
const alias = input.alias;
|
|
23246
23775
|
if (!alias) {
|
|
@@ -23270,7 +23799,7 @@ async function sendAgentpush(input, deps2) {
|
|
|
23270
23799
|
if (!upload.ok) {
|
|
23271
23800
|
return { ok: false, error: `upload_media_failed: ${upload.error}` };
|
|
23272
23801
|
}
|
|
23273
|
-
const result =
|
|
23802
|
+
const result = unwrapMcpToolResult(upload.result);
|
|
23274
23803
|
const providerMediaId = typeof result.media_id === "string" ? result.media_id : typeof result.id === "string" ? result.id : void 0;
|
|
23275
23804
|
if (!providerMediaId) {
|
|
23276
23805
|
return { ok: false, error: "upload_media_missing_media_id" };
|
|
@@ -23295,7 +23824,18 @@ async function sendAgentpush(input, deps2) {
|
|
|
23295
23824
|
if (!out.ok) {
|
|
23296
23825
|
return { ok: false, error: out.error };
|
|
23297
23826
|
}
|
|
23298
|
-
const
|
|
23827
|
+
const payload = unwrapMcpToolResult(out.result);
|
|
23828
|
+
const status = typeof payload.status === "string" ? payload.status : void 0;
|
|
23829
|
+
if (status === "blocked") {
|
|
23830
|
+
const blockedReason = typeof payload.blocked_reason === "string" ? payload.blocked_reason : "blocked";
|
|
23831
|
+
const suggestion = typeof payload.suggestion === "string" ? payload.suggestion : void 0;
|
|
23832
|
+
return { ok: false, error: blockedReason, blockedReason, ...suggestion ? { suggestion } : {} };
|
|
23833
|
+
}
|
|
23834
|
+
if (status === "failed") {
|
|
23835
|
+
const error = typeof payload.error === "string" ? payload.error : "failed";
|
|
23836
|
+
return { ok: false, error };
|
|
23837
|
+
}
|
|
23838
|
+
const providerMessageId = extractProviderMessageId(payload);
|
|
23299
23839
|
return { ok: true, providerMessageId };
|
|
23300
23840
|
}
|
|
23301
23841
|
async function sendTelegram(input, deps2) {
|
|
@@ -23320,12 +23860,7 @@ async function sendTelegram(input, deps2) {
|
|
|
23320
23860
|
parse_mode: "MarkdownV2"
|
|
23321
23861
|
})
|
|
23322
23862
|
});
|
|
23323
|
-
|
|
23324
|
-
const body2 = await res2.text().catch(() => "");
|
|
23325
|
-
return { ok: false, error: `telegram_http_${res2.status}: ${body2}` };
|
|
23326
|
-
}
|
|
23327
|
-
const json2 = await res2.json().catch(() => ({}));
|
|
23328
|
-
return { ok: true, providerMessageId: extractTelegramMessageId(json2) };
|
|
23863
|
+
return parseTelegramSendResponse(res2);
|
|
23329
23864
|
}
|
|
23330
23865
|
const files = [];
|
|
23331
23866
|
for (const att of input.attachments) {
|
|
@@ -23359,12 +23894,7 @@ async function sendTelegram(input, deps2) {
|
|
|
23359
23894
|
headers: { "Content-Type": contentType2 },
|
|
23360
23895
|
body: toFetchBody(body2)
|
|
23361
23896
|
});
|
|
23362
|
-
|
|
23363
|
-
const bodyText = await res2.text().catch(() => "");
|
|
23364
|
-
return { ok: false, error: `telegram_http_${res2.status}: ${bodyText}` };
|
|
23365
|
-
}
|
|
23366
|
-
const json2 = await res2.json().catch(() => ({}));
|
|
23367
|
-
return { ok: true, providerMessageId: extractTelegramMessageId(json2) };
|
|
23897
|
+
return parseTelegramSendResponse(res2);
|
|
23368
23898
|
}
|
|
23369
23899
|
const mediaJson = input.attachments.map((att, index) => {
|
|
23370
23900
|
const item = {
|
|
@@ -23397,11 +23927,18 @@ async function sendTelegram(input, deps2) {
|
|
|
23397
23927
|
headers: { "Content-Type": contentType },
|
|
23398
23928
|
body: toFetchBody(body)
|
|
23399
23929
|
});
|
|
23930
|
+
return parseTelegramSendResponse(res);
|
|
23931
|
+
}
|
|
23932
|
+
async function parseTelegramSendResponse(res) {
|
|
23400
23933
|
if (!res.ok) {
|
|
23401
|
-
const
|
|
23402
|
-
return { ok: false, error: `telegram_http_${res.status}: ${
|
|
23934
|
+
const body = await res.text().catch(() => "");
|
|
23935
|
+
return { ok: false, error: `telegram_http_${res.status}: ${body}` };
|
|
23403
23936
|
}
|
|
23404
23937
|
const json = await res.json().catch(() => ({}));
|
|
23938
|
+
if (json.ok !== true) {
|
|
23939
|
+
const description = typeof json.description === "string" ? json.description : "unknown_error";
|
|
23940
|
+
return { ok: false, error: `telegram_error: ${description}` };
|
|
23941
|
+
}
|
|
23405
23942
|
return { ok: true, providerMessageId: extractTelegramMessageId(json) };
|
|
23406
23943
|
}
|
|
23407
23944
|
function extractProviderMessageId(result) {
|
|
@@ -26348,7 +26885,16 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
26348
26885
|
if (!out.ok) {
|
|
26349
26886
|
return {
|
|
26350
26887
|
content: [
|
|
26351
|
-
{
|
|
26888
|
+
{
|
|
26889
|
+
type: "text",
|
|
26890
|
+
text: JSON.stringify({
|
|
26891
|
+
sent: false,
|
|
26892
|
+
bound: false,
|
|
26893
|
+
error: out.error,
|
|
26894
|
+
...out.blockedReason ? { blocked_reason: out.blockedReason } : {},
|
|
26895
|
+
...out.suggestion ? { suggestion: out.suggestion } : {}
|
|
26896
|
+
})
|
|
26897
|
+
}
|
|
26352
26898
|
],
|
|
26353
26899
|
isError: true
|
|
26354
26900
|
};
|
|
@@ -26364,7 +26910,18 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
26364
26910
|
provider
|
|
26365
26911
|
});
|
|
26366
26912
|
}
|
|
26367
|
-
return {
|
|
26913
|
+
return {
|
|
26914
|
+
content: [
|
|
26915
|
+
{
|
|
26916
|
+
type: "text",
|
|
26917
|
+
text: JSON.stringify({
|
|
26918
|
+
sent: true,
|
|
26919
|
+
bound,
|
|
26920
|
+
...out.providerMessageId ? { message_id: out.providerMessageId } : {}
|
|
26921
|
+
})
|
|
26922
|
+
}
|
|
26923
|
+
]
|
|
26924
|
+
};
|
|
26368
26925
|
}
|
|
26369
26926
|
);
|
|
26370
26927
|
}
|
|
@@ -26734,6 +27291,10 @@ function registerPresetTools(server, opts = {}) {
|
|
|
26734
27291
|
lister: async () => listPresets(process.env, adapterPresets)
|
|
26735
27292
|
});
|
|
26736
27293
|
}
|
|
27294
|
+
function sessionDescriptorForHttp(session) {
|
|
27295
|
+
const { ptyResumeEnv: _privateResumeEnv, ...publicDescriptor } = session;
|
|
27296
|
+
return publicDescriptor;
|
|
27297
|
+
}
|
|
26737
27298
|
var DEFAULT_ALLOWED_ORIGINS = [
|
|
26738
27299
|
"http://localhost:*",
|
|
26739
27300
|
"http://127.0.0.1:*",
|
|
@@ -26798,7 +27359,7 @@ async function startHttpServer(opts) {
|
|
|
26798
27359
|
}
|
|
26799
27360
|
function authorizeMcp(req, res) {
|
|
26800
27361
|
const origin = req.headers.origin;
|
|
26801
|
-
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin)) {
|
|
27362
|
+
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin) && !embedTokenTrusted(req)) {
|
|
26802
27363
|
const auth = readAuth();
|
|
26803
27364
|
const header = req.headers.authorization;
|
|
26804
27365
|
if (auth.mode === "bearer" && header === `Bearer ${auth.token}`) {
|
|
@@ -26828,8 +27389,12 @@ async function startHttpServer(opts) {
|
|
|
26828
27389
|
}
|
|
26829
27390
|
const origin = req.headers.origin;
|
|
26830
27391
|
if (typeof origin === "string" && originAllowed(origin)) return "ok";
|
|
27392
|
+
if (embedTokenTrusted(req)) return "ok";
|
|
26831
27393
|
return header ? "bad" : "missing";
|
|
26832
27394
|
}
|
|
27395
|
+
function embedTokenTrusted(req) {
|
|
27396
|
+
return isValidAppEmbedToken(requestEmbedToken(req));
|
|
27397
|
+
}
|
|
26833
27398
|
function originAllowed(origin) {
|
|
26834
27399
|
const list = opts.strictOrigins ? opts.allowedOrigins ?? [] : [...DEFAULT_ALLOWED_ORIGINS, ...opts.allowedOrigins ?? []];
|
|
26835
27400
|
for (const pattern of list) {
|
|
@@ -27115,7 +27680,7 @@ async function startHttpServer(opts) {
|
|
|
27115
27680
|
}
|
|
27116
27681
|
function guardBrowserOrigin(req, res) {
|
|
27117
27682
|
const origin = req.headers.origin;
|
|
27118
|
-
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin)) {
|
|
27683
|
+
if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin) && !embedTokenTrusted(req)) {
|
|
27119
27684
|
const auth = readAuth();
|
|
27120
27685
|
const header = req.headers.authorization;
|
|
27121
27686
|
if (auth.mode === "bearer" && header === `Bearer ${auth.token}`) {
|
|
@@ -27134,10 +27699,14 @@ async function startHttpServer(opts) {
|
|
|
27134
27699
|
}
|
|
27135
27700
|
function applyCors(req, res) {
|
|
27136
27701
|
const origin = req.headers.origin;
|
|
27137
|
-
const
|
|
27138
|
-
|
|
27702
|
+
const tokenTrusted = embedTokenTrusted(req);
|
|
27703
|
+
const originTrusted = typeof origin === "string" && origin.length > 0 && originAllowed(origin);
|
|
27704
|
+
const trusted = originTrusted || tokenTrusted;
|
|
27705
|
+
if (originTrusted) {
|
|
27139
27706
|
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
27140
27707
|
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
27708
|
+
} else if (tokenTrusted && origin && origin !== "null") {
|
|
27709
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
27141
27710
|
} else {
|
|
27142
27711
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
27143
27712
|
}
|
|
@@ -28105,7 +28674,9 @@ async function startHttpServer(opts) {
|
|
|
28105
28674
|
const uiMatch = path.match(/^\/apps\/(.+)\/ui$/);
|
|
28106
28675
|
if (uiMatch && req.method === "GET") {
|
|
28107
28676
|
const uiApp = opts.appRegistry.getApp(decodeURIComponent(uiMatch[1]));
|
|
28108
|
-
if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && guardBrowserOrigin(req, res))
|
|
28677
|
+
if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && !isValidAppEmbedToken(requestEmbedToken(req)) && guardBrowserOrigin(req, res)) {
|
|
28678
|
+
return;
|
|
28679
|
+
}
|
|
28109
28680
|
if (!authorize(req, res)) return;
|
|
28110
28681
|
await handleAppUiPage(
|
|
28111
28682
|
req,
|
|
@@ -28851,7 +29422,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
28851
29422
|
} else if (!includeCommands) {
|
|
28852
29423
|
rows = rows.filter((s) => s.kind !== "command");
|
|
28853
29424
|
}
|
|
28854
|
-
json(200, { sessions: rows });
|
|
29425
|
+
json(200, { sessions: rows.map(sessionDescriptorForHttp) });
|
|
28855
29426
|
return true;
|
|
28856
29427
|
}
|
|
28857
29428
|
if (path === "/sessions/summaries" && req.method === "GET") {
|
|
@@ -28941,7 +29512,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
28941
29512
|
return true;
|
|
28942
29513
|
}
|
|
28943
29514
|
json(201, {
|
|
28944
|
-
...result.descriptor,
|
|
29515
|
+
...sessionDescriptorForHttp(result.descriptor),
|
|
28945
29516
|
...result.warnings ? { warnings: result.warnings } : {},
|
|
28946
29517
|
...result.deduped ? { deduped: true } : {},
|
|
28947
29518
|
...result.dedupeSource ? { dedupeSource: result.dedupeSource } : {}
|
|
@@ -29157,7 +29728,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
29157
29728
|
...typeof b.name === "string" ? { name: b.name } : {},
|
|
29158
29729
|
...typeof b.label === "string" ? { label: b.label } : {}
|
|
29159
29730
|
});
|
|
29160
|
-
json(201, desc);
|
|
29731
|
+
json(201, sessionDescriptorForHttp(desc));
|
|
29161
29732
|
} catch (err) {
|
|
29162
29733
|
const msg = err instanceof Error ? err.message : String(err);
|
|
29163
29734
|
const status = msg.includes("already in use") ? 409 : msg.includes("no PTY factory") ? 501 : 500;
|
|
@@ -29396,7 +29967,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
29396
29967
|
...listCatalogModels ? { listCatalogModels } : {}
|
|
29397
29968
|
});
|
|
29398
29969
|
json(200, {
|
|
29399
|
-
...restarted.desc,
|
|
29970
|
+
...sessionDescriptorForHttp(restarted.desc),
|
|
29400
29971
|
resumedFrom: restarted.resumedFrom,
|
|
29401
29972
|
resumeVia: restarted.resumeVia,
|
|
29402
29973
|
...restarted.resumeFallback ? { resumeFallback: true } : {}
|
|
@@ -29471,7 +30042,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
29471
30042
|
};
|
|
29472
30043
|
try {
|
|
29473
30044
|
const desc = registry.renameSession(resolved.id, patch);
|
|
29474
|
-
json(200, desc);
|
|
30045
|
+
json(200, sessionDescriptorForHttp(desc));
|
|
29475
30046
|
} catch (err) {
|
|
29476
30047
|
const msg = err instanceof Error ? err.message : String(err);
|
|
29477
30048
|
json(msg.includes("no session") ? 404 : 500, { error: "rename_failed", message: msg });
|
|
@@ -29522,7 +30093,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
29522
30093
|
env: b.env && typeof b.env === "object" ? b.env : void 0,
|
|
29523
30094
|
label: typeof b.label === "string" ? b.label : void 0
|
|
29524
30095
|
});
|
|
29525
|
-
json(201, desc);
|
|
30096
|
+
json(201, sessionDescriptorForHttp(desc));
|
|
29526
30097
|
} catch (err) {
|
|
29527
30098
|
json(500, {
|
|
29528
30099
|
error: "spawn_failed",
|
|
@@ -29905,7 +30476,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
29905
30476
|
json(404, { error: "session_not_found", id: rawIdOrName });
|
|
29906
30477
|
return true;
|
|
29907
30478
|
}
|
|
29908
|
-
json(200, resolvedDesc);
|
|
30479
|
+
json(200, sessionDescriptorForHttp(resolvedDesc));
|
|
29909
30480
|
return true;
|
|
29910
30481
|
}
|
|
29911
30482
|
if (!suffix && req.method === "DELETE") {
|
|
@@ -30817,7 +31388,7 @@ async function handleProviderInbound(req, res, slug, deps2) {
|
|
|
30817
31388
|
res.end(JSON.stringify(result));
|
|
30818
31389
|
}
|
|
30819
31390
|
function requestHttpBaseUrl(req) {
|
|
30820
|
-
return
|
|
31391
|
+
return resolveRequestHttpBaseUrl(req.headers);
|
|
30821
31392
|
}
|
|
30822
31393
|
async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
|
|
30823
31394
|
const app = appRegistry.getApp(appId);
|
|
@@ -30853,13 +31424,24 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
|
|
|
30853
31424
|
if (frameAncestors.length === 0) {
|
|
30854
31425
|
headers["x-frame-options"] = "SAMEORIGIN";
|
|
30855
31426
|
}
|
|
31427
|
+
if (embedRequested && !isValidAppEmbedToken(requestEmbedToken(req))) {
|
|
31428
|
+
console.warn(
|
|
31429
|
+
`[app-ui] embed refused for "${appId}": ` + JSON.stringify({
|
|
31430
|
+
secFetchDest: req.headers["sec-fetch-dest"] ?? null,
|
|
31431
|
+
origin: req.headers.origin ?? null,
|
|
31432
|
+
referer: req.headers.referer ?? null,
|
|
31433
|
+
embedToken: requestEmbedToken(req) != null
|
|
31434
|
+
})
|
|
31435
|
+
);
|
|
31436
|
+
}
|
|
30856
31437
|
}
|
|
30857
31438
|
res.writeHead(200, headers);
|
|
30858
|
-
res.end(injectStandaloneAppBridge(raw));
|
|
31439
|
+
res.end(injectStandaloneAppBridge(raw, requestHttpBaseUrl(req)));
|
|
30859
31440
|
}
|
|
30860
31441
|
function iframeEmbedAllowed(req, app) {
|
|
30861
31442
|
const secFetchDest = req.headers["sec-fetch-dest"];
|
|
30862
31443
|
if (secFetchDest !== void 0 && secFetchDest !== "iframe") return false;
|
|
31444
|
+
if (isValidAppEmbedToken(requestEmbedToken(req))) return true;
|
|
30863
31445
|
const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
|
|
30864
31446
|
const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
|
|
30865
31447
|
if (!origin && !referer) return false;
|
|
@@ -30880,6 +31462,13 @@ function iframeEmbedAllowed(req, app) {
|
|
|
30880
31462
|
}
|
|
30881
31463
|
return false;
|
|
30882
31464
|
}
|
|
31465
|
+
function requestEmbedToken(req) {
|
|
31466
|
+
try {
|
|
31467
|
+
return new URL(req.url ?? "/", "http://localhost").searchParams.get("et");
|
|
31468
|
+
} catch {
|
|
31469
|
+
return null;
|
|
31470
|
+
}
|
|
31471
|
+
}
|
|
30883
31472
|
function iframeEmbedOriginAllowed(req, app) {
|
|
30884
31473
|
const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
|
|
30885
31474
|
const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
|
|
@@ -34838,6 +35427,7 @@ function createOrchestratorMcpServerFactory(deps2) {
|
|
|
34838
35427
|
...deps2.listHarnessCapabilities ? { listHarnessCapabilities: deps2.listHarnessCapabilities } : {},
|
|
34839
35428
|
...deps2.webhookNotifier ? { webhookNotifier: deps2.webhookNotifier } : {},
|
|
34840
35429
|
...deps2.resolveSandboxProvider ? { resolveSandboxProvider: deps2.resolveSandboxProvider } : {},
|
|
35430
|
+
...deps2.defaultAgentPromptInterrupt != null ? { defaultAgentPromptInterrupt: deps2.defaultAgentPromptInterrupt } : {},
|
|
34841
35431
|
daemonMcpUrl: deps2.daemonMcpUrl
|
|
34842
35432
|
});
|
|
34843
35433
|
registerOrchestrationTools(server, {
|
|
@@ -37961,7 +38551,7 @@ async function createGateway(opts) {
|
|
|
37961
38551
|
const port = opts.port ?? 18790;
|
|
37962
38552
|
const daemonMcpUrl = `http://127.0.0.1:${port}/mcp`;
|
|
37963
38553
|
const resolveSandboxProviderResolved = opts.resolveSandboxProvider ?? makeSandboxResolver(makeSandboxCredsStore());
|
|
37964
|
-
const ptyWsBaseUrl =
|
|
38554
|
+
const { httpOrigin: publicHttpOrigin, wsOrigin: ptyWsBaseUrl } = resolvePublicAppOrigins(port);
|
|
37965
38555
|
const events = createRuntimeEvents();
|
|
37966
38556
|
const conversations = fileConversationStore({ workspace });
|
|
37967
38557
|
const workspaceFs = createWorkspaceFs({ workspace });
|
|
@@ -38151,6 +38741,13 @@ async function createGateway(opts) {
|
|
|
38151
38741
|
dispatchTool
|
|
38152
38742
|
});
|
|
38153
38743
|
const appRegistry = createAppRegistry({ persist });
|
|
38744
|
+
const isSessionChatInstalled = () => {
|
|
38745
|
+
try {
|
|
38746
|
+
return appRegistry.getApp(SESSION_CHAT_APP_ID)?.ui != null;
|
|
38747
|
+
} catch {
|
|
38748
|
+
return false;
|
|
38749
|
+
}
|
|
38750
|
+
};
|
|
38154
38751
|
const appUiHtmlCache = createUiHtmlCache();
|
|
38155
38752
|
const workflowRunner = opts.resolveAgentAdapter ? createWorkflowRunner({
|
|
38156
38753
|
registry: sessions,
|
|
@@ -38278,6 +38875,10 @@ async function createGateway(opts) {
|
|
|
38278
38875
|
// can declare `sandbox` and resolve a provider, instead of throwing
|
|
38279
38876
|
// `sandbox_provider_not_found`.
|
|
38280
38877
|
resolveSandboxProvider: resolveSandboxProviderResolved,
|
|
38878
|
+
// config.json `defaults.agentPromptInterrupt` — same unset-default the
|
|
38879
|
+
// root /mcp surface uses, so a child driving/reporting through the scoped
|
|
38880
|
+
// orchestrator gateway honours it too.
|
|
38881
|
+
...configDefaults?.agentPromptInterrupt != null ? { defaultAgentPromptInterrupt: configDefaults.agentPromptInterrupt } : {},
|
|
38281
38882
|
...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
|
|
38282
38883
|
...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
|
|
38283
38884
|
...opts.listHarnessCapabilities ? { listHarnessCapabilities: opts.listHarnessCapabilities } : {}
|
|
@@ -38338,6 +38939,9 @@ async function createGateway(opts) {
|
|
|
38338
38939
|
webhookNotifier,
|
|
38339
38940
|
daemonMcpUrl,
|
|
38340
38941
|
resolveSandboxProvider: resolveSandboxProviderResolved,
|
|
38942
|
+
// config.json `defaults.agentPromptInterrupt` — the unset-default for
|
|
38943
|
+
// `interrupt` on agent_prompt / message_parent. Omitted ⇒ false.
|
|
38944
|
+
...configDefaults?.agentPromptInterrupt != null ? { defaultAgentPromptInterrupt: configDefaults.agentPromptInterrupt } : {},
|
|
38341
38945
|
...opts.provisionWorktree ? { provisionWorktree: opts.provisionWorktree } : {},
|
|
38342
38946
|
...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
|
|
38343
38947
|
...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
|
|
@@ -38345,7 +38949,8 @@ async function createGateway(opts) {
|
|
|
38345
38949
|
...opts.installAgentAdapter ? { installAgentAdapter: opts.installAgentAdapter } : {},
|
|
38346
38950
|
...opts.listCatalogModels ? { listCatalogModels: opts.listCatalogModels } : {},
|
|
38347
38951
|
...opts.listWorktreeStatuses ? { listWorktreeStatuses: opts.listWorktreeStatuses } : {},
|
|
38348
|
-
...opts.runWorktreeGc ? { runWorktreeGc: opts.runWorktreeGc } : {}
|
|
38952
|
+
...opts.runWorktreeGc ? { runWorktreeGc: opts.runWorktreeGc } : {},
|
|
38953
|
+
isSessionChatInstalled
|
|
38349
38954
|
});
|
|
38350
38955
|
registerBrainTools(server, {
|
|
38351
38956
|
brains: workspaceBrains,
|
|
@@ -38402,18 +39007,12 @@ async function createGateway(opts) {
|
|
|
38402
39007
|
listSessions: listSessionsFiltered,
|
|
38403
39008
|
// httpBaseUrl = this daemon's own origin (SSE stream + bridge
|
|
38404
39009
|
// fallback for the live-session widget).
|
|
38405
|
-
httpBaseUrl:
|
|
39010
|
+
httpBaseUrl: publicHttpOrigin,
|
|
38406
39011
|
// The session-chat widget is a thin launcher for the installed
|
|
38407
|
-
// `@agentik/session-chat` studio app —
|
|
38408
|
-
//
|
|
38409
|
-
// `
|
|
38410
|
-
isSessionChatInstalled
|
|
38411
|
-
try {
|
|
38412
|
-
return appRegistry.getApp(SESSION_CHAT_APP_ID)?.ui != null;
|
|
38413
|
-
} catch {
|
|
38414
|
-
return false;
|
|
38415
|
-
}
|
|
38416
|
-
},
|
|
39012
|
+
// `@agentik/session-chat` studio app — same call-time-resolved check
|
|
39013
|
+
// `registerSessionTools` above gets, so the mount decision here and
|
|
39014
|
+
// `agent_start`'s launch-card binding never disagree.
|
|
39015
|
+
isSessionChatInstalled,
|
|
38417
39016
|
// Work-board widget's read path — the root `/mcp` endpoint has no
|
|
38418
39017
|
// scope, so this mount is always the operator caller (default
|
|
38419
39018
|
// board `ws:<slug>`); `canAccessBoard` lets the operator read any
|