@agentproto/runtime 3.2.0 → 3.4.0

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.d.ts CHANGED
@@ -3899,8 +3899,12 @@ type WorktreeGcClass = "reclaim" | "salvage" | "hold";
3899
3899
  * `orphan` is set on an entry/outcome the orphan scan found — a directory
3900
3900
  * physically present under the repo's worktree pool with no `git worktree
3901
3901
  * list` entry at all (see `WorktreeGcPlanEntryView.orphan`).
3902
+ * `prunable` is set on an entry/outcome `git worktree list --porcelain`
3903
+ * itself already reported dead — the mirror image of `orphan`: the
3904
+ * registration is still there, but the working directory is gone (see
3905
+ * `WorktreeGcPlanEntryView.prunable`).
3902
3906
  */
3903
- type WorktreeGcReclaimReason = "dep-bump" | "orphan";
3907
+ type WorktreeGcReclaimReason = "dep-bump" | "orphan" | "prunable";
3904
3908
  /**
3905
3909
  * One entry of the dry-run plan — a runtime-local projection of a
3906
3910
  * `GcPlanEntry`. `tree` / `integration` / `liveness` are flattened to their
@@ -3912,7 +3916,7 @@ interface WorktreeGcPlanEntryView {
3912
3916
  branch: string | null;
3913
3917
  head: string;
3914
3918
  class: WorktreeGcClass;
3915
- /** Set only when `class === "reclaim"` via the dep-bump exemption or the orphan scan. */
3919
+ /** Set only when `class === "reclaim"` via the dep-bump exemption, the orphan scan, or the prunable signal. */
3916
3920
  reclaimReason?: WorktreeGcReclaimReason;
3917
3921
  /**
3918
3922
  * `true` only for an orphan-scan entry: a directory physically present
@@ -3923,6 +3927,13 @@ interface WorktreeGcPlanEntryView {
3923
3927
  * literal `"orphan"` placeholder below rather than a fabricated value.
3924
3928
  */
3925
3929
  orphan?: boolean;
3930
+ /**
3931
+ * `true` only for a linked worktree `git worktree list --porcelain` itself
3932
+ * already marked `prunable`. Same reasoning as `orphan` above — no
3933
+ * tree/integration/liveness axis was ever read for a working directory
3934
+ * that's gone — projected as the literal `"prunable"` placeholder below.
3935
+ */
3936
+ prunable?: boolean;
3926
3937
  tree: string;
3927
3938
  integration: {
3928
3939
  state: string;
@@ -5249,6 +5260,15 @@ interface SessionDescriptor {
5249
5260
  * posture or a raw `{ harnessModeId }` sourced from the harness's ACP mode
5250
5261
  * registry (SPEC §3.4a). */
5251
5262
  posture?: Posture;
5263
+ /**
5264
+ * Read-surface echo of the live harness's advertised ACP session modes
5265
+ * (`SessionModeState.availableModes`, #482) — stamped at READ TIME from the
5266
+ * live agent session by `list()`/`get()`, never persisted. A client posture
5267
+ * picker resolves the native (enforced, live-switchable) rows from this;
5268
+ * absent for arms with no native mode registry (print/proprietary) and for a
5269
+ * session whose live runtime handle is gone.
5270
+ */
5271
+ availableModes?: SessionMode[];
5252
5272
  /** Endpoint / gateway rail (SPEC §3.1 axis 4). `baseUrl` is carried only
5253
5273
  * for a custom gateway the catalog can't resolve; `access` is downstream
5254
5274
  * of this axis (SPEC §1c). */
@@ -6216,6 +6236,7 @@ interface SessionsRegistry {
6216
6236
  */
6217
6237
  listSummaries(opts?: {
6218
6238
  includeArchived?: boolean;
6239
+ lane?: "agents" | "auto";
6219
6240
  limit?: number;
6220
6241
  offset?: number;
6221
6242
  }): {
@@ -7522,14 +7543,17 @@ declare function registerBuiltinRoutes(opts?: {
7522
7543
  * kind-organized catalogs, so NO adapters, NO profiles, NO host wiring.
7523
7544
  * `getModelsByProvider` is itself router-aware: OpenRouter's route table is
7524
7545
  * spread into `LLM_PRICING_CATALOG` upstream (`llm/catalog.ts`), so an
7525
- * `openrouter` query returns that full, large list with bare ids; Requesty
7526
- * and HuggingFace are NOT spread into that catalog — spreading a second
7527
- * router's bare-id pricing there would repoint direct-vendor ids at router
7528
- * pricing (`route-identity/index.ts`) — so `getModelsByProvider` instead
7529
- * folds their generated route tables in directly, emitting `vendor/
7530
- * product@route` ids. All three routers enumerate through the same path;
7531
- * the picker paginates client-side. NEVER log the rows: OpenRouter alone is
7532
- * thousands.
7546
+ * `openrouter` query returns that full, large list with bare ids; Requesty,
7547
+ * HuggingFace and OpenCode's two hosted endpoints (`opencode-go`,
7548
+ * `opencode`) are NOT spread into that catalog — spreading a second router's
7549
+ * bare-id pricing there would repoint direct-vendor ids at router pricing
7550
+ * (`route-identity/index.ts`) — so `getModelsByProvider` instead folds their
7551
+ * generated route tables in directly. The emitted id is whatever the ref
7552
+ * formats to: `vendor/product@route` for the vendor-namespacing routers, and
7553
+ * the bare `opencode-go/glm-5.3` form for the OpenCode endpoints, whose route
7554
+ * IS the id's leading segment. Every provider enumerates through the same
7555
+ * path; the picker paginates client-side. NEVER log the rows: OpenRouter
7556
+ * alone is thousands.
7533
7557
  */
7534
7558
 
7535
7559
  interface CatalogProviderModelsQuery {
package/dist/index.mjs CHANGED
@@ -25,7 +25,7 @@ import { inferLegacyModeKind, parseModelSwitchCommand, isModelSwitchAcknowledgem
25
25
  import { loadSandboxConfig, resolveCommandSandbox, COMMAND_SANDBOX_MODE_ENV } from '@agentproto/command-sandbox';
26
26
  import { createBrainManager, parseKnowledgeConfig } from '@agentproto/workspace-brain';
27
27
  import { defineHttpDriver } from '@agentproto/driver-http';
28
- import { makeSessionsPanelApp, makeAgentsOverviewApp, makeBureauSessionsApp, makeSessionStoryPanelApp, makeLiveSessionApp, makeSessionChatApp, makeWorkBoardApp, SESSION_CHAT_APP_ID, sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, liveSessionApp, sessionChatApp, workBoardApp } from '@agentproto/apps';
28
+ import { liveSessionApp, makeLiveSessionApp, sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, workBoardApp, makeSessionsPanelApp, makeAgentsOverviewApp, makeBureauSessionsApp, makeSessionStoryPanelApp, makeSessionChatApp, makeWorkBoardApp, SESSION_CHAT_APP_ID, sessionChatApp } from '@agentproto/apps';
29
29
  import matter2 from 'gray-matter';
30
30
  import { createServer } from 'http';
31
31
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
@@ -4036,7 +4036,13 @@ function resolveWorktreeIdentity(cwd) {
4036
4036
  dir = parent;
4037
4037
  }
4038
4038
  }
4039
- var WIDENING_ROUTES = ["openrouter", "requesty", "huggingface"];
4039
+ var WIDENING_ROUTES = [
4040
+ "openrouter",
4041
+ "requesty",
4042
+ "huggingface",
4043
+ "opencode-go",
4044
+ "opencode"
4045
+ ];
4040
4046
  var VENDOR_COMPATIBILITY_ROUTES = {
4041
4047
  xai: ["xai", "xai-anthropic"]
4042
4048
  };
@@ -4375,9 +4381,12 @@ function reconcileModelRoute(input) {
4375
4381
  }
4376
4382
  function serviceableModelRoutes(model) {
4377
4383
  const routes = /* @__PURE__ */ new Set();
4378
- const provider = getModelProvider(model);
4379
- if (provider) routes.add(provider);
4380
4384
  const parsed = tryParseModelRef(normalizeRouterPrefixedId(model));
4385
+ const selfRouted = parsed !== void 0 && parsed.route === parsed.vendor && WIDENING_ROUTES.includes(parsed.vendor) && tryResolveLlmModelRoute(`${parsed.vendor}/${parsed.product}`)?.pricing.provider === parsed.vendor;
4386
+ if (!selfRouted) {
4387
+ const provider = getModelProvider(model);
4388
+ if (provider) routes.add(provider);
4389
+ }
4381
4390
  if (parsed) {
4382
4391
  if (parsed.route !== parsed.vendor) routes.add(parsed.route);
4383
4392
  if (resolvePricingExact(parsed.product)?.provider === parsed.vendor) {
@@ -11091,6 +11100,14 @@ function stampInterrupted(desc) {
11091
11100
  delete desc.interrupted;
11092
11101
  }
11093
11102
  }
11103
+ function stampLiveModes(desc, rt) {
11104
+ const modes = rt.agentSession?.availableModes;
11105
+ if (modes && modes.length > 0) {
11106
+ desc.availableModes = [...modes];
11107
+ } else {
11108
+ delete desc.availableModes;
11109
+ }
11110
+ }
11094
11111
  function isResumable(desc) {
11095
11112
  return desc.kind === "agent-cli" && !!desc.adapterSlug && !!desc.adapterSessionId && !!desc.cwd && !desc.archived;
11096
11113
  }
@@ -11525,6 +11542,7 @@ function createSessionsRegistry(opts) {
11525
11542
  secondsSinceLastActivity: _secondsSinceLastActivity,
11526
11543
  toolCallsThisTurn: _toolCallsThisTurn,
11527
11544
  eventsPath: _eventsPath,
11545
+ availableModes: _availableModes,
11528
11546
  ...rest
11529
11547
  } = s.desc;
11530
11548
  return rest;
@@ -13529,6 +13547,7 @@ ${message}`;
13529
13547
  stampInterrupted(desc);
13530
13548
  stampCurrentStatus(rt);
13531
13549
  stampWatchers(desc);
13550
+ stampLiveModes(desc, rt);
13532
13551
  desc.childrenBusy = childrenBusy.get(desc.id) ?? 0;
13533
13552
  desc.queuedPrompts = desc.promptQueue?.length ?? 0;
13534
13553
  return desc;
@@ -13536,10 +13555,30 @@ ${message}`;
13536
13555
  },
13537
13556
  listSummaries(opts2) {
13538
13557
  const includeArchived = opts2?.includeArchived ?? false;
13558
+ const lane = opts2?.lane;
13539
13559
  const limit = Math.max(1, Math.min(200, opts2?.limit ?? 50));
13540
13560
  const offset = Math.max(0, opts2?.offset ?? 0);
13541
13561
  const childrenBusy = childrenBusyCounts();
13542
- const all = Array.from(sessions.values()).filter((rt) => includeArchived || !rt.desc.archived).sort((a, b) => b.desc.startedAt.localeCompare(a.desc.startedAt));
13562
+ const hasMachineLineage = (rt) => {
13563
+ const ownMachine = (desc) => desc.origin === "cron" || desc.origin === "gate";
13564
+ if (ownMachine(rt.desc)) return true;
13565
+ const seen = /* @__PURE__ */ new Set([rt.desc.id]);
13566
+ let current = rt.desc;
13567
+ while (current.parentSessionId) {
13568
+ const parent = sessions.get(current.parentSessionId)?.desc;
13569
+ if (!parent || seen.has(parent.id)) return true;
13570
+ if (parent.kind === "terminal" || parent.kind === "command") return true;
13571
+ seen.add(parent.id);
13572
+ current = parent;
13573
+ }
13574
+ return ownMachine(current);
13575
+ };
13576
+ const all = Array.from(sessions.values()).filter((rt) => includeArchived || !rt.desc.archived).filter((rt) => {
13577
+ if (!lane) return true;
13578
+ if (rt.desc.kind === "terminal" || rt.desc.kind === "command") return false;
13579
+ const machine = hasMachineLineage(rt);
13580
+ return lane === "auto" ? machine : !machine;
13581
+ }).sort((a, b) => b.desc.startedAt.localeCompare(a.desc.startedAt));
13543
13582
  const slice = all.slice(offset, offset + limit);
13544
13583
  const summaries = slice.map((rt) => {
13545
13584
  const desc = rt.desc;
@@ -13561,6 +13600,7 @@ ${message}`;
13561
13600
  stampInterrupted(desc);
13562
13601
  stampCurrentStatus(rt);
13563
13602
  stampWatchers(desc);
13603
+ stampLiveModes(desc, rt);
13564
13604
  desc.childrenBusy = childrenBusyCounts().get(desc.id) ?? 0;
13565
13605
  desc.queuedPrompts = desc.promptQueue?.length ?? 0;
13566
13606
  }
@@ -20343,6 +20383,18 @@ var PANEL_APP_HANDLES = [
20343
20383
  sessionChatApp,
20344
20384
  workBoardApp
20345
20385
  ];
20386
+ function resolveBuiltinPanelUi(appId, httpBaseUrl) {
20387
+ if (appId === liveSessionApp.id) {
20388
+ const app = makeLiveSessionApp({ httpBaseUrl });
20389
+ const html = typeof app.html === "function" ? app.html({ httpBaseUrl }) : app.html;
20390
+ return { html, tools: liveSessionApp.ui?.tools ?? [] };
20391
+ }
20392
+ const handle = [sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, workBoardApp].find(
20393
+ (h) => h.id === appId
20394
+ );
20395
+ if (!handle?.ui) return void 0;
20396
+ return { html: handle.ui.html, tools: handle.ui.tools ?? [] };
20397
+ }
20346
20398
  function builtinPanelCatalogEntries() {
20347
20399
  const apps = makeBuiltinPanelApps({
20348
20400
  listSessions: () => [],
@@ -21953,12 +22005,7 @@ function notEnabled(tool) {
21953
22005
  `${tool} is not enabled \u2014 the daemon was started without an adapter resolver. Re-run the daemon with the \`@agentproto/cli\` shim wired (see playground/scripts/gateway.ts).`
21954
22006
  );
21955
22007
  }
21956
- async function performAppToolCall(appRegistry, input, deps2) {
21957
- const installed = appRegistry.getApp(input.appId);
21958
- if (!installed || !installed.ui) {
21959
- return errorResult2(`app_tool_call: app "${input.appId}" is not installed or has no UI.`);
21960
- }
21961
- const declaredAllowlist = installed.ui.tools ?? [];
22008
+ async function dispatchAllowlistedAppTool(declaredAllowlist, input, deps2) {
21962
22009
  const effectiveAllowlist = [...declaredAllowlist, ...APP_UI_DISCOVERY_TOOLS];
21963
22010
  if (!effectiveAllowlist.includes(input.tool)) {
21964
22011
  return errorResult2(
@@ -21986,6 +22033,19 @@ async function performAppToolCall(appRegistry, input, deps2) {
21986
22033
  return errorResult2(`app_tool_call: ${err instanceof Error ? err.message : String(err)}`);
21987
22034
  }
21988
22035
  }
22036
+ async function performAppToolCall(appRegistry, input, deps2) {
22037
+ const installed = appRegistry.getApp(input.appId);
22038
+ if (!installed || !installed.ui) {
22039
+ return errorResult2(`app_tool_call: app "${input.appId}" is not installed or has no UI.`);
22040
+ }
22041
+ return dispatchAllowlistedAppTool(installed.ui.tools ?? [], input, deps2);
22042
+ }
22043
+ async function performBuiltinPanelToolCall(tools, input, deps2) {
22044
+ if (tools === void 0) {
22045
+ return errorResult2(`app_tool_call: app "${input.appId}" is not installed or has no UI.`);
22046
+ }
22047
+ return dispatchAllowlistedAppTool(tools, input, deps2);
22048
+ }
21989
22049
  function refIdOf(ref) {
21990
22050
  if (typeof ref === "string") return ref;
21991
22051
  return ref.ref ?? ref.file ?? "inline";
@@ -22871,7 +22931,7 @@ var STANDALONE_REST_BRIDGE_SCRIPT = `<script>
22871
22931
  return res.json().catch(function () {
22872
22932
  throw new Error("tool-call failed: HTTP " + res.status);
22873
22933
  }).then(function (body) {
22874
- if (!res.ok) throw new Error((body && body.error) || ("tool-call failed: HTTP " + res.status));
22934
+ if (!res.ok) throw new Error((body && (body.message || body.error)) || ("tool-call failed: HTTP " + res.status));
22875
22935
  return body;
22876
22936
  });
22877
22937
  });
@@ -26688,6 +26748,7 @@ var DEFAULT_ALLOWED_ORIGINS = [
26688
26748
  // matching how guilde.work is trusted. Drop it via `strictOrigins`.
26689
26749
  "https://cli.agentproto.sh"
26690
26750
  ];
26751
+ var DEFAULT_FRAME_ANCESTORS = ["vscode-webview:"];
26691
26752
  var PROXY_FORWARDING_HEADERS = [
26692
26753
  "x-forwarded-for",
26693
26754
  "forwarded",
@@ -28050,7 +28111,8 @@ async function startHttpServer(opts) {
28050
28111
  req,
28051
28112
  res,
28052
28113
  decodeURIComponent(uiMatch[1]),
28053
- opts.appRegistry
28114
+ opts.appRegistry,
28115
+ [...DEFAULT_FRAME_ANCESTORS, ...opts.frameAncestors ?? []]
28054
28116
  );
28055
28117
  return;
28056
28118
  }
@@ -28601,7 +28663,9 @@ function parseMcpServersField(raw) {
28601
28663
  servers.push({
28602
28664
  name: o.name,
28603
28665
  transport: o.transport,
28604
- ...typeof o.ref === "string" ? { ref: o.ref } : {}
28666
+ ...typeof o.ref === "string" ? { ref: o.ref } : {},
28667
+ ...isStringRecord(o.headers) ? { headers: o.headers } : {},
28668
+ ...typeof o.credentialRef === "string" ? { credentialRef: o.credentialRef } : {}
28605
28669
  });
28606
28670
  }
28607
28671
  return servers;
@@ -28795,10 +28859,13 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
28795
28859
  const queryString = reqUrl.includes("?") ? reqUrl.slice(reqUrl.indexOf("?") + 1) : "";
28796
28860
  const params = new URLSearchParams(queryString);
28797
28861
  const includeArchived = params.get("includeArchived") === "true";
28862
+ const laneParam = params.get("lane");
28863
+ const lane = laneParam === "agents" || laneParam === "auto" ? laneParam : void 0;
28798
28864
  const limit = Number.parseInt(params.get("limit") ?? "", 10);
28799
28865
  const offset = Number.parseInt(params.get("offset") ?? "", 10);
28800
28866
  const result = registry.listSummaries({
28801
28867
  includeArchived,
28868
+ lane,
28802
28869
  limit: Number.isNaN(limit) ? void 0 : limit,
28803
28870
  offset: Number.isNaN(offset) ? void 0 : offset
28804
28871
  });
@@ -30749,33 +30816,43 @@ async function handleProviderInbound(req, res, slug, deps2) {
30749
30816
  res.writeHead(200, { "content-type": "application/json" });
30750
30817
  res.end(JSON.stringify(result));
30751
30818
  }
30752
- async function handleAppUiPage(req, res, appId, appRegistry) {
30819
+ function requestHttpBaseUrl(req) {
30820
+ return `http://${req.headers.host ?? "127.0.0.1"}`;
30821
+ }
30822
+ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
30753
30823
  const app = appRegistry.getApp(appId);
30754
- if (!app?.ui) {
30824
+ const builtin = app?.ui ? void 0 : resolveBuiltinPanelUi(appId, requestHttpBaseUrl(req));
30825
+ if (!app?.ui && !builtin) {
30755
30826
  res.writeHead(404, { "content-type": "application/json" });
30756
30827
  res.end(JSON.stringify({ error: `app "${appId}" is not installed or has no UI.` }));
30757
30828
  return;
30758
30829
  }
30759
30830
  let raw;
30760
- try {
30761
- raw = await readFile(app.ui.path, "utf8");
30762
- } catch (err) {
30763
- res.writeHead(500, { "content-type": "application/json" });
30764
- res.end(
30765
- JSON.stringify({
30766
- error: `could not read app "${appId}"'s ui html at "${app.ui.path}": ${err instanceof Error ? err.message : String(err)}`
30767
- })
30768
- );
30769
- return;
30831
+ if (app?.ui) {
30832
+ try {
30833
+ raw = await readFile(app.ui.path, "utf8");
30834
+ } catch (err) {
30835
+ res.writeHead(500, { "content-type": "application/json" });
30836
+ res.end(
30837
+ JSON.stringify({
30838
+ error: `could not read app "${appId}"'s ui html at "${app.ui.path}": ${err instanceof Error ? err.message : String(err)}`
30839
+ })
30840
+ );
30841
+ return;
30842
+ }
30843
+ } else {
30844
+ raw = builtin.html;
30770
30845
  }
30771
30846
  const embedRequested = new URL(req.url ?? "/", "http://localhost").searchParams.get("embed") === "1";
30772
30847
  const headers = {
30773
30848
  "content-type": "text/html; charset=utf-8",
30774
30849
  "cache-control": "no-store"
30775
30850
  };
30776
- if (!(embedRequested && iframeEmbedAllowed(req, app))) {
30777
- headers["x-frame-options"] = "DENY";
30778
- headers["content-security-policy"] = "frame-ancestors 'none'";
30851
+ if (!(embedRequested && iframeEmbedAllowed(req, app ?? {}))) {
30852
+ headers["content-security-policy"] = `frame-ancestors 'self' ${frameAncestors.join(" ")}`.trimEnd();
30853
+ if (frameAncestors.length === 0) {
30854
+ headers["x-frame-options"] = "SAMEORIGIN";
30855
+ }
30779
30856
  }
30780
30857
  res.writeHead(200, headers);
30781
30858
  res.end(injectStandaloneAppBridge(raw));
@@ -30911,7 +30988,12 @@ async function handleAppUiToolCall(req, res, appId, appRegistry, deps2) {
30911
30988
  tool = inner.tool;
30912
30989
  args = inner.args && typeof inner.args === "object" && !Array.isArray(inner.args) ? inner.args : {};
30913
30990
  }
30914
- const result = await performAppToolCall(appRegistry, { appId, tool, args }, deps2);
30991
+ const installed = appRegistry.getApp(appId);
30992
+ const result = installed?.ui ? await performAppToolCall(appRegistry, { appId, tool, args }, deps2) : await performBuiltinPanelToolCall(
30993
+ resolveBuiltinPanelUi(appId, requestHttpBaseUrl(req))?.tools,
30994
+ { appId, tool, args },
30995
+ deps2
30996
+ );
30915
30997
  res.writeHead(200, { "content-type": "application/json" });
30916
30998
  res.end(JSON.stringify(result));
30917
30999
  }