@agentproto/runtime 3.5.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/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
- const last = r.time_updated ?? r.time_created;
1670
- return last === void 0 || last >= sinceMs - 1e3;
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
  }
@@ -2243,6 +2246,9 @@ function conversationTerminalSlugFor(subject) {
2243
2246
  const argv = subject.argv;
2244
2247
  if (!argv || argv.length === 0 || !argv[0]) return void 0;
2245
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
+ }
2246
2252
  for (const [slug, launch] of Object.entries(NATIVE_LAUNCH_ARGV)) {
2247
2253
  const launchBin = launch[0];
2248
2254
  if (!launchBin || argvBasename(launchBin) !== bin) continue;
@@ -2265,7 +2271,7 @@ function isConversationTerminal(subject) {
2265
2271
  }
2266
2272
  return false;
2267
2273
  }
2268
- var CONVERSATION_STORES, NATIVE_LAUNCH_ARGV, CONVERSATION_RESUME_KEYS;
2274
+ var CONVERSATION_STORES, NATIVE_LAUNCH_ARGV, BARE_LAUNCH_BINS, CONVERSATION_RESUME_KEYS;
2269
2275
  var init_conversation_store = __esm({
2270
2276
  "src/conversation-store.ts"() {
2271
2277
  CONVERSATION_STORES = {
@@ -2300,6 +2306,9 @@ var init_conversation_store = __esm({
2300
2306
  },
2301
2307
  opencode: {
2302
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],
2303
2312
  discover: discoverOpenCode,
2304
2313
  read: readOpenCode
2305
2314
  },
@@ -2337,6 +2346,10 @@ var init_conversation_store = __esm({
2337
2346
  "mistral-vibe": ["vibe"],
2338
2347
  "kimi-cli": ["kimi"]
2339
2348
  };
2349
+ BARE_LAUNCH_BINS = {
2350
+ opencode: ["opencode"],
2351
+ mastracode: ["mastracode"]
2352
+ };
2340
2353
  CONVERSATION_RESUME_KEYS = new Set(
2341
2354
  Object.values(CONVERSATION_STORES).map((s) => s.storeAs)
2342
2355
  );
@@ -2589,7 +2602,8 @@ var RESUME_STRATEGIES = Object.fromEntries(
2589
2602
  );
2590
2603
  function decideRestartStrategy(prev, opts = {}) {
2591
2604
  const mayPreferNative = prev.pty === true || opts.preferNativeTerminal === true;
2592
- if (prev.adapterSlug && prev.nativeTerminalResume === true && mayPreferNative) {
2605
+ const hasNativeCapability = prev.nativeTerminalResume === true || prev.pty === true && prev.nativeTerminalResume === void 0;
2606
+ if (prev.adapterSlug && hasNativeCapability && mayPreferNative) {
2593
2607
  const strategy = RESUME_STRATEGIES[prev.adapterSlug];
2594
2608
  const id = strategy?.storeAs ? prev.resumeMetadata?.[strategy.storeAs] : void 0;
2595
2609
  if (strategy?.spawnArgs && id) {
@@ -2661,11 +2675,13 @@ async function probeNativeTranscript(prev) {
2661
2675
  }
2662
2676
  function describeResumePath(prev, opts = {}) {
2663
2677
  const mayPreferNative = prev.pty === true || opts.preferNativeTerminal === true;
2664
- if (prev.adapterSlug && mayPreferNative) {
2678
+ const hasNativeCapability = prev.nativeTerminalResume === true || prev.pty === true && prev.nativeTerminalResume === void 0;
2679
+ if (prev.adapterSlug && hasNativeCapability && mayPreferNative) {
2665
2680
  const s = RESUME_STRATEGIES[prev.adapterSlug];
2666
2681
  if (s?.spawnArgs && s.storeAs && prev.resumeMetadata?.[s.storeAs]) {
2667
- const sample = s.spawnArgs("\u2026")[0] ?? prev.adapterSlug;
2668
- return `resumed via ${sample} --resume`;
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}`;
2669
2685
  }
2670
2686
  }
2671
2687
  if (prev.adapterSlug && prev.adapterSessionId) {
@@ -5359,6 +5375,9 @@ async function resolveWorkspaceRules(slug, deps2 = {}) {
5359
5375
 
5360
5376
  // src/session-title.ts
5361
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
+ }
5362
5381
  function isRecord3(value) {
5363
5382
  return typeof value === "object" && value !== null;
5364
5383
  }
@@ -5385,6 +5404,7 @@ function deriveSessionTitle(message) {
5385
5404
  if (raw === void 0) return void 0;
5386
5405
  const collapsed = raw.replace(/\s+/g, " ").trim();
5387
5406
  if (collapsed === "") return void 0;
5407
+ if (isInternalDerivedTitle(collapsed)) return void 0;
5388
5408
  const sentence = collapsed.replace(/[.?!](\s.*)?$/, "").trim();
5389
5409
  if (sentence === "" || !/[\p{L}\p{N}]/u.test(sentence)) return void 0;
5390
5410
  return Array.from(sentence).length > MAX_LENGTH ? truncate2(sentence, MAX_LENGTH) : sentence;
@@ -8284,8 +8304,175 @@ var sandboxSpecWithReuseSchema = z.object({
8284
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)."
8285
8305
  )
8286
8306
  }).strict();
8287
-
8288
- // src/agent-tools.ts
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
+ }
8289
8476
  function stripAnsi(s) {
8290
8477
  let result = s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
8291
8478
  result = result.replace(/\r\n/g, "\n");
@@ -8341,9 +8528,11 @@ function registerAgentTools(server, opts) {
8341
8528
  provisionWorktree,
8342
8529
  resolveWorktreeIsolation,
8343
8530
  supervisor,
8344
- defaultAgentPromptInterrupt
8531
+ defaultAgentPromptInterrupt,
8532
+ isSessionChatInstalled
8345
8533
  } = opts;
8346
8534
  const interruptDefault = defaultAgentPromptInterrupt ?? false;
8535
+ const sessionChatResourceUri = isSessionChatInstalled?.() ? `ui://${appUiToolId(SESSION_CHAT_APP_ID)}/view` : "ui://agentproto_session_chat/view";
8347
8536
  server.registerTool(
8348
8537
  "agent_start",
8349
8538
  {
@@ -8589,10 +8778,15 @@ function registerAgentTools(server, opts) {
8589
8778
  // `ui://live_session/view` resource stays registered for its own
8590
8779
  // `live_session` tool and other consumers — only this binding moved.
8591
8780
  // `visibility:["model","app"]` keeps agent_start fully usable by the
8592
- // model AND lets the widget re-call it if needed.
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.
8593
8787
  _meta: {
8594
8788
  ui: {
8595
- resourceUri: "ui://agentproto_session_chat/view",
8789
+ resourceUri: sessionChatResourceUri,
8596
8790
  visibility: ["model", "app"]
8597
8791
  }
8598
8792
  }
@@ -10432,6 +10626,149 @@ function filterSessionObserver(inner, shouldObserve) {
10432
10626
  init_tool_presenter();
10433
10627
  init_transcript_writer();
10434
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
+
10435
10772
  // src/resume-context-digest.ts
10436
10773
  init_transcript_export();
10437
10774
  var DIGEST_CHAR_BUDGET = 7e3;
@@ -12712,6 +13049,9 @@ ${message}`;
12712
13049
  });
12713
13050
  }
12714
13051
  if (overBudget) emitExited(rt);
13052
+ if ((rt.desc.turnsCompleted ?? 0) === 1) {
13053
+ void maybeTitleSession(registry, rt.desc.id);
13054
+ }
12715
13055
  } else {
12716
13056
  rt.desc.turnsCompleted = (rt.desc.turnsCompleted ?? 0) + 1;
12717
13057
  if (rt.desc.lastTurnEmpty !== void 0) delete rt.desc.lastTurnEmpty;
@@ -13221,7 +13561,7 @@ ${message}`;
13221
13561
  const resumeArgvId = (() => {
13222
13562
  if (!conversationStore) return void 0;
13223
13563
  for (let i = 0; i < input.argv.length - 1; i++) {
13224
- if (input.argv[i] !== "--resume" && input.argv[i] !== "-r") continue;
13564
+ if (input.argv[i] !== "--resume" && input.argv[i] !== "-r" && !(conversationSlug === "opencode" && (input.argv[i] === "--session" || input.argv[i] === "-s"))) continue;
13225
13565
  const raw = input.argv[i + 1];
13226
13566
  const base = raw.slice(raw.lastIndexOf("/") + 1);
13227
13567
  return base.endsWith(".jsonl") ? base.slice(0, -".jsonl".length) : raw;
@@ -16671,6 +17011,10 @@ var mcpBool2 = z.preprocess(
16671
17011
  (v) => v === "true" ? true : v === "false" ? false : v,
16672
17012
  z.boolean()
16673
17013
  );
17014
+ var publicSessionDescriptor = (session) => {
17015
+ const { ptyResumeEnv: _privateResumeEnv, ...publicDescriptor } = session;
17016
+ return publicDescriptor;
17017
+ };
16674
17018
  var compactSessionItem = (s) => ({
16675
17019
  id: s.id,
16676
17020
  kind: s.kind,
@@ -16807,7 +17151,7 @@ function registerSessionTools(rawServer, opts) {
16807
17151
  (s) => s.status === "running" || s.status === "starting"
16808
17152
  );
16809
17153
  }
16810
- return rows;
17154
+ return rows.map(publicSessionDescriptor);
16811
17155
  },
16812
17156
  transformers: [
16813
17157
  paginated({
@@ -17264,7 +17608,7 @@ function registerSessionTools(rawServer, opts) {
17264
17608
  (s) => s.status === "running" || s.status === "starting"
17265
17609
  );
17266
17610
  }
17267
- return rows;
17611
+ return rows.map(publicSessionDescriptor);
17268
17612
  },
17269
17613
  project: compactSessionItemWithProvenance,
17270
17614
  keyOf: (s) => s.id,
@@ -17299,7 +17643,7 @@ function registerSessionTools(rawServer, opts) {
17299
17643
  (s) => s.status === "running" || s.status === "starting"
17300
17644
  );
17301
17645
  }
17302
- return rows;
17646
+ return rows.map(publicSessionDescriptor);
17303
17647
  },
17304
17648
  project: compactSessionItemWithProvenance,
17305
17649
  keyOf: (s) => s.id,
@@ -18213,7 +18557,7 @@ function registerSessionTools(rawServer, opts) {
18213
18557
  content: [
18214
18558
  {
18215
18559
  type: "text",
18216
- text: JSON.stringify(desc)
18560
+ text: JSON.stringify(publicSessionDescriptor(desc))
18217
18561
  }
18218
18562
  ]
18219
18563
  };
@@ -18258,7 +18602,7 @@ function registerSessionTools(rawServer, opts) {
18258
18602
  type: "text",
18259
18603
  text: JSON.stringify(
18260
18604
  {
18261
- ...restarted.desc,
18605
+ ...publicSessionDescriptor(restarted.desc),
18262
18606
  resumedFrom: restarted.resumedFrom,
18263
18607
  resumeVia: restarted.resumeVia,
18264
18608
  ...restarted.resumeFallback ? { resumeFallback: true } : {},
@@ -18329,7 +18673,7 @@ function registerSessionTools(rawServer, opts) {
18329
18673
  try {
18330
18674
  const desc = registry.archiveSession(prev.id);
18331
18675
  return {
18332
- content: [{ type: "text", text: JSON.stringify(desc) }]
18676
+ content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
18333
18677
  };
18334
18678
  } catch (err) {
18335
18679
  return {
@@ -18411,7 +18755,7 @@ function registerSessionTools(rawServer, opts) {
18411
18755
  try {
18412
18756
  const desc = registry.unarchiveSession(prev.id);
18413
18757
  return {
18414
- content: [{ type: "text", text: JSON.stringify(desc) }]
18758
+ content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
18415
18759
  };
18416
18760
  } catch (err) {
18417
18761
  return {
@@ -18496,7 +18840,7 @@ function registerSessionTools(rawServer, opts) {
18496
18840
  reason: input.reason
18497
18841
  });
18498
18842
  return {
18499
- content: [{ type: "text", text: JSON.stringify(desc) }]
18843
+ content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
18500
18844
  };
18501
18845
  } catch (err) {
18502
18846
  return {
@@ -18580,7 +18924,7 @@ function registerSessionTools(rawServer, opts) {
18580
18924
  ...input.label !== void 0 ? { label: input.label } : {}
18581
18925
  });
18582
18926
  return {
18583
- content: [{ type: "text", text: JSON.stringify(desc) }]
18927
+ content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
18584
18928
  };
18585
18929
  } catch (err) {
18586
18930
  return {
@@ -18642,7 +18986,7 @@ function registerSessionTools(rawServer, opts) {
18642
18986
  try {
18643
18987
  const desc = registry.setKeepAlive(prev.id, input.keepAlive);
18644
18988
  return {
18645
- content: [{ type: "text", text: JSON.stringify(desc) }]
18989
+ content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
18646
18990
  };
18647
18991
  } catch (err) {
18648
18992
  return {
@@ -18704,7 +19048,7 @@ function registerSessionTools(rawServer, opts) {
18704
19048
  try {
18705
19049
  const desc = registry.setPinned(prev.id, input.pinned);
18706
19050
  return {
18707
- content: [{ type: "text", text: JSON.stringify(desc) }]
19051
+ content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
18708
19052
  };
18709
19053
  } catch (err) {
18710
19054
  return {
@@ -18820,7 +19164,7 @@ function registerSessionTools(rawServer, opts) {
18820
19164
  } : {}
18821
19165
  });
18822
19166
  return {
18823
- content: [{ type: "text", text: JSON.stringify(desc) }]
19167
+ content: [{ type: "text", text: JSON.stringify(publicSessionDescriptor(desc)) }]
18824
19168
  };
18825
19169
  } catch (err) {
18826
19170
  return {
@@ -20640,6 +20984,7 @@ function registerMcpApps(server, apps) {
20640
20984
  }
20641
20985
  }
20642
20986
  function makeBuiltinPanelApps(ops) {
20987
+ const sessionChatNativeInstalled = ops.isSessionChatInstalled();
20643
20988
  return [
20644
20989
  makeSessionsPanelApp({ listSessions: ops.listSessions }),
20645
20990
  makeAgentsOverviewApp({ listSessions: ops.listSessions }),
@@ -20649,17 +20994,26 @@ function makeBuiltinPanelApps(ops) {
20649
20994
  // `agent_start` via _meta.ui.resourceUri (agent-tools.ts) so a launch
20650
20995
  // auto-renders it.
20651
20996
  makeLiveSessionApp({ httpBaseUrl: ops.httpBaseUrl }),
20652
- // Session-chat widget — thin launcher for the installed
20997
+ // Session-chat widget — thin loopback-HTTP launcher for the installed
20653
20998
  // `@agentik/session-chat` app's standalone UI (deep-linked iframe when
20654
20999
  // installed, install notice otherwise; see apps/src/session-chat).
20655
- makeSessionChatApp({
20656
- httpBaseUrl: ops.httpBaseUrl,
20657
- isSessionChatInstalled: ops.isSessionChatInstalled,
20658
- // Hand every result a live embed token so a host-cached widget
20659
- // (Claude Desktop's per-conversation srcdoc) re-arms itself after a
20660
- // daemon restart instead of 403ing on its dead baked token.
20661
- mintEmbedToken: () => stableAppEmbedToken("agentproto_session_chat")
20662
- }),
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
+ ],
20663
21017
  // Work-board widget — kanban over the Task ledger (see apps/src/
20664
21018
  // work-board). Read path only; writes go through task_claim/
20665
21019
  // task_update/task_create over the bridge, same as every other caller.
@@ -20712,6 +21066,39 @@ function builtinPanelCatalogEntries() {
20712
21066
  };
20713
21067
  });
20714
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
+ }
20715
21102
  function summarizeSession(desc, lines, nowMs) {
20716
21103
  return {
20717
21104
  sessionId: desc.id,
@@ -23138,172 +23525,6 @@ function registerAppTools(server, opts) {
23138
23525
  }
23139
23526
  );
23140
23527
  }
23141
- function appUiToolId(appId) {
23142
- const slug = appId.replace(/^@[^/]+\//, "").replace(/[^a-z0-9]/g, "_");
23143
- return `app_ui_${slug}`;
23144
- }
23145
- var MCP_APP_BRIDGE_SCRIPT = `<script>
23146
- (function () {
23147
- if (window.McpApp) return;
23148
- var nextId = 1, pending = {}, teardownCbs = [];
23149
- function post(m) { window.parent.postMessage(m, "*"); }
23150
- window.addEventListener("message", function (e) {
23151
- var m = e.data;
23152
- if (typeof m === "string") { try { m = JSON.parse(m); } catch (_) { return; } }
23153
- if (!m || m.jsonrpc !== "2.0") return;
23154
- if (m.method === "ui/resource-teardown") {
23155
- for (var i = 0; i < teardownCbs.length; i++) teardownCbs[i]();
23156
- post({ jsonrpc: "2.0", id: m.id, result: {} });
23157
- return;
23158
- }
23159
- if (m.id != null && pending[m.id]) { var cb = pending[m.id]; delete pending[m.id]; cb(m.result, m.error); }
23160
- });
23161
- function request(method, params) {
23162
- return new Promise(function (resolve, reject) {
23163
- var id = nextId++;
23164
- pending[id] = function (r, err) { if (err) reject(new Error((err && err.message) || String(err))); else resolve(r); };
23165
- post({ jsonrpc: "2.0", id: id, method: method, params: params });
23166
- });
23167
- }
23168
- window.McpApp = {
23169
- connect: function () {
23170
- if (window.parent === window) return Promise.reject(new Error("no host (standalone)"));
23171
- return request("ui/initialize", {
23172
- appInfo: { name: "agentproto-app", version: "1.0" },
23173
- appCapabilities: { availableDisplayModes: ["inline", "fullscreen"] },
23174
- protocolVersion: "2026-01-26"
23175
- }).then(function () {
23176
- post({ jsonrpc: "2.0", method: "ui/notifications/initialized", params: {} });
23177
- return {
23178
- callTool: function (name, args) { return request("tools/call", { name: name, arguments: args || {} }); },
23179
- sendMessage: function (p) {
23180
- var params = typeof p === "string" ? { content: p } : (p || {});
23181
- var content = typeof params.content === "string" ? [{ type: "text", text: params.content }] : (params.content || []);
23182
- return request("ui/message", { role: "user", content: content });
23183
- },
23184
- updateModelContext: function (p) { return request("ui/update-model-context", p || {}); },
23185
- openLink: function (url) { return request("ui/open-link", { url: url }); },
23186
- onTeardown: function (cb) { teardownCbs.push(cb); }
23187
- };
23188
- });
23189
- }
23190
- };
23191
- })();
23192
- </script>
23193
- `;
23194
- function injectAfterStructuralTag(html, script) {
23195
- for (const tag of ["head", "body", "html"]) {
23196
- const match = html.match(new RegExp(`<${tag}[^>]*>`, "i"));
23197
- if (match?.index !== void 0) {
23198
- const insertAt = match.index + match[0].length;
23199
- return html.slice(0, insertAt) + script + html.slice(insertAt);
23200
- }
23201
- }
23202
- return script + html;
23203
- }
23204
- function injectMcpAppBridge(html) {
23205
- const hasBridge = /window\.McpApp\s*=/.test(html);
23206
- const hasRunnerSelect = /window\.AgentprotoUI\s*=/.test(html);
23207
- if (hasBridge && hasRunnerSelect) return html;
23208
- const script = (hasBridge ? "" : MCP_APP_BRIDGE_SCRIPT) + (hasRunnerSelect ? "" : RUNNER_SELECT_SCRIPT);
23209
- return injectAfterStructuralTag(html, script);
23210
- }
23211
- var STANDALONE_REST_BRIDGE_SCRIPT = `<script>
23212
- (function () {
23213
- if (window.McpApp) return;
23214
- window.McpApp = {
23215
- connect: function () {
23216
- return Promise.resolve({
23217
- callTool: function (name, args) {
23218
- return fetch("./tool-call", {
23219
- method: "POST",
23220
- headers: { "content-type": "application/json" },
23221
- body: JSON.stringify({ tool: name, args: args || {} })
23222
- }).then(function (res) {
23223
- return res.json().catch(function () {
23224
- throw new Error("tool-call failed: HTTP " + res.status);
23225
- }).then(function (body) {
23226
- if (!res.ok) throw new Error((body && (body.message || body.error)) || ("tool-call failed: HTTP " + res.status));
23227
- return body;
23228
- });
23229
- });
23230
- },
23231
- sendMessage: function () {
23232
- return Promise.reject(new Error("sendMessage: no chat host (standalone mode)"));
23233
- },
23234
- updateModelContext: function () {
23235
- return Promise.reject(new Error("updateModelContext: no host (standalone mode)"));
23236
- },
23237
- openLink: function (url) {
23238
- window.open(url, "_blank");
23239
- return Promise.resolve({});
23240
- },
23241
- onTeardown: function () {}
23242
- });
23243
- }
23244
- };
23245
- })();
23246
- </script>
23247
- `;
23248
- function injectStandaloneAppBridge(html) {
23249
- const hasRunnerSelect = /window\.AgentprotoUI\s*=/.test(html);
23250
- const script = STANDALONE_REST_BRIDGE_SCRIPT + (hasRunnerSelect ? "" : RUNNER_SELECT_SCRIPT);
23251
- return injectAfterStructuralTag(html, script);
23252
- }
23253
- function createUiHtmlCache() {
23254
- const cache = /* @__PURE__ */ new Map();
23255
- return {
23256
- async get(path, version) {
23257
- const cached = cache.get(path);
23258
- if (cached && cached.version === version) return cached.html;
23259
- const raw = await readFile(path, "utf8");
23260
- const html = injectMcpAppBridge(raw);
23261
- cache.set(path, { version, html });
23262
- return html;
23263
- }
23264
- };
23265
- }
23266
- async function makeInstalledAppUiApps(appRegistry, cache, existingToolNames) {
23267
- const apps = [];
23268
- const seen = new Set(existingToolNames);
23269
- for (const app of appRegistry.listApps()) {
23270
- const ui = app.ui;
23271
- if (!ui) continue;
23272
- const toolId = appUiToolId(app.appId);
23273
- if (seen.has(toolId)) {
23274
- console.warn(
23275
- `[app-ui-apps] skipping UI panel for app "${app.appId}": tool id "${toolId}" collides with an existing tool or another installed app's panel.`
23276
- );
23277
- continue;
23278
- }
23279
- let html;
23280
- try {
23281
- html = await cache.get(ui.path, app.updatedAt);
23282
- } catch (err) {
23283
- console.warn(
23284
- `[app-ui-apps] skipping UI panel for app "${app.appId}": could not read "${ui.path}": ${err instanceof Error ? err.message : String(err)}`
23285
- );
23286
- continue;
23287
- }
23288
- seen.add(toolId);
23289
- apps.push({
23290
- id: toolId,
23291
- title: ui.title ?? app.name ?? app.appId,
23292
- ...ui.description ? { description: ui.description } : {},
23293
- inputSchema: z.object({}),
23294
- execute: async () => ({ appId: app.appId, tools: ui.tools ?? [] }),
23295
- html,
23296
- ...ui.csp ? {
23297
- csp: {
23298
- ...ui.csp.connectDomains ? { connectDomains: [...ui.csp.connectDomains] } : {},
23299
- ...ui.csp.resourceDomains ? { resourceDomains: [...ui.csp.resourceDomains] } : {},
23300
- ...ui.csp.frameDomains ? { frameDomains: [...ui.csp.frameDomains] } : {}
23301
- }
23302
- } : {}
23303
- });
23304
- }
23305
- return apps;
23306
- }
23307
23528
  function paginatedLegacyList3(opts) {
23308
23529
  return {
23309
23530
  name: "paginatedLegacyList",
@@ -27070,6 +27291,10 @@ function registerPresetTools(server, opts = {}) {
27070
27291
  lister: async () => listPresets(process.env, adapterPresets)
27071
27292
  });
27072
27293
  }
27294
+ function sessionDescriptorForHttp(session) {
27295
+ const { ptyResumeEnv: _privateResumeEnv, ...publicDescriptor } = session;
27296
+ return publicDescriptor;
27297
+ }
27073
27298
  var DEFAULT_ALLOWED_ORIGINS = [
27074
27299
  "http://localhost:*",
27075
27300
  "http://127.0.0.1:*",
@@ -29197,7 +29422,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
29197
29422
  } else if (!includeCommands) {
29198
29423
  rows = rows.filter((s) => s.kind !== "command");
29199
29424
  }
29200
- json(200, { sessions: rows });
29425
+ json(200, { sessions: rows.map(sessionDescriptorForHttp) });
29201
29426
  return true;
29202
29427
  }
29203
29428
  if (path === "/sessions/summaries" && req.method === "GET") {
@@ -29287,7 +29512,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
29287
29512
  return true;
29288
29513
  }
29289
29514
  json(201, {
29290
- ...result.descriptor,
29515
+ ...sessionDescriptorForHttp(result.descriptor),
29291
29516
  ...result.warnings ? { warnings: result.warnings } : {},
29292
29517
  ...result.deduped ? { deduped: true } : {},
29293
29518
  ...result.dedupeSource ? { dedupeSource: result.dedupeSource } : {}
@@ -29503,7 +29728,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
29503
29728
  ...typeof b.name === "string" ? { name: b.name } : {},
29504
29729
  ...typeof b.label === "string" ? { label: b.label } : {}
29505
29730
  });
29506
- json(201, desc);
29731
+ json(201, sessionDescriptorForHttp(desc));
29507
29732
  } catch (err) {
29508
29733
  const msg = err instanceof Error ? err.message : String(err);
29509
29734
  const status = msg.includes("already in use") ? 409 : msg.includes("no PTY factory") ? 501 : 500;
@@ -29742,7 +29967,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
29742
29967
  ...listCatalogModels ? { listCatalogModels } : {}
29743
29968
  });
29744
29969
  json(200, {
29745
- ...restarted.desc,
29970
+ ...sessionDescriptorForHttp(restarted.desc),
29746
29971
  resumedFrom: restarted.resumedFrom,
29747
29972
  resumeVia: restarted.resumeVia,
29748
29973
  ...restarted.resumeFallback ? { resumeFallback: true } : {}
@@ -29817,7 +30042,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
29817
30042
  };
29818
30043
  try {
29819
30044
  const desc = registry.renameSession(resolved.id, patch);
29820
- json(200, desc);
30045
+ json(200, sessionDescriptorForHttp(desc));
29821
30046
  } catch (err) {
29822
30047
  const msg = err instanceof Error ? err.message : String(err);
29823
30048
  json(msg.includes("no session") ? 404 : 500, { error: "rename_failed", message: msg });
@@ -29868,7 +30093,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
29868
30093
  env: b.env && typeof b.env === "object" ? b.env : void 0,
29869
30094
  label: typeof b.label === "string" ? b.label : void 0
29870
30095
  });
29871
- json(201, desc);
30096
+ json(201, sessionDescriptorForHttp(desc));
29872
30097
  } catch (err) {
29873
30098
  json(500, {
29874
30099
  error: "spawn_failed",
@@ -30251,7 +30476,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
30251
30476
  json(404, { error: "session_not_found", id: rawIdOrName });
30252
30477
  return true;
30253
30478
  }
30254
- json(200, resolvedDesc);
30479
+ json(200, sessionDescriptorForHttp(resolvedDesc));
30255
30480
  return true;
30256
30481
  }
30257
30482
  if (!suffix && req.method === "DELETE") {
@@ -31163,7 +31388,7 @@ async function handleProviderInbound(req, res, slug, deps2) {
31163
31388
  res.end(JSON.stringify(result));
31164
31389
  }
31165
31390
  function requestHttpBaseUrl(req) {
31166
- return `http://${req.headers.host ?? "127.0.0.1"}`;
31391
+ return resolveRequestHttpBaseUrl(req.headers);
31167
31392
  }
31168
31393
  async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
31169
31394
  const app = appRegistry.getApp(appId);
@@ -31211,7 +31436,7 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
31211
31436
  }
31212
31437
  }
31213
31438
  res.writeHead(200, headers);
31214
- res.end(injectStandaloneAppBridge(raw));
31439
+ res.end(injectStandaloneAppBridge(raw, requestHttpBaseUrl(req)));
31215
31440
  }
31216
31441
  function iframeEmbedAllowed(req, app) {
31217
31442
  const secFetchDest = req.headers["sec-fetch-dest"];
@@ -38326,7 +38551,7 @@ async function createGateway(opts) {
38326
38551
  const port = opts.port ?? 18790;
38327
38552
  const daemonMcpUrl = `http://127.0.0.1:${port}/mcp`;
38328
38553
  const resolveSandboxProviderResolved = opts.resolveSandboxProvider ?? makeSandboxResolver(makeSandboxCredsStore());
38329
- const ptyWsBaseUrl = process.env.AGENTPROTO_PUBLIC_WS_ORIGIN?.trim().replace(/\/+$/, "") || `ws://127.0.0.1:${port}`;
38554
+ const { httpOrigin: publicHttpOrigin, wsOrigin: ptyWsBaseUrl } = resolvePublicAppOrigins(port);
38330
38555
  const events = createRuntimeEvents();
38331
38556
  const conversations = fileConversationStore({ workspace });
38332
38557
  const workspaceFs = createWorkspaceFs({ workspace });
@@ -38516,6 +38741,13 @@ async function createGateway(opts) {
38516
38741
  dispatchTool
38517
38742
  });
38518
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
+ };
38519
38751
  const appUiHtmlCache = createUiHtmlCache();
38520
38752
  const workflowRunner = opts.resolveAgentAdapter ? createWorkflowRunner({
38521
38753
  registry: sessions,
@@ -38717,7 +38949,8 @@ async function createGateway(opts) {
38717
38949
  ...opts.installAgentAdapter ? { installAgentAdapter: opts.installAgentAdapter } : {},
38718
38950
  ...opts.listCatalogModels ? { listCatalogModels: opts.listCatalogModels } : {},
38719
38951
  ...opts.listWorktreeStatuses ? { listWorktreeStatuses: opts.listWorktreeStatuses } : {},
38720
- ...opts.runWorktreeGc ? { runWorktreeGc: opts.runWorktreeGc } : {}
38952
+ ...opts.runWorktreeGc ? { runWorktreeGc: opts.runWorktreeGc } : {},
38953
+ isSessionChatInstalled
38721
38954
  });
38722
38955
  registerBrainTools(server, {
38723
38956
  brains: workspaceBrains,
@@ -38774,18 +39007,12 @@ async function createGateway(opts) {
38774
39007
  listSessions: listSessionsFiltered,
38775
39008
  // httpBaseUrl = this daemon's own origin (SSE stream + bridge
38776
39009
  // fallback for the live-session widget).
38777
- httpBaseUrl: `http://127.0.0.1:${port}`,
39010
+ httpBaseUrl: publicHttpOrigin,
38778
39011
  // The session-chat widget is a thin launcher for the installed
38779
- // `@agentik/session-chat` studio app — resolve installed-ness from
38780
- // the AppRegistry at call time (not boot) so `app_install`/
38781
- // `app_uninstall` of that app is reflected without a daemon restart.
38782
- isSessionChatInstalled: () => {
38783
- try {
38784
- return appRegistry.getApp(SESSION_CHAT_APP_ID)?.ui != null;
38785
- } catch {
38786
- return false;
38787
- }
38788
- },
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,
38789
39016
  // Work-board widget's read path — the root `/mcp` endpoint has no
38790
39017
  // scope, so this mount is always the operator caller (default
38791
39018
  // board `ws:<slug>`); `canAccessBoard` lets the operator read any