@agentproto/runtime 5.10.0 → 5.11.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/app-catalog.d.ts +162 -0
- package/dist/app-catalog.mjs +250 -0
- package/dist/app-catalog.mjs.map +1 -0
- package/dist/app-registry-B64vZEaO.d.ts +35 -0
- package/dist/app-remote-install.d.ts +97 -0
- package/dist/app-remote-install.mjs +281 -0
- package/dist/app-remote-install.mjs.map +1 -0
- package/dist/first-party-catalog-gen.d.ts +47 -0
- package/dist/first-party-catalog-gen.mjs +116 -0
- package/dist/first-party-catalog-gen.mjs.map +1 -0
- package/dist/index.d.ts +10 -11
- package/dist/index.mjs +389 -30
- package/dist/index.mjs.map +1 -1
- package/dist/{sessions-BdgU9X6P.d.ts → sessions-Cv4XNQXv.d.ts} +19 -2
- package/dist/user-presets.d.ts +1 -1
- package/docs/mcp-tools/agent_start.md +5 -2
- package/package.json +34 -18
package/dist/index.mjs
CHANGED
|
@@ -30,7 +30,7 @@ import { APP_UI_DISCOVERY_TOOLS, RUNNER_SELECT_SCRIPT } from '@agentproto/app-cl
|
|
|
30
30
|
import { DISPLAY_MODE_SCRIPT } from '@agentproto/app-client/display-mode';
|
|
31
31
|
import { defineTool, catchErrors, ToolError, pageParamsShape, paginated, toToolResult, paginate, toolText } from '@agentproto/tool';
|
|
32
32
|
import { toMcpTool, createMcpServer, registerBuiltinTool, registerUiResource, registerEventsMethods } from '@agentproto/mcp-server';
|
|
33
|
-
import { SESSION_CHAT_APP_ID, liveSessionApp, makeLiveSessionApp, sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, workBoardApp, reviewPanelApp, makeSessionsPanelApp, makeAgentsOverviewApp, makeBureauSessionsApp, makeSessionStoryPanelApp, makeSessionChatApp, makeWorkBoardApp, makeReviewPanelApp, sessionChatApp } from '@agentproto/apps';
|
|
33
|
+
import { storeApp, SESSION_CHAT_APP_ID, liveSessionApp, makeLiveSessionApp, sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, workBoardApp, reviewPanelApp, makeSessionsPanelApp, makeAgentsOverviewApp, makeBureauSessionsApp, makeSessionStoryPanelApp, makeSessionChatApp, makeWorkBoardApp, makeReviewPanelApp, makeStoreApp, sessionChatApp } from '@agentproto/apps';
|
|
34
34
|
import { EventEmitter } from 'events';
|
|
35
35
|
import { inferLegacyModeKind, parseModelSwitchCommand, isModelSwitchAcknowledgement } from '@agentproto/driver-agent-cli';
|
|
36
36
|
import { lookup } from 'dns/promises';
|
|
@@ -27928,6 +27928,185 @@ init_context_continuity();
|
|
|
27928
27928
|
// src/session-capabilities.ts
|
|
27929
27929
|
init_canonical_posture();
|
|
27930
27930
|
init_conversation_store();
|
|
27931
|
+
|
|
27932
|
+
// src/mcp-session-observer.ts
|
|
27933
|
+
var OBSERVED_METHODS = /* @__PURE__ */ new Set([
|
|
27934
|
+
"initialize",
|
|
27935
|
+
"server/discover",
|
|
27936
|
+
"tools/list"
|
|
27937
|
+
]);
|
|
27938
|
+
var MAX_DESCRIPTION_CHARS = 200;
|
|
27939
|
+
var DEFAULT_MAX_SESSIONS = 500;
|
|
27940
|
+
function createMcpObservationStore(opts = {}) {
|
|
27941
|
+
const maxSessions = opts.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
27942
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
27943
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
27944
|
+
const touch = (sessionId) => {
|
|
27945
|
+
const at = now().toISOString();
|
|
27946
|
+
let obs = bySession.get(sessionId);
|
|
27947
|
+
if (!obs) {
|
|
27948
|
+
obs = { firstSeenAt: at, lastSeenAt: at };
|
|
27949
|
+
bySession.set(sessionId, obs);
|
|
27950
|
+
if (bySession.size > maxSessions) {
|
|
27951
|
+
const oldest = bySession.keys().next().value;
|
|
27952
|
+
if (oldest !== void 0) bySession.delete(oldest);
|
|
27953
|
+
}
|
|
27954
|
+
}
|
|
27955
|
+
obs.lastSeenAt = at;
|
|
27956
|
+
return obs;
|
|
27957
|
+
};
|
|
27958
|
+
const record2 = (sessionId, method, message, clientProtocolVersion) => {
|
|
27959
|
+
const obs = touch(sessionId);
|
|
27960
|
+
const at = obs.lastSeenAt;
|
|
27961
|
+
const result = message.result && typeof message.result === "object" ? message.result : void 0;
|
|
27962
|
+
const isExpectedDiscoverMiss = method === "server/discover" && message.error?.code === -32601;
|
|
27963
|
+
const error = message.error && !isExpectedDiscoverMiss ? `${String(message.error.code ?? "error")}: ${String(message.error.message ?? "")}` : void 0;
|
|
27964
|
+
const answeredVersion = typeof result?.["protocolVersion"] === "string" ? result["protocolVersion"] : void 0;
|
|
27965
|
+
const protocolVersion = method === "initialize" ? answeredVersion : clientProtocolVersion;
|
|
27966
|
+
const base = {
|
|
27967
|
+
at,
|
|
27968
|
+
ok: error === void 0,
|
|
27969
|
+
...error !== void 0 ? { error } : {},
|
|
27970
|
+
...protocolVersion ? { protocolVersion } : {}
|
|
27971
|
+
};
|
|
27972
|
+
if (method === "initialize") obs.initialize = base;
|
|
27973
|
+
else if (method === "server/discover") obs.discover = base;
|
|
27974
|
+
else {
|
|
27975
|
+
const rawTools = Array.isArray(result?.["tools"]) ? result["tools"] : [];
|
|
27976
|
+
const tools = [];
|
|
27977
|
+
for (const t of rawTools) {
|
|
27978
|
+
if (!t || typeof t !== "object") continue;
|
|
27979
|
+
const name = t.name;
|
|
27980
|
+
if (typeof name !== "string") continue;
|
|
27981
|
+
const description = t.description;
|
|
27982
|
+
tools.push({
|
|
27983
|
+
name,
|
|
27984
|
+
...typeof description === "string" && description.length > 0 ? { description: description.slice(0, MAX_DESCRIPTION_CHARS) } : {}
|
|
27985
|
+
});
|
|
27986
|
+
}
|
|
27987
|
+
obs.toolsList = {
|
|
27988
|
+
...base,
|
|
27989
|
+
toolCount: tools.length,
|
|
27990
|
+
deferred: tools.some((t) => t.name === "tool_search"),
|
|
27991
|
+
tools
|
|
27992
|
+
};
|
|
27993
|
+
}
|
|
27994
|
+
};
|
|
27995
|
+
return {
|
|
27996
|
+
observe(transport, ctx) {
|
|
27997
|
+
const pending = /* @__PURE__ */ new Map();
|
|
27998
|
+
const innerOnMessage = transport.onmessage;
|
|
27999
|
+
transport.onmessage = (message, extra) => {
|
|
28000
|
+
try {
|
|
28001
|
+
if ("method" in message && "id" in message && message.id !== void 0 && OBSERVED_METHODS.has(message.method)) {
|
|
28002
|
+
pending.set(message.id, message.method);
|
|
28003
|
+
}
|
|
28004
|
+
} catch {
|
|
28005
|
+
}
|
|
28006
|
+
innerOnMessage?.(message, extra);
|
|
28007
|
+
};
|
|
28008
|
+
const innerSend = transport.send.bind(transport);
|
|
28009
|
+
transport.send = async (message, options) => {
|
|
28010
|
+
try {
|
|
28011
|
+
if ("id" in message && message.id !== void 0 && ("result" in message || "error" in message)) {
|
|
28012
|
+
const method = pending.get(message.id);
|
|
28013
|
+
if (method) {
|
|
28014
|
+
pending.delete(message.id);
|
|
28015
|
+
record2(ctx.sessionId, method, message, ctx.clientProtocolVersion);
|
|
28016
|
+
}
|
|
28017
|
+
}
|
|
28018
|
+
} catch {
|
|
28019
|
+
}
|
|
28020
|
+
return innerSend(message, options);
|
|
28021
|
+
};
|
|
28022
|
+
},
|
|
28023
|
+
recordHttpError(sessionId, status) {
|
|
28024
|
+
const obs = touch(sessionId);
|
|
28025
|
+
obs.httpError = { at: obs.lastSeenAt, status };
|
|
28026
|
+
},
|
|
28027
|
+
get: (sessionId) => bySession.get(sessionId),
|
|
28028
|
+
delete: (sessionId) => {
|
|
28029
|
+
bySession.delete(sessionId);
|
|
28030
|
+
}
|
|
28031
|
+
};
|
|
28032
|
+
}
|
|
28033
|
+
function isDaemonMountFor(sessionId, entry) {
|
|
28034
|
+
if (entry.transport !== "http" || typeof entry.ref !== "string") return false;
|
|
28035
|
+
try {
|
|
28036
|
+
const url = new URL(entry.ref);
|
|
28037
|
+
return url.pathname === "/mcp" && url.searchParams.get("callerSessionId") === sessionId;
|
|
28038
|
+
} catch {
|
|
28039
|
+
return false;
|
|
28040
|
+
}
|
|
28041
|
+
}
|
|
28042
|
+
function daemonMountStatus(obs, turnsCompleted) {
|
|
28043
|
+
if (!obs) return (turnsCompleted ?? 0) > 0 ? "never-contacted" : "declared";
|
|
28044
|
+
if (obs.toolsList) return obs.toolsList.ok ? "listed" : "error";
|
|
28045
|
+
if (obs.initialize?.ok === false || obs.discover?.ok === false || obs.httpError) return "error";
|
|
28046
|
+
if (obs.initialize || obs.discover) return "connected";
|
|
28047
|
+
return (turnsCompleted ?? 0) > 0 ? "never-contacted" : "declared";
|
|
28048
|
+
}
|
|
28049
|
+
function assessDaemonMount(obs) {
|
|
28050
|
+
const tl = obs?.toolsList;
|
|
28051
|
+
if (tl) {
|
|
28052
|
+
if (!tl.ok) return { reason: "error", ...tl.error ? { detail: tl.error } : {} };
|
|
28053
|
+
if (tl.toolCount === 0) return { reason: "zero-tools" };
|
|
28054
|
+
return void 0;
|
|
28055
|
+
}
|
|
28056
|
+
if (obs?.initialize?.ok === false) {
|
|
28057
|
+
return { reason: "error", ...obs.initialize.error ? { detail: `initialize ${obs.initialize.error}` } : {} };
|
|
28058
|
+
}
|
|
28059
|
+
if (obs?.discover?.ok === false) {
|
|
28060
|
+
return { reason: "error", ...obs.discover.error ? { detail: `server/discover ${obs.discover.error}` } : {} };
|
|
28061
|
+
}
|
|
28062
|
+
if (obs?.httpError) return { reason: "error", detail: `HTTP ${obs.httpError.status}` };
|
|
28063
|
+
return {
|
|
28064
|
+
reason: "never-listed",
|
|
28065
|
+
...obs?.initialize || obs?.discover ? { detail: "handshake seen but no tools/list" } : {}
|
|
28066
|
+
};
|
|
28067
|
+
}
|
|
28068
|
+
var ABNORMAL_TURN_REASONS = /* @__PURE__ */ new Set(["error", "cancelled", "watchdog-timeout", "aborted"]);
|
|
28069
|
+
function wireMcpDegradedWarnings(opts) {
|
|
28070
|
+
const lastEmitted = /* @__PURE__ */ new Map();
|
|
28071
|
+
const offTurnEnd = opts.sessionEvents.on("session:turn-end", (ev) => {
|
|
28072
|
+
if (ev.reason !== void 0 && ABNORMAL_TURN_REASONS.has(ev.reason)) return;
|
|
28073
|
+
const desc = opts.getSession(ev.sessionId);
|
|
28074
|
+
if (!desc || desc.kind !== "agent-cli") return;
|
|
28075
|
+
const mount = (desc.mcpServers ?? []).find((s) => isDaemonMountFor(ev.sessionId, s));
|
|
28076
|
+
if (!mount) return;
|
|
28077
|
+
const assessment = assessDaemonMount(opts.store.get(ev.sessionId));
|
|
28078
|
+
if (!assessment) {
|
|
28079
|
+
lastEmitted.delete(ev.sessionId);
|
|
28080
|
+
return;
|
|
28081
|
+
}
|
|
28082
|
+
const key = `${assessment.reason}|${assessment.detail ?? ""}`;
|
|
28083
|
+
if (lastEmitted.get(ev.sessionId) === key) return;
|
|
28084
|
+
lastEmitted.set(ev.sessionId, key);
|
|
28085
|
+
opts.sessionEvents.emit({
|
|
28086
|
+
type: "mcp:degraded",
|
|
28087
|
+
sessionId: ev.sessionId,
|
|
28088
|
+
server: mount.name,
|
|
28089
|
+
reason: assessment.reason,
|
|
28090
|
+
...assessment.detail ? { detail: assessment.detail } : {},
|
|
28091
|
+
...desc.label ? { label: desc.label } : {},
|
|
28092
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
28093
|
+
});
|
|
28094
|
+
});
|
|
28095
|
+
const offExit = opts.sessionEvents.on("session:exited", (ev) => {
|
|
28096
|
+
lastEmitted.delete(ev.sessionId);
|
|
28097
|
+
});
|
|
28098
|
+
const offResumed = opts.sessionEvents.on("session:resumed", (ev) => {
|
|
28099
|
+
lastEmitted.delete(ev.sessionId);
|
|
28100
|
+
opts.store.delete(ev.sessionId);
|
|
28101
|
+
});
|
|
28102
|
+
return () => {
|
|
28103
|
+
offTurnEnd();
|
|
28104
|
+
offExit();
|
|
28105
|
+
offResumed();
|
|
28106
|
+
};
|
|
28107
|
+
}
|
|
28108
|
+
|
|
28109
|
+
// src/session-capabilities.ts
|
|
27931
28110
|
var SKILLS_APPLYING_ADAPTERS = /* @__PURE__ */ new Set(["hermes"]);
|
|
27932
28111
|
function sessionArm(desc) {
|
|
27933
28112
|
if (desc.kind === "terminal") return "pty";
|
|
@@ -27936,7 +28115,7 @@ function sessionArm(desc) {
|
|
|
27936
28115
|
}
|
|
27937
28116
|
return "other";
|
|
27938
28117
|
}
|
|
27939
|
-
function buildSessionCapabilities(desc, pendingPermissions) {
|
|
28118
|
+
function buildSessionCapabilities(desc, pendingPermissions, mcpObservation) {
|
|
27940
28119
|
return {
|
|
27941
28120
|
sessionId: desc.id,
|
|
27942
28121
|
adapter: desc.adapterSlug ?? conversationTerminalSlugFor(desc) ?? "unknown",
|
|
@@ -27959,7 +28138,8 @@ function buildSessionCapabilities(desc, pendingPermissions) {
|
|
|
27959
28138
|
mcpServers: (desc.mcpServers ?? []).map((s) => ({
|
|
27960
28139
|
name: s.name,
|
|
27961
28140
|
transport: s.transport,
|
|
27962
|
-
...s.ref ? { ref: s.ref } : {}
|
|
28141
|
+
...s.ref ? { ref: s.ref } : {},
|
|
28142
|
+
...isDaemonMountFor(desc.id, s) ? daemonMountFields(mcpObservation, desc.turnsCompleted) : { status: "declared" }
|
|
27963
28143
|
})),
|
|
27964
28144
|
skills: desc.skills ?? [],
|
|
27965
28145
|
skillsApplied: desc.adapterSlug !== void 0 && SKILLS_APPLYING_ADAPTERS.has(desc.adapterSlug),
|
|
@@ -27967,6 +28147,20 @@ function buildSessionCapabilities(desc, pendingPermissions) {
|
|
|
27967
28147
|
pendingPermissions
|
|
27968
28148
|
};
|
|
27969
28149
|
}
|
|
28150
|
+
function daemonMountFields(obs, turnsCompleted) {
|
|
28151
|
+
const status = daemonMountStatus(obs, turnsCompleted);
|
|
28152
|
+
const tl = obs?.toolsList;
|
|
28153
|
+
const failing = tl ? tl.ok ? void 0 : tl : [obs?.initialize, obs?.discover].find((c) => c && !c.ok);
|
|
28154
|
+
const error = failing?.error ?? (obs?.httpError ? `HTTP ${obs.httpError.status}` : void 0);
|
|
28155
|
+
const protocolVersion = tl?.protocolVersion ?? obs?.initialize?.protocolVersion ?? obs?.discover?.protocolVersion;
|
|
28156
|
+
return {
|
|
28157
|
+
status,
|
|
28158
|
+
...tl?.ok ? { toolCount: tl.toolCount, tools: tl.tools, deferred: tl.deferred } : {},
|
|
28159
|
+
...protocolVersion ? { protocolVersion } : {},
|
|
28160
|
+
...obs ? { lastSeenAt: obs.lastSeenAt } : {},
|
|
28161
|
+
...error ? { error } : {}
|
|
28162
|
+
};
|
|
28163
|
+
}
|
|
27970
28164
|
|
|
27971
28165
|
// src/session-tools.ts
|
|
27972
28166
|
init_context_checkpoint();
|
|
@@ -30769,7 +30963,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
30769
30963
|
const pendingPermissions = registry.listPendingPermissions({ sessionId: fresh.id }).length;
|
|
30770
30964
|
return {
|
|
30771
30965
|
content: [
|
|
30772
|
-
{ type: "text", text: JSON.stringify(buildSessionCapabilities(fresh, pendingPermissions)) }
|
|
30966
|
+
{ type: "text", text: JSON.stringify(buildSessionCapabilities(fresh, pendingPermissions, opts.mcpObservations?.get(fresh.id))) }
|
|
30773
30967
|
]
|
|
30774
30968
|
};
|
|
30775
30969
|
}
|
|
@@ -36193,6 +36387,20 @@ function registerMcpApps(server, apps) {
|
|
|
36193
36387
|
);
|
|
36194
36388
|
}
|
|
36195
36389
|
}
|
|
36390
|
+
|
|
36391
|
+
// src/store-panel-allowlist.ts
|
|
36392
|
+
var STORE_UI_TOOLS = [
|
|
36393
|
+
"app_catalog",
|
|
36394
|
+
"app_list",
|
|
36395
|
+
"app_install",
|
|
36396
|
+
"app_resync",
|
|
36397
|
+
"app_updates",
|
|
36398
|
+
"app_uninstall",
|
|
36399
|
+
"app_status"
|
|
36400
|
+
];
|
|
36401
|
+
|
|
36402
|
+
// src/builtin-apps.ts
|
|
36403
|
+
var STORE_PANEL_APP_ID = storeApp.id ?? "@agentproto/store";
|
|
36196
36404
|
function makeBuiltinPanelApps(ops) {
|
|
36197
36405
|
const sessionChatNativeInstalled = ops.isSessionChatInstalled();
|
|
36198
36406
|
return [
|
|
@@ -36236,7 +36444,18 @@ function makeBuiltinPanelApps(ops) {
|
|
|
36236
36444
|
// building `makeBuiltinPanelApps` without one — e.g.
|
|
36237
36445
|
// `builtinPanelCatalogEntries`'s stub ops below — still gets a
|
|
36238
36446
|
// deterministic, small app list).
|
|
36239
|
-
...ops.listReviews ? [makeReviewPanelApp({ listReviews: ops.listReviews })] : []
|
|
36447
|
+
...ops.listReviews ? [makeReviewPanelApp({ listReviews: ops.listReviews })] : [],
|
|
36448
|
+
// App Store widget — the browser/launcher over app_catalog + installed
|
|
36449
|
+
// apps (see apps/src/store). Initial snapshot only when the ops are
|
|
36450
|
+
// wired; writes (install/update/uninstall) always go through the SAME
|
|
36451
|
+
// MCP verbs every other caller uses — no second install path.
|
|
36452
|
+
...ops.listCatalog && ops.listInstalled && ops.listUpdates ? [
|
|
36453
|
+
makeStoreApp({
|
|
36454
|
+
listCatalog: ops.listCatalog,
|
|
36455
|
+
listInstalled: ops.listInstalled,
|
|
36456
|
+
listUpdates: ops.listUpdates
|
|
36457
|
+
})
|
|
36458
|
+
] : []
|
|
36240
36459
|
];
|
|
36241
36460
|
}
|
|
36242
36461
|
var PANEL_APP_HANDLES = [
|
|
@@ -36247,7 +36466,8 @@ var PANEL_APP_HANDLES = [
|
|
|
36247
36466
|
liveSessionApp,
|
|
36248
36467
|
sessionChatApp,
|
|
36249
36468
|
workBoardApp,
|
|
36250
|
-
reviewPanelApp
|
|
36469
|
+
reviewPanelApp,
|
|
36470
|
+
storeApp
|
|
36251
36471
|
];
|
|
36252
36472
|
var DEEP_LINK_SESSION_ID_RE = /^sess_[A-Za-z0-9_-]+$/;
|
|
36253
36473
|
function isValidDeepLinkSessionId(id) {
|
|
@@ -36260,6 +36480,9 @@ function resolveBuiltinPanelUi(appId, httpBaseUrl, sessionId) {
|
|
|
36260
36480
|
const html = typeof app.html === "function" ? app.html({ httpBaseUrl, sessionId: pinnedSessionId }) : app.html;
|
|
36261
36481
|
return { html, tools: liveSessionApp.ui?.tools ?? [] };
|
|
36262
36482
|
}
|
|
36483
|
+
if (appId === storeApp.id) {
|
|
36484
|
+
return { html: storeApp.ui?.html ?? "", tools: STORE_UI_TOOLS };
|
|
36485
|
+
}
|
|
36263
36486
|
const handle = [sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, workBoardApp, reviewPanelApp].find(
|
|
36264
36487
|
(h) => h.id === appId
|
|
36265
36488
|
);
|
|
@@ -36274,10 +36497,14 @@ function builtinPanelCatalogEntries() {
|
|
|
36274
36497
|
listTasks: (boardId) => ({ boardId: boardId ?? "ws:default", tasks: [] }),
|
|
36275
36498
|
// Always provided (never omitted) here, same reasoning as
|
|
36276
36499
|
// `isSessionChatInstalled: () => false` above: `PANEL_APP_HANDLES` is
|
|
36277
|
-
// zipped against this call's output BY INDEX, so the review panel
|
|
36278
|
-
// always be present in this specific call regardless of
|
|
36279
|
-
// real daemon happens to have a review runner wired
|
|
36280
|
-
|
|
36500
|
+
// zipped against this call's output BY INDEX, so the review panel and
|
|
36501
|
+
// the store must always be present in this specific call regardless of
|
|
36502
|
+
// whether the real daemon happens to have a review runner wired or any
|
|
36503
|
+
// store ops at all.
|
|
36504
|
+
listReviews: async () => ({ total: 0, attestations: [] }),
|
|
36505
|
+
listCatalog: async () => [],
|
|
36506
|
+
listInstalled: () => [],
|
|
36507
|
+
listUpdates: () => []
|
|
36281
36508
|
});
|
|
36282
36509
|
return apps.map((app, i) => {
|
|
36283
36510
|
const handle = PANEL_APP_HANDLES[i];
|
|
@@ -45778,7 +46005,7 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
45778
46005
|
"Opaque cursor from a prior call's `nextCursor`. Omit (or pass 0) to start from the current tail \u2014 returns events emitted after this call."
|
|
45779
46006
|
),
|
|
45780
46007
|
sessionIds: z.array(z.string()).optional().describe("Filter to these session ids. Omit \u2192 all sessions."),
|
|
45781
|
-
types: z.array(z.enum(["turn-end", "awaiting-input", "permission-request", "permission-resolved", "exited", "session:spawned", "command-done", "policy:passed", "policy:failed", "policy:commit-ready", "policy:committed", "cron:fired", "cron:succeeded", "cron:failed", "cron:unhealthy", "activity:changed", "task:changed"])).optional().describe("Filter to these event types. Omit \u2192 all types."),
|
|
46008
|
+
types: z.array(z.enum(["turn-end", "awaiting-input", "permission-request", "permission-resolved", "exited", "session:spawned", "command-done", "policy:passed", "policy:failed", "policy:commit-ready", "policy:committed", "cron:fired", "cron:succeeded", "cron:failed", "cron:unhealthy", "activity:changed", "task:changed", "mcp:degraded"])).optional().describe("Filter to these event types. Omit \u2192 all types."),
|
|
45782
46009
|
limit: z.number().int().min(1).max(200).optional().describe("Max events to return. Default 50."),
|
|
45783
46010
|
full: z.boolean().optional().describe(
|
|
45784
46011
|
"Request verbose per-event detail. Accepted for forward compatibility \u2014 this build always returns the same compact event shape regardless of the flag. Default false."
|
|
@@ -47332,7 +47559,7 @@ var RemoteCatalogSchema = z.object({
|
|
|
47332
47559
|
generatedAt: z.string().optional(),
|
|
47333
47560
|
entries: z.array(z.unknown())
|
|
47334
47561
|
});
|
|
47335
|
-
var DEFAULT_CATALOG_SOURCE_URL = "https://
|
|
47562
|
+
var DEFAULT_CATALOG_SOURCE_URL = "https://agentproto.sh/catalog/v1/apps.json";
|
|
47336
47563
|
var EMPTY_CATALOG = { apps: [] };
|
|
47337
47564
|
function defaultAppCatalogPath() {
|
|
47338
47565
|
return join(homedir(), ".agentproto", "app-catalog.json");
|
|
@@ -47655,6 +47882,82 @@ function notEnabled(tool) {
|
|
|
47655
47882
|
`${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).`
|
|
47656
47883
|
);
|
|
47657
47884
|
}
|
|
47885
|
+
function confirmTokenOf(args) {
|
|
47886
|
+
const digest = createHash("sha256");
|
|
47887
|
+
digest.update(
|
|
47888
|
+
JSON.stringify([
|
|
47889
|
+
"agentproto-app-install-confirm-v1",
|
|
47890
|
+
args.url,
|
|
47891
|
+
isAgentappUrl(args.url) ? "agentapp" : "git",
|
|
47892
|
+
args.sha256 ?? null,
|
|
47893
|
+
args.sha ?? null,
|
|
47894
|
+
args.ref ?? null,
|
|
47895
|
+
args.subdir ?? null,
|
|
47896
|
+
args.allowBuild === true
|
|
47897
|
+
])
|
|
47898
|
+
);
|
|
47899
|
+
return digest.digest("hex").slice(0, 32);
|
|
47900
|
+
}
|
|
47901
|
+
var appInstallNonces = /* @__PURE__ */ new Map();
|
|
47902
|
+
var APP_INSTALL_NONCE_MAX = 256;
|
|
47903
|
+
var APP_INSTALL_NONCE_TTL_MS = 5 * 60 * 1e3;
|
|
47904
|
+
function issueInstallNonce(args) {
|
|
47905
|
+
const now = Date.now();
|
|
47906
|
+
for (const [nonce2, entry] of appInstallNonces) {
|
|
47907
|
+
if (entry.expiresAt <= now) appInstallNonces.delete(nonce2);
|
|
47908
|
+
}
|
|
47909
|
+
if (appInstallNonces.size >= APP_INSTALL_NONCE_MAX) {
|
|
47910
|
+
const oldest = appInstallNonces.keys().next().value;
|
|
47911
|
+
if (oldest !== void 0) appInstallNonces.delete(oldest);
|
|
47912
|
+
}
|
|
47913
|
+
const nonce = randomBytes(16).toString("hex");
|
|
47914
|
+
appInstallNonces.set(nonce, {
|
|
47915
|
+
fingerprint: confirmTokenOf({
|
|
47916
|
+
url: typeof args.url === "string" ? args.url : "",
|
|
47917
|
+
sha256: typeof args.sha256 === "string" ? args.sha256 : void 0,
|
|
47918
|
+
sha: typeof args.sha === "string" ? args.sha : void 0,
|
|
47919
|
+
ref: typeof args.ref === "string" ? args.ref : void 0,
|
|
47920
|
+
subdir: typeof args.subdir === "string" ? args.subdir : void 0,
|
|
47921
|
+
allowBuild: args.allowBuild === true
|
|
47922
|
+
}),
|
|
47923
|
+
expiresAt: now + APP_INSTALL_NONCE_TTL_MS
|
|
47924
|
+
});
|
|
47925
|
+
return nonce;
|
|
47926
|
+
}
|
|
47927
|
+
function appInstallConfirmationClass(args) {
|
|
47928
|
+
const url = args.url;
|
|
47929
|
+
if (typeof url !== "string" || url.trim() === "" || args.dir !== void 0 || args.file !== void 0) {
|
|
47930
|
+
return void 0;
|
|
47931
|
+
}
|
|
47932
|
+
if (typeof args.confirm !== "string") return { confirmed: false };
|
|
47933
|
+
const nonce = appInstallNonces.get(args.confirm);
|
|
47934
|
+
appInstallNonces.delete(args.confirm);
|
|
47935
|
+
const fingerprint = confirmTokenOf({
|
|
47936
|
+
url,
|
|
47937
|
+
sha256: typeof args.sha256 === "string" ? args.sha256 : void 0,
|
|
47938
|
+
sha: typeof args.sha === "string" ? args.sha : void 0,
|
|
47939
|
+
ref: typeof args.ref === "string" ? args.ref : void 0,
|
|
47940
|
+
subdir: typeof args.subdir === "string" ? args.subdir : void 0,
|
|
47941
|
+
allowBuild: args.allowBuild === true
|
|
47942
|
+
});
|
|
47943
|
+
const valid = nonce !== void 0 && nonce.fingerprint === fingerprint && Date.now() < nonce.expiresAt;
|
|
47944
|
+
return valid ? { confirmed: true } : { confirmed: false };
|
|
47945
|
+
}
|
|
47946
|
+
function buildAppInstallPreview(args) {
|
|
47947
|
+
const url = typeof args.url === "string" ? args.url : "";
|
|
47948
|
+
const isAgentapp = isAgentappUrl(url);
|
|
47949
|
+
return {
|
|
47950
|
+
needsConfirmation: true,
|
|
47951
|
+
confirm: issueInstallNonce(args),
|
|
47952
|
+
kind: isAgentapp ? "agentapp" : "git",
|
|
47953
|
+
url,
|
|
47954
|
+
...typeof args.sha256 === "string" ? { sha256: args.sha256 } : {},
|
|
47955
|
+
...typeof args.sha === "string" ? { sha: args.sha } : {},
|
|
47956
|
+
...typeof args.ref === "string" ? { ref: args.ref } : {},
|
|
47957
|
+
...typeof args.subdir === "string" ? { subdir: args.subdir } : {},
|
|
47958
|
+
runsBuildCommand: !isAgentapp && args.allowBuild === true
|
|
47959
|
+
};
|
|
47960
|
+
}
|
|
47658
47961
|
async function dispatchAllowlistedAppTool(declaredAllowlist, input, deps2) {
|
|
47659
47962
|
const effectiveAllowlist = [...declaredAllowlist, ...APP_UI_DISCOVERY_TOOLS];
|
|
47660
47963
|
if (!effectiveAllowlist.includes(input.tool)) {
|
|
@@ -47663,6 +47966,9 @@ async function dispatchAllowlistedAppTool(declaredAllowlist, input, deps2) {
|
|
|
47663
47966
|
);
|
|
47664
47967
|
}
|
|
47665
47968
|
const args = input.args ?? {};
|
|
47969
|
+
if (input.tool === "app_install" && appInstallConfirmationClass(args)?.confirmed === false) {
|
|
47970
|
+
return textResult2(buildAppInstallPreview(args));
|
|
47971
|
+
}
|
|
47666
47972
|
try {
|
|
47667
47973
|
if (input.tool.startsWith("imported:")) {
|
|
47668
47974
|
if (!deps2.callImportedTool) return notEnabled("app_tool_call");
|
|
@@ -50263,16 +50569,7 @@ function projectSessionForHttp(session, fields) {
|
|
|
50263
50569
|
var DEFAULT_ALLOWED_ORIGINS = [
|
|
50264
50570
|
"http://localhost:*",
|
|
50265
50571
|
"http://127.0.0.1:*",
|
|
50266
|
-
"https://localhost:*"
|
|
50267
|
-
// The canonical hosted agentproto panel (github.com/agentproto/cli-site,
|
|
50268
|
-
// deployed at cli.agentproto.sh). It drives the user's OWN local daemon
|
|
50269
|
-
// from the browser: read-only GETs (session list, SSE stream) are ungated
|
|
50270
|
-
// and already work, but the /sessions/:id/pty WebSocket upgrade IS gated —
|
|
50271
|
-
// so a PTY terminal in the panel 401s unless this first-party origin is
|
|
50272
|
-
// trusted like localhost. A malicious page can't forge this Origin (the
|
|
50273
|
-
// browser sets it), so the trust is scoped to agentproto's own panel,
|
|
50274
|
-
// matching how guilde.work is trusted. Drop it via `strictOrigins`.
|
|
50275
|
-
"https://cli.agentproto.sh"
|
|
50572
|
+
"https://localhost:*"
|
|
50276
50573
|
];
|
|
50277
50574
|
var DEFAULT_FRAME_ANCESTORS = ["vscode-webview:", "vscode-file:"];
|
|
50278
50575
|
var EVENTS_KEEPALIVE_MS = 25e3;
|
|
@@ -50447,7 +50744,9 @@ async function startHttpServer(opts) {
|
|
|
50447
50744
|
}
|
|
50448
50745
|
}
|
|
50449
50746
|
}
|
|
50450
|
-
async function serveMcp(req, res, server2) {
|
|
50747
|
+
async function serveMcp(req, res, server2, observedSessionId) {
|
|
50748
|
+
const rawVersionHeader = req.headers["mcp-protocol-version"];
|
|
50749
|
+
const clientProtocolVersion = Array.isArray(rawVersionHeader) ? rawVersionHeader[0] : rawVersionHeader;
|
|
50451
50750
|
coerceUnsupportedProtocolVersionHeader(req);
|
|
50452
50751
|
const transport = new StreamableHTTPServerTransport({
|
|
50453
50752
|
sessionIdGenerator: void 0
|
|
@@ -50466,9 +50765,25 @@ async function startHttpServer(opts) {
|
|
|
50466
50765
|
void transport.close();
|
|
50467
50766
|
void server2.close();
|
|
50468
50767
|
});
|
|
50768
|
+
const observations = observedSessionId ? opts.mcpObservations : void 0;
|
|
50769
|
+
let sawMessage = false;
|
|
50469
50770
|
try {
|
|
50470
50771
|
await server2.connect(transport);
|
|
50772
|
+
if (observations && observedSessionId) {
|
|
50773
|
+
observations.observe(transport, {
|
|
50774
|
+
sessionId: observedSessionId,
|
|
50775
|
+
...clientProtocolVersion ? { clientProtocolVersion } : {}
|
|
50776
|
+
});
|
|
50777
|
+
const innerOnMessage = transport.onmessage;
|
|
50778
|
+
transport.onmessage = (message, extra) => {
|
|
50779
|
+
sawMessage = true;
|
|
50780
|
+
innerOnMessage?.(message, extra);
|
|
50781
|
+
};
|
|
50782
|
+
}
|
|
50471
50783
|
await transport.handleRequest(req, res);
|
|
50784
|
+
if (observations && observedSessionId && !sawMessage && req.method === "POST" && res.statusCode >= 400) {
|
|
50785
|
+
observations.recordHttpError(observedSessionId, res.statusCode);
|
|
50786
|
+
}
|
|
50472
50787
|
} catch (err) {
|
|
50473
50788
|
const line = mcpErrorLogGate.onFailure(
|
|
50474
50789
|
"mcp:handleRequest",
|
|
@@ -50539,7 +50854,7 @@ async function startHttpServer(opts) {
|
|
|
50539
50854
|
return;
|
|
50540
50855
|
}
|
|
50541
50856
|
const server2 = await opts.mcpServerFactory(denyTools, callerSessionId, origin, deferred, allowTools, surface);
|
|
50542
|
-
await serveMcp(req, res, server2);
|
|
50857
|
+
await serveMcp(req, res, server2, callerSessionId);
|
|
50543
50858
|
}
|
|
50544
50859
|
async function handleOrchestratorMcp(req, res) {
|
|
50545
50860
|
if (!opts.orchestratorMcpServerFactory || !opts.verifyOrchestratorScope) {
|
|
@@ -51195,7 +51510,8 @@ async function startHttpServer(opts) {
|
|
|
51195
51510
|
opts.webhookNotifier,
|
|
51196
51511
|
opts.ensureLlmEndpointRunning,
|
|
51197
51512
|
opts.listAgentAdapters,
|
|
51198
|
-
opts.deviceMirrorSync
|
|
51513
|
+
opts.deviceMirrorSync,
|
|
51514
|
+
opts.mcpObservations
|
|
51199
51515
|
);
|
|
51200
51516
|
if (handled) return;
|
|
51201
51517
|
}
|
|
@@ -52421,6 +52737,12 @@ async function startHttpServer(opts) {
|
|
|
52421
52737
|
});
|
|
52422
52738
|
return;
|
|
52423
52739
|
}
|
|
52740
|
+
if (path === "/store" && req.method === "GET") {
|
|
52741
|
+
const target = normalizeStoreRedirectUrl(req.url ?? "/store");
|
|
52742
|
+
res.writeHead(302, { location: target });
|
|
52743
|
+
res.end();
|
|
52744
|
+
return;
|
|
52745
|
+
}
|
|
52424
52746
|
if (opts.appRegistry && path.startsWith("/apps/")) {
|
|
52425
52747
|
const appUiShellBlocked = (appId) => !iframeEmbedOriginAllowed(req, opts.appRegistry.getApp(appId) ?? {}) && !isValidAppEmbedToken(requestEmbedToken(req)) && guardBrowserOrigin(req, res);
|
|
52426
52748
|
const assetMatch = path.match(APP_UI_ASSET_RE);
|
|
@@ -53281,7 +53603,7 @@ async function handleSandboxes(_req, res, path, resolveSandboxProvider2) {
|
|
|
53281
53603
|
json3(200, { alive: true, ...probe.state ? { state: probe.state } : { state: row.state }, checkedAt });
|
|
53282
53604
|
return true;
|
|
53283
53605
|
}
|
|
53284
|
-
async function handleSessions(req, res, path, registry, resolveAgentAdapter, ptyEnabled = false, resolveBrowserAdapter, listBrowserAdapters, sessionEvents, eventRing, buildOrchestratorMcp, daemonMcpUrl, provisionWorktree, listCatalogModels, resolveSandboxProvider2, webhookNotifier, ensureLlmEndpointRunning, listAgentAdapters, deviceMirrorSync) {
|
|
53606
|
+
async function handleSessions(req, res, path, registry, resolveAgentAdapter, ptyEnabled = false, resolveBrowserAdapter, listBrowserAdapters, sessionEvents, eventRing, buildOrchestratorMcp, daemonMcpUrl, provisionWorktree, listCatalogModels, resolveSandboxProvider2, webhookNotifier, ensureLlmEndpointRunning, listAgentAdapters, deviceMirrorSync, mcpObservations) {
|
|
53285
53607
|
const json3 = (status, body) => {
|
|
53286
53608
|
res.writeHead(status, { "content-type": "application/json" });
|
|
53287
53609
|
res.end(JSON.stringify(body));
|
|
@@ -54329,7 +54651,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
54329
54651
|
}
|
|
54330
54652
|
const fresh = registry.get(desc.id) ?? desc;
|
|
54331
54653
|
const pendingPermissions = registry.listPendingPermissions({ sessionId: fresh.id }).length;
|
|
54332
|
-
json3(200, buildSessionCapabilities(fresh, pendingPermissions));
|
|
54654
|
+
json3(200, buildSessionCapabilities(fresh, pendingPermissions, mcpObservations?.get(fresh.id)));
|
|
54333
54655
|
return true;
|
|
54334
54656
|
}
|
|
54335
54657
|
const renameMatch = path.match(/^\/sessions\/([^/]+)$/);
|
|
@@ -56690,6 +57012,11 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
|
|
|
56690
57012
|
sendRepresentation(req, res, rep, headers);
|
|
56691
57013
|
}
|
|
56692
57014
|
var APP_UI_PAGE_RE = /^\/apps\/(.+)\/ui\/?$/;
|
|
57015
|
+
function normalizeStoreRedirectUrl(rawUrl) {
|
|
57016
|
+
const queryIndex = rawUrl.indexOf("?");
|
|
57017
|
+
const query = queryIndex >= 0 ? rawUrl.slice(queryIndex) : "";
|
|
57018
|
+
return `/apps/${STORE_PANEL_APP_ID.split("/")[0]}/${STORE_PANEL_APP_ID.split("/")[1]}/ui${query}`;
|
|
57019
|
+
}
|
|
56693
57020
|
var APP_UI_ASSET_RE = /^\/apps\/(.+?)\/ui\/assets\/([^/]*)$/;
|
|
56694
57021
|
function isAppUiShellRequest(method, path) {
|
|
56695
57022
|
if (method !== "GET") return false;
|
|
@@ -64021,7 +64348,9 @@ var RemoteController = class {
|
|
|
64021
64348
|
result.bearerToken = bearerToken;
|
|
64022
64349
|
result.mcpEndpoint = mcpEndpoint;
|
|
64023
64350
|
result.mcpConfigSnippet = buildMcpConfigSnippet2(mcpEndpoint, bearerToken);
|
|
64024
|
-
|
|
64351
|
+
if (this.opts.isSessionChatInstalled?.()) {
|
|
64352
|
+
result.phoneUrl = `${started.publicUrl}/apps/${SESSION_CHAT_APP_ID}/ui#token=${bearerToken}`;
|
|
64353
|
+
}
|
|
64025
64354
|
} else {
|
|
64026
64355
|
const warning = `\u26A0 ${started.publicUrl} forwards to ${target.host}:${target.port} with NO authentication from the daemon \u2014 anyone with the URL can reach it. Ensure the upstream service enforces its own auth, and run remote_disable when done.`;
|
|
64027
64356
|
result.warning = warning;
|
|
@@ -64115,7 +64444,7 @@ function registerRemoteTools(server, opts) {
|
|
|
64115
64444
|
const { controller } = opts;
|
|
64116
64445
|
server.tool(
|
|
64117
64446
|
"remote_enable",
|
|
64118
|
-
"Publish a local port to the internet via Cloudflare. By default exposes this gateway and gates it with a bearer token (returned ONCE in the response \u2014 store it; only its hash is persisted). Pass `targetPort` to tunnel a different local service (Supabase, dev server, \u2026); in that mode the daemon does not gate the proxied traffic \u2014 the upstream service handles its own auth. Returns the public URL plus a paste-ready `.mcp.json` snippet and a `phoneUrl` \u2014 a single link to open on a phone, with the token in a URL fragment (gateway mode only). Re-running while a tunnel is already up errors; call `remote_disable` first to rotate.",
|
|
64447
|
+
"Publish a local port to the internet via Cloudflare. By default exposes this gateway and gates it with a bearer token (returned ONCE in the response \u2014 store it; only its hash is persisted). Pass `targetPort` to tunnel a different local service (Supabase, dev server, \u2026); in that mode the daemon does not gate the proxied traffic \u2014 the upstream service handles its own auth. Returns the public URL plus a paste-ready `.mcp.json` snippet and a `phoneUrl` \u2014 a single link to open on a phone, with the token in a URL fragment (gateway mode, and only when the @agentik/session-chat app is installed). Re-running while a tunnel is already up errors; call `remote_disable` first to rotate.",
|
|
64119
64448
|
{
|
|
64120
64449
|
provider: z.enum(["quick"]).optional().describe(
|
|
64121
64450
|
"Tunnel backend. `quick` = Cloudflare Quick Tunnel (no API key, ephemeral *.trycloudflare.com URL). Defaults to `quick`. Named-CF / Tailscale providers TBD."
|
|
@@ -66904,6 +67233,7 @@ async function createGateway(opts) {
|
|
|
66904
67233
|
});
|
|
66905
67234
|
const sessionEvents = createSessionEventBus();
|
|
66906
67235
|
const eventRing = createEventRing();
|
|
67236
|
+
const mcpObservations = createMcpObservationStore();
|
|
66907
67237
|
eventRing.wire(sessionEvents);
|
|
66908
67238
|
const webhookNotifier = createWebhookNotifier();
|
|
66909
67239
|
sessionEvents.onAny((ev) => webhookNotifier.onSessionEvent(ev));
|
|
@@ -67235,6 +67565,11 @@ async function createGateway(opts) {
|
|
|
67235
67565
|
webOrigins: daemonConfig.approvals?.webOrigins ?? []
|
|
67236
67566
|
});
|
|
67237
67567
|
wireSupervisorNotify({ registry: sessions, sessionEvents });
|
|
67568
|
+
wireMcpDegradedWarnings({
|
|
67569
|
+
sessionEvents,
|
|
67570
|
+
getSession: (id) => sessions.get(id),
|
|
67571
|
+
store: mcpObservations
|
|
67572
|
+
});
|
|
67238
67573
|
const activityProjector = createActivityProjector({
|
|
67239
67574
|
registry: sessions,
|
|
67240
67575
|
sessionEvents,
|
|
@@ -67435,6 +67770,7 @@ async function createGateway(opts) {
|
|
|
67435
67770
|
}
|
|
67436
67771
|
registerSessionTools(server, {
|
|
67437
67772
|
registry: sessions,
|
|
67773
|
+
mcpObservations,
|
|
67438
67774
|
workspace,
|
|
67439
67775
|
mcpProxy,
|
|
67440
67776
|
ptyEnabled: opts.spawnPty != null,
|
|
@@ -67539,6 +67875,21 @@ async function createGateway(opts) {
|
|
|
67539
67875
|
registerAppDataTools(server, { appRegistry });
|
|
67540
67876
|
registerAppExternalTools(server, { appRegistry });
|
|
67541
67877
|
registerTelegramBotTools(server, { telegramCreds: telegramBotCreds });
|
|
67878
|
+
const listCatalogRows = async () => {
|
|
67879
|
+
const rows = await dispatchTool("app_catalog", {});
|
|
67880
|
+
if (typeof rows !== "string") return [];
|
|
67881
|
+
try {
|
|
67882
|
+
const parsed = JSON.parse(rows);
|
|
67883
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
67884
|
+
} catch {
|
|
67885
|
+
return [];
|
|
67886
|
+
}
|
|
67887
|
+
};
|
|
67888
|
+
const listInstalledRows = () => {
|
|
67889
|
+
const apps = appRegistry.listApps();
|
|
67890
|
+
return apps;
|
|
67891
|
+
};
|
|
67892
|
+
const listUpdateRows = () => appRegistry.listApps().filter((a) => a.source !== void 0 && a.source.kind !== "local" && a.source.catalogId !== void 0).map((a) => ({ appId: a.appId, updateAvailable: true, ...a.version !== void 0 ? { version: a.version } : {} }));
|
|
67542
67893
|
const listSessionsFiltered = (filter) => {
|
|
67543
67894
|
let rows = sessions.list().filter((s) => s.kind !== "command");
|
|
67544
67895
|
if (filter === "running") {
|
|
@@ -67579,7 +67930,14 @@ async function createGateway(opts) {
|
|
|
67579
67930
|
reviewRunner,
|
|
67580
67931
|
{ ...input, includeRunning: true },
|
|
67581
67932
|
{ resolveSubtree: (sessionId) => [...collectSubtree(sessionId, sessions.list({ includeArchived: true }))] }
|
|
67582
|
-
)
|
|
67933
|
+
),
|
|
67934
|
+
// Store widget's read paths — app_catalog's rows (via the SAME
|
|
67935
|
+
// `dispatchTool` the panel's own apply verbs will route through, so
|
|
67936
|
+
// its snapshot can never disagree with what the tool call returns),
|
|
67937
|
+
// and the registry's installed apps.
|
|
67938
|
+
listCatalog: listCatalogRows,
|
|
67939
|
+
listInstalled: listInstalledRows,
|
|
67940
|
+
listUpdates: listUpdateRows
|
|
67583
67941
|
}),
|
|
67584
67942
|
// Same ptyEnabled gate as terminal_start/terminal_input/… in
|
|
67585
67943
|
// session-tools.ts — the panel would be able to open the WS but
|
|
@@ -67745,6 +68103,7 @@ async function createGateway(opts) {
|
|
|
67745
68103
|
const http = await startHttpServer({
|
|
67746
68104
|
port,
|
|
67747
68105
|
bind: opts.bind,
|
|
68106
|
+
mcpObservations,
|
|
67748
68107
|
// Auth is read on every request via this getter. `opts.auth`
|
|
67749
68108
|
// (when provided) is the startup default and represents the
|
|
67750
68109
|
// operator's intent — bearer-only deployments stay bearer-only
|