@agentproto/runtime 5.10.0 → 5.11.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/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 +2 -2
- package/dist/index.mjs +383 -18
- 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 +24 -8
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,184 @@ 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 error = message.error ? `${String(message.error.code ?? "error")}: ${String(message.error.message ?? "")}` : void 0;
|
|
27963
|
+
const answeredVersion = typeof result?.["protocolVersion"] === "string" ? result["protocolVersion"] : void 0;
|
|
27964
|
+
const protocolVersion = method === "initialize" ? answeredVersion : clientProtocolVersion;
|
|
27965
|
+
const base = {
|
|
27966
|
+
at,
|
|
27967
|
+
ok: error === void 0,
|
|
27968
|
+
...error !== void 0 ? { error } : {},
|
|
27969
|
+
...protocolVersion ? { protocolVersion } : {}
|
|
27970
|
+
};
|
|
27971
|
+
if (method === "initialize") obs.initialize = base;
|
|
27972
|
+
else if (method === "server/discover") obs.discover = base;
|
|
27973
|
+
else {
|
|
27974
|
+
const rawTools = Array.isArray(result?.["tools"]) ? result["tools"] : [];
|
|
27975
|
+
const tools = [];
|
|
27976
|
+
for (const t of rawTools) {
|
|
27977
|
+
if (!t || typeof t !== "object") continue;
|
|
27978
|
+
const name = t.name;
|
|
27979
|
+
if (typeof name !== "string") continue;
|
|
27980
|
+
const description = t.description;
|
|
27981
|
+
tools.push({
|
|
27982
|
+
name,
|
|
27983
|
+
...typeof description === "string" && description.length > 0 ? { description: description.slice(0, MAX_DESCRIPTION_CHARS) } : {}
|
|
27984
|
+
});
|
|
27985
|
+
}
|
|
27986
|
+
obs.toolsList = {
|
|
27987
|
+
...base,
|
|
27988
|
+
toolCount: tools.length,
|
|
27989
|
+
deferred: tools.some((t) => t.name === "tool_search"),
|
|
27990
|
+
tools
|
|
27991
|
+
};
|
|
27992
|
+
}
|
|
27993
|
+
};
|
|
27994
|
+
return {
|
|
27995
|
+
observe(transport, ctx) {
|
|
27996
|
+
const pending = /* @__PURE__ */ new Map();
|
|
27997
|
+
const innerOnMessage = transport.onmessage;
|
|
27998
|
+
transport.onmessage = (message, extra) => {
|
|
27999
|
+
try {
|
|
28000
|
+
if ("method" in message && "id" in message && message.id !== void 0 && OBSERVED_METHODS.has(message.method)) {
|
|
28001
|
+
pending.set(message.id, message.method);
|
|
28002
|
+
}
|
|
28003
|
+
} catch {
|
|
28004
|
+
}
|
|
28005
|
+
innerOnMessage?.(message, extra);
|
|
28006
|
+
};
|
|
28007
|
+
const innerSend = transport.send.bind(transport);
|
|
28008
|
+
transport.send = async (message, options) => {
|
|
28009
|
+
try {
|
|
28010
|
+
if ("id" in message && message.id !== void 0 && ("result" in message || "error" in message)) {
|
|
28011
|
+
const method = pending.get(message.id);
|
|
28012
|
+
if (method) {
|
|
28013
|
+
pending.delete(message.id);
|
|
28014
|
+
record2(ctx.sessionId, method, message, ctx.clientProtocolVersion);
|
|
28015
|
+
}
|
|
28016
|
+
}
|
|
28017
|
+
} catch {
|
|
28018
|
+
}
|
|
28019
|
+
return innerSend(message, options);
|
|
28020
|
+
};
|
|
28021
|
+
},
|
|
28022
|
+
recordHttpError(sessionId, status) {
|
|
28023
|
+
const obs = touch(sessionId);
|
|
28024
|
+
obs.httpError = { at: obs.lastSeenAt, status };
|
|
28025
|
+
},
|
|
28026
|
+
get: (sessionId) => bySession.get(sessionId),
|
|
28027
|
+
delete: (sessionId) => {
|
|
28028
|
+
bySession.delete(sessionId);
|
|
28029
|
+
}
|
|
28030
|
+
};
|
|
28031
|
+
}
|
|
28032
|
+
function isDaemonMountFor(sessionId, entry) {
|
|
28033
|
+
if (entry.transport !== "http" || typeof entry.ref !== "string") return false;
|
|
28034
|
+
try {
|
|
28035
|
+
const url = new URL(entry.ref);
|
|
28036
|
+
return url.pathname === "/mcp" && url.searchParams.get("callerSessionId") === sessionId;
|
|
28037
|
+
} catch {
|
|
28038
|
+
return false;
|
|
28039
|
+
}
|
|
28040
|
+
}
|
|
28041
|
+
function daemonMountStatus(obs, turnsCompleted) {
|
|
28042
|
+
if (!obs) return (turnsCompleted ?? 0) > 0 ? "never-contacted" : "declared";
|
|
28043
|
+
if (obs.toolsList) return obs.toolsList.ok ? "listed" : "error";
|
|
28044
|
+
if (obs.initialize?.ok === false || obs.discover?.ok === false || obs.httpError) return "error";
|
|
28045
|
+
if (obs.initialize || obs.discover) return "connected";
|
|
28046
|
+
return (turnsCompleted ?? 0) > 0 ? "never-contacted" : "declared";
|
|
28047
|
+
}
|
|
28048
|
+
function assessDaemonMount(obs) {
|
|
28049
|
+
const tl = obs?.toolsList;
|
|
28050
|
+
if (tl) {
|
|
28051
|
+
if (!tl.ok) return { reason: "error", ...tl.error ? { detail: tl.error } : {} };
|
|
28052
|
+
if (tl.toolCount === 0) return { reason: "zero-tools" };
|
|
28053
|
+
return void 0;
|
|
28054
|
+
}
|
|
28055
|
+
if (obs?.initialize?.ok === false) {
|
|
28056
|
+
return { reason: "error", ...obs.initialize.error ? { detail: `initialize ${obs.initialize.error}` } : {} };
|
|
28057
|
+
}
|
|
28058
|
+
if (obs?.discover?.ok === false) {
|
|
28059
|
+
return { reason: "error", ...obs.discover.error ? { detail: `server/discover ${obs.discover.error}` } : {} };
|
|
28060
|
+
}
|
|
28061
|
+
if (obs?.httpError) return { reason: "error", detail: `HTTP ${obs.httpError.status}` };
|
|
28062
|
+
return {
|
|
28063
|
+
reason: "never-listed",
|
|
28064
|
+
...obs?.initialize || obs?.discover ? { detail: "handshake seen but no tools/list" } : {}
|
|
28065
|
+
};
|
|
28066
|
+
}
|
|
28067
|
+
var ABNORMAL_TURN_REASONS = /* @__PURE__ */ new Set(["error", "cancelled", "watchdog-timeout", "aborted"]);
|
|
28068
|
+
function wireMcpDegradedWarnings(opts) {
|
|
28069
|
+
const lastEmitted = /* @__PURE__ */ new Map();
|
|
28070
|
+
const offTurnEnd = opts.sessionEvents.on("session:turn-end", (ev) => {
|
|
28071
|
+
if (ev.reason !== void 0 && ABNORMAL_TURN_REASONS.has(ev.reason)) return;
|
|
28072
|
+
const desc = opts.getSession(ev.sessionId);
|
|
28073
|
+
if (!desc || desc.kind !== "agent-cli") return;
|
|
28074
|
+
const mount = (desc.mcpServers ?? []).find((s) => isDaemonMountFor(ev.sessionId, s));
|
|
28075
|
+
if (!mount) return;
|
|
28076
|
+
const assessment = assessDaemonMount(opts.store.get(ev.sessionId));
|
|
28077
|
+
if (!assessment) {
|
|
28078
|
+
lastEmitted.delete(ev.sessionId);
|
|
28079
|
+
return;
|
|
28080
|
+
}
|
|
28081
|
+
const key = `${assessment.reason}|${assessment.detail ?? ""}`;
|
|
28082
|
+
if (lastEmitted.get(ev.sessionId) === key) return;
|
|
28083
|
+
lastEmitted.set(ev.sessionId, key);
|
|
28084
|
+
opts.sessionEvents.emit({
|
|
28085
|
+
type: "mcp:degraded",
|
|
28086
|
+
sessionId: ev.sessionId,
|
|
28087
|
+
server: mount.name,
|
|
28088
|
+
reason: assessment.reason,
|
|
28089
|
+
...assessment.detail ? { detail: assessment.detail } : {},
|
|
28090
|
+
...desc.label ? { label: desc.label } : {},
|
|
28091
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
28092
|
+
});
|
|
28093
|
+
});
|
|
28094
|
+
const offExit = opts.sessionEvents.on("session:exited", (ev) => {
|
|
28095
|
+
lastEmitted.delete(ev.sessionId);
|
|
28096
|
+
});
|
|
28097
|
+
const offResumed = opts.sessionEvents.on("session:resumed", (ev) => {
|
|
28098
|
+
lastEmitted.delete(ev.sessionId);
|
|
28099
|
+
opts.store.delete(ev.sessionId);
|
|
28100
|
+
});
|
|
28101
|
+
return () => {
|
|
28102
|
+
offTurnEnd();
|
|
28103
|
+
offExit();
|
|
28104
|
+
offResumed();
|
|
28105
|
+
};
|
|
28106
|
+
}
|
|
28107
|
+
|
|
28108
|
+
// src/session-capabilities.ts
|
|
27931
28109
|
var SKILLS_APPLYING_ADAPTERS = /* @__PURE__ */ new Set(["hermes"]);
|
|
27932
28110
|
function sessionArm(desc) {
|
|
27933
28111
|
if (desc.kind === "terminal") return "pty";
|
|
@@ -27936,7 +28114,7 @@ function sessionArm(desc) {
|
|
|
27936
28114
|
}
|
|
27937
28115
|
return "other";
|
|
27938
28116
|
}
|
|
27939
|
-
function buildSessionCapabilities(desc, pendingPermissions) {
|
|
28117
|
+
function buildSessionCapabilities(desc, pendingPermissions, mcpObservation) {
|
|
27940
28118
|
return {
|
|
27941
28119
|
sessionId: desc.id,
|
|
27942
28120
|
adapter: desc.adapterSlug ?? conversationTerminalSlugFor(desc) ?? "unknown",
|
|
@@ -27959,7 +28137,8 @@ function buildSessionCapabilities(desc, pendingPermissions) {
|
|
|
27959
28137
|
mcpServers: (desc.mcpServers ?? []).map((s) => ({
|
|
27960
28138
|
name: s.name,
|
|
27961
28139
|
transport: s.transport,
|
|
27962
|
-
...s.ref ? { ref: s.ref } : {}
|
|
28140
|
+
...s.ref ? { ref: s.ref } : {},
|
|
28141
|
+
...isDaemonMountFor(desc.id, s) ? daemonMountFields(mcpObservation, desc.turnsCompleted) : { status: "declared" }
|
|
27963
28142
|
})),
|
|
27964
28143
|
skills: desc.skills ?? [],
|
|
27965
28144
|
skillsApplied: desc.adapterSlug !== void 0 && SKILLS_APPLYING_ADAPTERS.has(desc.adapterSlug),
|
|
@@ -27967,6 +28146,20 @@ function buildSessionCapabilities(desc, pendingPermissions) {
|
|
|
27967
28146
|
pendingPermissions
|
|
27968
28147
|
};
|
|
27969
28148
|
}
|
|
28149
|
+
function daemonMountFields(obs, turnsCompleted) {
|
|
28150
|
+
const status = daemonMountStatus(obs, turnsCompleted);
|
|
28151
|
+
const tl = obs?.toolsList;
|
|
28152
|
+
const failing = tl && !tl.ok ? tl : [obs?.initialize, obs?.discover].find((c) => c && !c.ok);
|
|
28153
|
+
const error = failing?.error ?? (obs?.httpError ? `HTTP ${obs.httpError.status}` : void 0);
|
|
28154
|
+
const protocolVersion = tl?.protocolVersion ?? obs?.initialize?.protocolVersion ?? obs?.discover?.protocolVersion;
|
|
28155
|
+
return {
|
|
28156
|
+
status,
|
|
28157
|
+
...tl?.ok ? { toolCount: tl.toolCount, tools: tl.tools, deferred: tl.deferred } : {},
|
|
28158
|
+
...protocolVersion ? { protocolVersion } : {},
|
|
28159
|
+
...obs ? { lastSeenAt: obs.lastSeenAt } : {},
|
|
28160
|
+
...error ? { error } : {}
|
|
28161
|
+
};
|
|
28162
|
+
}
|
|
27970
28163
|
|
|
27971
28164
|
// src/session-tools.ts
|
|
27972
28165
|
init_context_checkpoint();
|
|
@@ -30769,7 +30962,7 @@ function registerSessionTools(rawServer, opts) {
|
|
|
30769
30962
|
const pendingPermissions = registry.listPendingPermissions({ sessionId: fresh.id }).length;
|
|
30770
30963
|
return {
|
|
30771
30964
|
content: [
|
|
30772
|
-
{ type: "text", text: JSON.stringify(buildSessionCapabilities(fresh, pendingPermissions)) }
|
|
30965
|
+
{ type: "text", text: JSON.stringify(buildSessionCapabilities(fresh, pendingPermissions, opts.mcpObservations?.get(fresh.id))) }
|
|
30773
30966
|
]
|
|
30774
30967
|
};
|
|
30775
30968
|
}
|
|
@@ -36193,6 +36386,20 @@ function registerMcpApps(server, apps) {
|
|
|
36193
36386
|
);
|
|
36194
36387
|
}
|
|
36195
36388
|
}
|
|
36389
|
+
|
|
36390
|
+
// src/store-panel-allowlist.ts
|
|
36391
|
+
var STORE_UI_TOOLS = [
|
|
36392
|
+
"app_catalog",
|
|
36393
|
+
"app_list",
|
|
36394
|
+
"app_install",
|
|
36395
|
+
"app_resync",
|
|
36396
|
+
"app_updates",
|
|
36397
|
+
"app_uninstall",
|
|
36398
|
+
"app_status"
|
|
36399
|
+
];
|
|
36400
|
+
|
|
36401
|
+
// src/builtin-apps.ts
|
|
36402
|
+
var STORE_PANEL_APP_ID = storeApp.id ?? "@agentproto/store";
|
|
36196
36403
|
function makeBuiltinPanelApps(ops) {
|
|
36197
36404
|
const sessionChatNativeInstalled = ops.isSessionChatInstalled();
|
|
36198
36405
|
return [
|
|
@@ -36236,7 +36443,18 @@ function makeBuiltinPanelApps(ops) {
|
|
|
36236
36443
|
// building `makeBuiltinPanelApps` without one — e.g.
|
|
36237
36444
|
// `builtinPanelCatalogEntries`'s stub ops below — still gets a
|
|
36238
36445
|
// deterministic, small app list).
|
|
36239
|
-
...ops.listReviews ? [makeReviewPanelApp({ listReviews: ops.listReviews })] : []
|
|
36446
|
+
...ops.listReviews ? [makeReviewPanelApp({ listReviews: ops.listReviews })] : [],
|
|
36447
|
+
// App Store widget — the browser/launcher over app_catalog + installed
|
|
36448
|
+
// apps (see apps/src/store). Initial snapshot only when the ops are
|
|
36449
|
+
// wired; writes (install/update/uninstall) always go through the SAME
|
|
36450
|
+
// MCP verbs every other caller uses — no second install path.
|
|
36451
|
+
...ops.listCatalog && ops.listInstalled && ops.listUpdates ? [
|
|
36452
|
+
makeStoreApp({
|
|
36453
|
+
listCatalog: ops.listCatalog,
|
|
36454
|
+
listInstalled: ops.listInstalled,
|
|
36455
|
+
listUpdates: ops.listUpdates
|
|
36456
|
+
})
|
|
36457
|
+
] : []
|
|
36240
36458
|
];
|
|
36241
36459
|
}
|
|
36242
36460
|
var PANEL_APP_HANDLES = [
|
|
@@ -36247,7 +36465,8 @@ var PANEL_APP_HANDLES = [
|
|
|
36247
36465
|
liveSessionApp,
|
|
36248
36466
|
sessionChatApp,
|
|
36249
36467
|
workBoardApp,
|
|
36250
|
-
reviewPanelApp
|
|
36468
|
+
reviewPanelApp,
|
|
36469
|
+
storeApp
|
|
36251
36470
|
];
|
|
36252
36471
|
var DEEP_LINK_SESSION_ID_RE = /^sess_[A-Za-z0-9_-]+$/;
|
|
36253
36472
|
function isValidDeepLinkSessionId(id) {
|
|
@@ -36260,6 +36479,9 @@ function resolveBuiltinPanelUi(appId, httpBaseUrl, sessionId) {
|
|
|
36260
36479
|
const html = typeof app.html === "function" ? app.html({ httpBaseUrl, sessionId: pinnedSessionId }) : app.html;
|
|
36261
36480
|
return { html, tools: liveSessionApp.ui?.tools ?? [] };
|
|
36262
36481
|
}
|
|
36482
|
+
if (appId === storeApp.id) {
|
|
36483
|
+
return { html: storeApp.ui?.html ?? "", tools: STORE_UI_TOOLS };
|
|
36484
|
+
}
|
|
36263
36485
|
const handle = [sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, workBoardApp, reviewPanelApp].find(
|
|
36264
36486
|
(h) => h.id === appId
|
|
36265
36487
|
);
|
|
@@ -36274,10 +36496,14 @@ function builtinPanelCatalogEntries() {
|
|
|
36274
36496
|
listTasks: (boardId) => ({ boardId: boardId ?? "ws:default", tasks: [] }),
|
|
36275
36497
|
// Always provided (never omitted) here, same reasoning as
|
|
36276
36498
|
// `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
|
-
|
|
36499
|
+
// zipped against this call's output BY INDEX, so the review panel and
|
|
36500
|
+
// the store must always be present in this specific call regardless of
|
|
36501
|
+
// whether the real daemon happens to have a review runner wired or any
|
|
36502
|
+
// store ops at all.
|
|
36503
|
+
listReviews: async () => ({ total: 0, attestations: [] }),
|
|
36504
|
+
listCatalog: async () => [],
|
|
36505
|
+
listInstalled: () => [],
|
|
36506
|
+
listUpdates: () => []
|
|
36281
36507
|
});
|
|
36282
36508
|
return apps.map((app, i) => {
|
|
36283
36509
|
const handle = PANEL_APP_HANDLES[i];
|
|
@@ -45778,7 +46004,7 @@ function registerOrchestrationTools(rawServer, opts) {
|
|
|
45778
46004
|
"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
46005
|
),
|
|
45780
46006
|
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."),
|
|
46007
|
+
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
46008
|
limit: z.number().int().min(1).max(200).optional().describe("Max events to return. Default 50."),
|
|
45783
46009
|
full: z.boolean().optional().describe(
|
|
45784
46010
|
"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 +47558,7 @@ var RemoteCatalogSchema = z.object({
|
|
|
47332
47558
|
generatedAt: z.string().optional(),
|
|
47333
47559
|
entries: z.array(z.unknown())
|
|
47334
47560
|
});
|
|
47335
|
-
var DEFAULT_CATALOG_SOURCE_URL = "https://
|
|
47561
|
+
var DEFAULT_CATALOG_SOURCE_URL = "https://agentproto.sh/catalog/v1/apps.json";
|
|
47336
47562
|
var EMPTY_CATALOG = { apps: [] };
|
|
47337
47563
|
function defaultAppCatalogPath() {
|
|
47338
47564
|
return join(homedir(), ".agentproto", "app-catalog.json");
|
|
@@ -47655,6 +47881,82 @@ function notEnabled(tool) {
|
|
|
47655
47881
|
`${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
47882
|
);
|
|
47657
47883
|
}
|
|
47884
|
+
function confirmTokenOf(args) {
|
|
47885
|
+
const digest = createHash("sha256");
|
|
47886
|
+
digest.update(
|
|
47887
|
+
JSON.stringify([
|
|
47888
|
+
"agentproto-app-install-confirm-v1",
|
|
47889
|
+
args.url,
|
|
47890
|
+
isAgentappUrl(args.url) ? "agentapp" : "git",
|
|
47891
|
+
args.sha256 ?? null,
|
|
47892
|
+
args.sha ?? null,
|
|
47893
|
+
args.ref ?? null,
|
|
47894
|
+
args.subdir ?? null,
|
|
47895
|
+
args.allowBuild === true
|
|
47896
|
+
])
|
|
47897
|
+
);
|
|
47898
|
+
return digest.digest("hex").slice(0, 32);
|
|
47899
|
+
}
|
|
47900
|
+
var appInstallNonces = /* @__PURE__ */ new Map();
|
|
47901
|
+
var APP_INSTALL_NONCE_MAX = 256;
|
|
47902
|
+
var APP_INSTALL_NONCE_TTL_MS = 5 * 60 * 1e3;
|
|
47903
|
+
function issueInstallNonce(args) {
|
|
47904
|
+
const now = Date.now();
|
|
47905
|
+
for (const [nonce2, entry] of appInstallNonces) {
|
|
47906
|
+
if (entry.expiresAt <= now) appInstallNonces.delete(nonce2);
|
|
47907
|
+
}
|
|
47908
|
+
if (appInstallNonces.size >= APP_INSTALL_NONCE_MAX) {
|
|
47909
|
+
const oldest = appInstallNonces.keys().next().value;
|
|
47910
|
+
if (oldest !== void 0) appInstallNonces.delete(oldest);
|
|
47911
|
+
}
|
|
47912
|
+
const nonce = randomBytes(16).toString("hex");
|
|
47913
|
+
appInstallNonces.set(nonce, {
|
|
47914
|
+
fingerprint: confirmTokenOf({
|
|
47915
|
+
url: typeof args.url === "string" ? args.url : "",
|
|
47916
|
+
sha256: typeof args.sha256 === "string" ? args.sha256 : void 0,
|
|
47917
|
+
sha: typeof args.sha === "string" ? args.sha : void 0,
|
|
47918
|
+
ref: typeof args.ref === "string" ? args.ref : void 0,
|
|
47919
|
+
subdir: typeof args.subdir === "string" ? args.subdir : void 0,
|
|
47920
|
+
allowBuild: args.allowBuild === true
|
|
47921
|
+
}),
|
|
47922
|
+
expiresAt: now + APP_INSTALL_NONCE_TTL_MS
|
|
47923
|
+
});
|
|
47924
|
+
return nonce;
|
|
47925
|
+
}
|
|
47926
|
+
function appInstallConfirmationClass(args) {
|
|
47927
|
+
const url = args.url;
|
|
47928
|
+
if (typeof url !== "string" || url.trim() === "" || args.dir !== void 0 || args.file !== void 0) {
|
|
47929
|
+
return void 0;
|
|
47930
|
+
}
|
|
47931
|
+
if (typeof args.confirm !== "string") return { confirmed: false };
|
|
47932
|
+
const nonce = appInstallNonces.get(args.confirm);
|
|
47933
|
+
appInstallNonces.delete(args.confirm);
|
|
47934
|
+
const fingerprint = confirmTokenOf({
|
|
47935
|
+
url,
|
|
47936
|
+
sha256: typeof args.sha256 === "string" ? args.sha256 : void 0,
|
|
47937
|
+
sha: typeof args.sha === "string" ? args.sha : void 0,
|
|
47938
|
+
ref: typeof args.ref === "string" ? args.ref : void 0,
|
|
47939
|
+
subdir: typeof args.subdir === "string" ? args.subdir : void 0,
|
|
47940
|
+
allowBuild: args.allowBuild === true
|
|
47941
|
+
});
|
|
47942
|
+
const valid = nonce !== void 0 && nonce.fingerprint === fingerprint && Date.now() < nonce.expiresAt;
|
|
47943
|
+
return valid ? { confirmed: true } : { confirmed: false };
|
|
47944
|
+
}
|
|
47945
|
+
function buildAppInstallPreview(args) {
|
|
47946
|
+
const url = typeof args.url === "string" ? args.url : "";
|
|
47947
|
+
const isAgentapp = isAgentappUrl(url);
|
|
47948
|
+
return {
|
|
47949
|
+
needsConfirmation: true,
|
|
47950
|
+
confirm: issueInstallNonce(args),
|
|
47951
|
+
kind: isAgentapp ? "agentapp" : "git",
|
|
47952
|
+
url,
|
|
47953
|
+
...typeof args.sha256 === "string" ? { sha256: args.sha256 } : {},
|
|
47954
|
+
...typeof args.sha === "string" ? { sha: args.sha } : {},
|
|
47955
|
+
...typeof args.ref === "string" ? { ref: args.ref } : {},
|
|
47956
|
+
...typeof args.subdir === "string" ? { subdir: args.subdir } : {},
|
|
47957
|
+
runsBuildCommand: !isAgentapp && args.allowBuild === true
|
|
47958
|
+
};
|
|
47959
|
+
}
|
|
47658
47960
|
async function dispatchAllowlistedAppTool(declaredAllowlist, input, deps2) {
|
|
47659
47961
|
const effectiveAllowlist = [...declaredAllowlist, ...APP_UI_DISCOVERY_TOOLS];
|
|
47660
47962
|
if (!effectiveAllowlist.includes(input.tool)) {
|
|
@@ -47663,6 +47965,9 @@ async function dispatchAllowlistedAppTool(declaredAllowlist, input, deps2) {
|
|
|
47663
47965
|
);
|
|
47664
47966
|
}
|
|
47665
47967
|
const args = input.args ?? {};
|
|
47968
|
+
if (input.tool === "app_install" && appInstallConfirmationClass(args)?.confirmed === false) {
|
|
47969
|
+
return textResult2(buildAppInstallPreview(args));
|
|
47970
|
+
}
|
|
47666
47971
|
try {
|
|
47667
47972
|
if (input.tool.startsWith("imported:")) {
|
|
47668
47973
|
if (!deps2.callImportedTool) return notEnabled("app_tool_call");
|
|
@@ -50447,7 +50752,9 @@ async function startHttpServer(opts) {
|
|
|
50447
50752
|
}
|
|
50448
50753
|
}
|
|
50449
50754
|
}
|
|
50450
|
-
async function serveMcp(req, res, server2) {
|
|
50755
|
+
async function serveMcp(req, res, server2, observedSessionId) {
|
|
50756
|
+
const rawVersionHeader = req.headers["mcp-protocol-version"];
|
|
50757
|
+
const clientProtocolVersion = Array.isArray(rawVersionHeader) ? rawVersionHeader[0] : rawVersionHeader;
|
|
50451
50758
|
coerceUnsupportedProtocolVersionHeader(req);
|
|
50452
50759
|
const transport = new StreamableHTTPServerTransport({
|
|
50453
50760
|
sessionIdGenerator: void 0
|
|
@@ -50466,9 +50773,25 @@ async function startHttpServer(opts) {
|
|
|
50466
50773
|
void transport.close();
|
|
50467
50774
|
void server2.close();
|
|
50468
50775
|
});
|
|
50776
|
+
const observations = observedSessionId ? opts.mcpObservations : void 0;
|
|
50777
|
+
let sawMessage = false;
|
|
50469
50778
|
try {
|
|
50470
50779
|
await server2.connect(transport);
|
|
50780
|
+
if (observations && observedSessionId) {
|
|
50781
|
+
observations.observe(transport, {
|
|
50782
|
+
sessionId: observedSessionId,
|
|
50783
|
+
...clientProtocolVersion ? { clientProtocolVersion } : {}
|
|
50784
|
+
});
|
|
50785
|
+
const innerOnMessage = transport.onmessage;
|
|
50786
|
+
transport.onmessage = (message, extra) => {
|
|
50787
|
+
sawMessage = true;
|
|
50788
|
+
innerOnMessage?.(message, extra);
|
|
50789
|
+
};
|
|
50790
|
+
}
|
|
50471
50791
|
await transport.handleRequest(req, res);
|
|
50792
|
+
if (observations && observedSessionId && !sawMessage && req.method === "POST" && res.statusCode >= 400) {
|
|
50793
|
+
observations.recordHttpError(observedSessionId, res.statusCode);
|
|
50794
|
+
}
|
|
50472
50795
|
} catch (err) {
|
|
50473
50796
|
const line = mcpErrorLogGate.onFailure(
|
|
50474
50797
|
"mcp:handleRequest",
|
|
@@ -50539,7 +50862,7 @@ async function startHttpServer(opts) {
|
|
|
50539
50862
|
return;
|
|
50540
50863
|
}
|
|
50541
50864
|
const server2 = await opts.mcpServerFactory(denyTools, callerSessionId, origin, deferred, allowTools, surface);
|
|
50542
|
-
await serveMcp(req, res, server2);
|
|
50865
|
+
await serveMcp(req, res, server2, callerSessionId);
|
|
50543
50866
|
}
|
|
50544
50867
|
async function handleOrchestratorMcp(req, res) {
|
|
50545
50868
|
if (!opts.orchestratorMcpServerFactory || !opts.verifyOrchestratorScope) {
|
|
@@ -51195,7 +51518,8 @@ async function startHttpServer(opts) {
|
|
|
51195
51518
|
opts.webhookNotifier,
|
|
51196
51519
|
opts.ensureLlmEndpointRunning,
|
|
51197
51520
|
opts.listAgentAdapters,
|
|
51198
|
-
opts.deviceMirrorSync
|
|
51521
|
+
opts.deviceMirrorSync,
|
|
51522
|
+
opts.mcpObservations
|
|
51199
51523
|
);
|
|
51200
51524
|
if (handled) return;
|
|
51201
51525
|
}
|
|
@@ -52421,6 +52745,12 @@ async function startHttpServer(opts) {
|
|
|
52421
52745
|
});
|
|
52422
52746
|
return;
|
|
52423
52747
|
}
|
|
52748
|
+
if (path === "/store" && req.method === "GET") {
|
|
52749
|
+
const target = normalizeStoreRedirectUrl(req.url ?? "/store");
|
|
52750
|
+
res.writeHead(302, { location: target });
|
|
52751
|
+
res.end();
|
|
52752
|
+
return;
|
|
52753
|
+
}
|
|
52424
52754
|
if (opts.appRegistry && path.startsWith("/apps/")) {
|
|
52425
52755
|
const appUiShellBlocked = (appId) => !iframeEmbedOriginAllowed(req, opts.appRegistry.getApp(appId) ?? {}) && !isValidAppEmbedToken(requestEmbedToken(req)) && guardBrowserOrigin(req, res);
|
|
52426
52756
|
const assetMatch = path.match(APP_UI_ASSET_RE);
|
|
@@ -53281,7 +53611,7 @@ async function handleSandboxes(_req, res, path, resolveSandboxProvider2) {
|
|
|
53281
53611
|
json3(200, { alive: true, ...probe.state ? { state: probe.state } : { state: row.state }, checkedAt });
|
|
53282
53612
|
return true;
|
|
53283
53613
|
}
|
|
53284
|
-
async function handleSessions(req, res, path, registry, resolveAgentAdapter, ptyEnabled = false, resolveBrowserAdapter, listBrowserAdapters, sessionEvents, eventRing, buildOrchestratorMcp, daemonMcpUrl, provisionWorktree, listCatalogModels, resolveSandboxProvider2, webhookNotifier, ensureLlmEndpointRunning, listAgentAdapters, deviceMirrorSync) {
|
|
53614
|
+
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
53615
|
const json3 = (status, body) => {
|
|
53286
53616
|
res.writeHead(status, { "content-type": "application/json" });
|
|
53287
53617
|
res.end(JSON.stringify(body));
|
|
@@ -54329,7 +54659,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
54329
54659
|
}
|
|
54330
54660
|
const fresh = registry.get(desc.id) ?? desc;
|
|
54331
54661
|
const pendingPermissions = registry.listPendingPermissions({ sessionId: fresh.id }).length;
|
|
54332
|
-
json3(200, buildSessionCapabilities(fresh, pendingPermissions));
|
|
54662
|
+
json3(200, buildSessionCapabilities(fresh, pendingPermissions, mcpObservations?.get(fresh.id)));
|
|
54333
54663
|
return true;
|
|
54334
54664
|
}
|
|
54335
54665
|
const renameMatch = path.match(/^\/sessions\/([^/]+)$/);
|
|
@@ -56690,6 +57020,11 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
|
|
|
56690
57020
|
sendRepresentation(req, res, rep, headers);
|
|
56691
57021
|
}
|
|
56692
57022
|
var APP_UI_PAGE_RE = /^\/apps\/(.+)\/ui\/?$/;
|
|
57023
|
+
function normalizeStoreRedirectUrl(rawUrl) {
|
|
57024
|
+
const queryIndex = rawUrl.indexOf("?");
|
|
57025
|
+
const query = queryIndex >= 0 ? rawUrl.slice(queryIndex) : "";
|
|
57026
|
+
return `/apps/${STORE_PANEL_APP_ID.split("/")[0]}/${STORE_PANEL_APP_ID.split("/")[1]}/ui${query}`;
|
|
57027
|
+
}
|
|
56693
57028
|
var APP_UI_ASSET_RE = /^\/apps\/(.+?)\/ui\/assets\/([^/]*)$/;
|
|
56694
57029
|
function isAppUiShellRequest(method, path) {
|
|
56695
57030
|
if (method !== "GET") return false;
|
|
@@ -66904,6 +67239,7 @@ async function createGateway(opts) {
|
|
|
66904
67239
|
});
|
|
66905
67240
|
const sessionEvents = createSessionEventBus();
|
|
66906
67241
|
const eventRing = createEventRing();
|
|
67242
|
+
const mcpObservations = createMcpObservationStore();
|
|
66907
67243
|
eventRing.wire(sessionEvents);
|
|
66908
67244
|
const webhookNotifier = createWebhookNotifier();
|
|
66909
67245
|
sessionEvents.onAny((ev) => webhookNotifier.onSessionEvent(ev));
|
|
@@ -67235,6 +67571,11 @@ async function createGateway(opts) {
|
|
|
67235
67571
|
webOrigins: daemonConfig.approvals?.webOrigins ?? []
|
|
67236
67572
|
});
|
|
67237
67573
|
wireSupervisorNotify({ registry: sessions, sessionEvents });
|
|
67574
|
+
wireMcpDegradedWarnings({
|
|
67575
|
+
sessionEvents,
|
|
67576
|
+
getSession: (id) => sessions.get(id),
|
|
67577
|
+
store: mcpObservations
|
|
67578
|
+
});
|
|
67238
67579
|
const activityProjector = createActivityProjector({
|
|
67239
67580
|
registry: sessions,
|
|
67240
67581
|
sessionEvents,
|
|
@@ -67435,6 +67776,7 @@ async function createGateway(opts) {
|
|
|
67435
67776
|
}
|
|
67436
67777
|
registerSessionTools(server, {
|
|
67437
67778
|
registry: sessions,
|
|
67779
|
+
mcpObservations,
|
|
67438
67780
|
workspace,
|
|
67439
67781
|
mcpProxy,
|
|
67440
67782
|
ptyEnabled: opts.spawnPty != null,
|
|
@@ -67539,6 +67881,21 @@ async function createGateway(opts) {
|
|
|
67539
67881
|
registerAppDataTools(server, { appRegistry });
|
|
67540
67882
|
registerAppExternalTools(server, { appRegistry });
|
|
67541
67883
|
registerTelegramBotTools(server, { telegramCreds: telegramBotCreds });
|
|
67884
|
+
const listCatalogRows = async () => {
|
|
67885
|
+
const rows = await dispatchTool("app_catalog", {});
|
|
67886
|
+
if (typeof rows !== "string") return [];
|
|
67887
|
+
try {
|
|
67888
|
+
const parsed = JSON.parse(rows);
|
|
67889
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
67890
|
+
} catch {
|
|
67891
|
+
return [];
|
|
67892
|
+
}
|
|
67893
|
+
};
|
|
67894
|
+
const listInstalledRows = () => {
|
|
67895
|
+
const apps = appRegistry.listApps();
|
|
67896
|
+
return apps;
|
|
67897
|
+
};
|
|
67898
|
+
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
67899
|
const listSessionsFiltered = (filter) => {
|
|
67543
67900
|
let rows = sessions.list().filter((s) => s.kind !== "command");
|
|
67544
67901
|
if (filter === "running") {
|
|
@@ -67579,7 +67936,14 @@ async function createGateway(opts) {
|
|
|
67579
67936
|
reviewRunner,
|
|
67580
67937
|
{ ...input, includeRunning: true },
|
|
67581
67938
|
{ resolveSubtree: (sessionId) => [...collectSubtree(sessionId, sessions.list({ includeArchived: true }))] }
|
|
67582
|
-
)
|
|
67939
|
+
),
|
|
67940
|
+
// Store widget's read paths — app_catalog's rows (via the SAME
|
|
67941
|
+
// `dispatchTool` the panel's own apply verbs will route through, so
|
|
67942
|
+
// its snapshot can never disagree with what the tool call returns),
|
|
67943
|
+
// and the registry's installed apps.
|
|
67944
|
+
listCatalog: listCatalogRows,
|
|
67945
|
+
listInstalled: listInstalledRows,
|
|
67946
|
+
listUpdates: listUpdateRows
|
|
67583
67947
|
}),
|
|
67584
67948
|
// Same ptyEnabled gate as terminal_start/terminal_input/… in
|
|
67585
67949
|
// session-tools.ts — the panel would be able to open the WS but
|
|
@@ -67745,6 +68109,7 @@ async function createGateway(opts) {
|
|
|
67745
68109
|
const http = await startHttpServer({
|
|
67746
68110
|
port,
|
|
67747
68111
|
bind: opts.bind,
|
|
68112
|
+
mcpObservations,
|
|
67748
68113
|
// Auth is read on every request via this getter. `opts.auth`
|
|
67749
68114
|
// (when provided) is the startup default and represents the
|
|
67750
68115
|
// operator's intent — bearer-only deployments stay bearer-only
|