@wenathlan/extension 1.1.54 → 1.1.56

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.
@@ -4702,7 +4702,7 @@ function renderagentprotocol(context) {
4702
4702
  badge.className = "sessionbadge";
4703
4703
  badge.dataset.restored = "false";
4704
4704
  badge.textContent = client.paired ? "paired" : "waiting for approval";
4705
- headline.append(`${client.id} \xB7 ${client.transport}${client.capabilities !== void 0 ? ` \xB7 protocol ${client.capabilities.protocolversion} \xB7 tool floor ${client.capabilities.toolversion} \xB7 ${client.capabilities.tools} tools` : " \xB7 not negotiated yet"}`, badge);
4705
+ headline.append(`${client.id} \xB7 ${client.transport}${client.fingerprint !== void 0 ? ` \xB7 ${client.fingerprint.slice(0, 12)}` : ""}${client.capabilities !== void 0 ? ` \xB7 protocol ${client.capabilities.protocolversion} \xB7 tool floor ${client.toolfloor ?? client.capabilities.toolversion} \xB7 ${client.capabilities.tools} tools` : " \xB7 not negotiated yet"}`, badge);
4706
4706
  row.append(headline);
4707
4707
  const actions = document.createElement("div");
4708
4708
  actions.className = "actions";
@@ -4723,6 +4723,18 @@ function renderagentprotocol(context) {
4723
4723
  status(`Disconnected the client ${client.id}; its record stays for the audit trail.`);
4724
4724
  await refresh();
4725
4725
  }));
4726
+ const clienttokens = mcp.tokens.filter((token) => token.clientid === client.id && token.revokedat === void 0);
4727
+ for (const token of clienttokens) {
4728
+ const tokenline = document.createElement("p");
4729
+ const remaining = Math.max(0, Math.round((token.expiresat - Date.now()) / 1e3));
4730
+ tokenline.textContent = `Session token ${token.id.slice(0, 8)} \xB7 scopes ${token.scopes.join(", ") || "none"} \xB7 expires in ${Math.floor(remaining / 60)}m ${remaining % 60}s${token.revokedat !== void 0 ? " \xB7 revoked" : ""}.`;
4731
+ row.append(tokenline);
4732
+ }
4733
+ actions.append(" ", button("Revoke client", async () => {
4734
+ await request({ kind: "mcprevokeclient", clientid: client.id });
4735
+ status(`Revoked the client ${client.id}; its tokens stopped verifying at once.`);
4736
+ await refresh();
4737
+ }));
4726
4738
  row.append(actions);
4727
4739
  clientsbox.append(row);
4728
4740
  }
@@ -4732,6 +4744,155 @@ function renderagentprotocol(context) {
4732
4744
  clientsbox.append(empty);
4733
4745
  }
4734
4746
  agentprotocolroot.append(clientsbox);
4747
+ const approvalsbox = document.createElement("details");
4748
+ approvalsbox.className = "sessiongroup";
4749
+ approvalsbox.open = true;
4750
+ const approvalssummary = document.createElement("summary");
4751
+ const pendingapprovals = mcp.approvals.filter((request2) => request2.state === "pending");
4752
+ approvalssummary.textContent = `Approval gates (${pendingapprovals.length} pending, ${mcp.approvals.length - pendingapprovals.length} resolved)`;
4753
+ approvalsbox.append(approvalssummary);
4754
+ for (const gate of mcp.approvals.slice(0, 10)) {
4755
+ const card = document.createElement("div");
4756
+ card.className = "sessionrow";
4757
+ const identity = mcp.identities.find((entry) => entry.fingerprint === mcp.clients.find((client) => client.id === gate.clientid)?.fingerprint);
4758
+ const headline = document.createElement("p");
4759
+ const secrets = gate.secretfields ?? [];
4760
+ const shown = {};
4761
+ for (const [name, value] of Object.entries(gate.params)) shown[name] = secrets.includes(name) ? "[redacted]" : value;
4762
+ headline.textContent = `${gate.state === "pending" ? "PENDING" : gate.state.toUpperCase()} \xB7 ${identity?.displayname ?? gate.clientid} calls ${gate.tool} \xB7 ${gate.reason} Arguments: ${JSON.stringify(shown)}${gate.timeoutat !== void 0 ? ` \xB7 refuses by default at ${new Date(gate.timeoutat).toISOString()}` : ""}.`;
4763
+ card.append(headline);
4764
+ if (gate.state === "pending") {
4765
+ const actions = document.createElement("div");
4766
+ actions.className = "actions";
4767
+ actions.append(button("Approve", async () => {
4768
+ await request({ kind: "mcpapprovaldecision", approvalid: gate.id, approved: true });
4769
+ status(`Approved the ${gate.tool} call of the client ${gate.clientid}; the gate executed the held call.`);
4770
+ await refresh();
4771
+ }));
4772
+ actions.append(" ", button("Refuse", async () => {
4773
+ await request({ kind: "mcpapprovaldecision", approvalid: gate.id, approved: false });
4774
+ status(`Refused the ${gate.tool} call of the client ${gate.clientid}; the pending call never executes.`);
4775
+ await refresh();
4776
+ }));
4777
+ card.append(actions);
4778
+ }
4779
+ approvalsbox.append(card);
4780
+ }
4781
+ if (mcp.approvals.length === 0) {
4782
+ const empty = document.createElement("p");
4783
+ empty.textContent = "No approval gate yet; every sensitive call a remote client raises lands here with its full arguments before anything executes.";
4784
+ approvalsbox.append(empty);
4785
+ }
4786
+ agentprotocolroot.append(approvalsbox);
4787
+ const remotebox = document.createElement("details");
4788
+ remotebox.className = "sessiongroup";
4789
+ const remotesummary = document.createElement("summary");
4790
+ remotesummary.textContent = `Remote transport (${mcp.remote.channelsopen} live channel${mcp.remote.channelsopen === 1 ? "" : "s"}, ${mcp.remote.channelsdead} closed)`;
4791
+ remotebox.append(remotesummary);
4792
+ const remoteline = document.createElement("p");
4793
+ remoteline.textContent = `Endpoint ${mcp.remote.endpoint} \xB7 tls ${mcp.remote.tls.mode}${mcp.remote.tls.certificaterequired ? " (certificate required)" : ""}${mcp.remote.tls.verified ? " and verified" : " and unverified"} \xB7 ${mcp.remote.paired} of ${mcp.remote.clients} client${mcp.remote.clients === 1 ? "" : "s"} paired \xB7 ${mcp.remote.tokenslive} live token${mcp.remote.tokenslive === 1 ? "" : "s"}.`;
4794
+ remotebox.append(remoteline);
4795
+ for (const channel of mcp.channels.slice(0, 10)) {
4796
+ const line = document.createElement("p");
4797
+ const beatage = Math.max(0, Math.round((Date.now() - channel.lastbeatat) / 1e3));
4798
+ line.textContent = `Stream channel ${channel.id.slice(0, 12)} of ${channel.clientid} \xB7 ${channel.closedat === void 0 ? `heartbeat ${beatage}s ago` : `closed at ${new Date(channel.closedat).toISOString()}`}.`;
4799
+ remotebox.append(line);
4800
+ }
4801
+ for (const shake of mcp.handshakes.slice(0, 5)) {
4802
+ const line = document.createElement("p");
4803
+ line.textContent = `Auth handshake ${shake.method} of ${shake.clientid} \xB7 ${shake.outcome}${shake.outcome === "refused" ? " (consentrefused error, no pairing state leaked)" : ""} \xB7 ${new Date(shake.at).toISOString()}.`;
4804
+ remotebox.append(line);
4805
+ }
4806
+ const pairingline = document.createElement("p");
4807
+ pairingline.textContent = mcp.pairing.length > 0 ? `Pairing code ${mcp.pairing[0]?.code} for ${mcp.pairing[0]?.scopes.join(", ") || "no"} namespaces \xB7 expires at ${new Date(mcp.pairing[0]?.expiresat ?? Date.now()).toISOString()} \xB7 single use.` : "No pairing code pending; issue one while a session is live to pair a remote client.";
4808
+ remotebox.append(pairingline);
4809
+ const pairingactions = document.createElement("div");
4810
+ pairingactions.className = "actions";
4811
+ pairingactions.append(button("Issue pairing code", async () => {
4812
+ await request({ kind: "mcppairing", scopes: ["browser", "workflow", "memory", "system"] });
4813
+ status("Issued one single use pairing code; copy it to the remote client before its window closes.");
4814
+ await refresh();
4815
+ }));
4816
+ pairingactions.append(" ", button("Copy pairing code", async () => {
4817
+ const code = mcp.pairing[0]?.code ?? "";
4818
+ if (code === "") {
4819
+ status("No pending pairing code to copy.", true);
4820
+ return;
4821
+ }
4822
+ await navigator.clipboard.writeText(code).then(() => status(`Copied the pairing code ${code} to the clipboard.`)).catch(() => status(`Pairing code: ${code} (the clipboard permission was refused).`, true));
4823
+ }));
4824
+ remotebox.append(pairingactions);
4825
+ const tlsheadline = document.createElement("p");
4826
+ tlsheadline.textContent = "Tls certificate options of the remote transport: the mode and the reviewed sha-256 fingerprint stay user choices with no hardcoded certificate.";
4827
+ remotebox.append(tlsheadline);
4828
+ const tlsactions = document.createElement("div");
4829
+ tlsactions.className = "actions";
4830
+ const endpointinput = document.createElement("input");
4831
+ endpointinput.placeholder = "remote endpoint url";
4832
+ endpointinput.value = mcp.config.remoteaccess?.endpoint ?? "";
4833
+ const tlsmodeinput = document.createElement("input");
4834
+ tlsmodeinput.placeholder = "tls mode: off, on or required";
4835
+ tlsmodeinput.value = mcp.config.remoteaccess?.tls.mode ?? "off";
4836
+ const certinput = document.createElement("input");
4837
+ certinput.placeholder = "certificate sha-256 fingerprint";
4838
+ certinput.value = mcp.config.httpstream?.tls.certificatefingerprint ?? "";
4839
+ const maxclientsinput = document.createElement("input");
4840
+ maxclientsinput.placeholder = "max clients (empty: unbounded)";
4841
+ maxclientsinput.value = mcp.config.remoteaccess?.maxclients !== void 0 ? String(mcp.config.remoteaccess.maxclients) : "";
4842
+ const timeoutinput = document.createElement("input");
4843
+ timeoutinput.placeholder = "approval window ms";
4844
+ timeoutinput.value = mcp.config.remoteaccess?.approvaltimeout !== void 0 ? String(mcp.config.remoteaccess.approvaltimeout.windowms) : "";
4845
+ tlsactions.append(endpointinput, " ", tlsmodeinput, " ", certinput, " ", maxclientsinput, " ", timeoutinput, " ", button("Save remote config", async () => {
4846
+ await request({ kind: "mcpremoteconfig", endpoint: endpointinput.value, tlsmode: tlsmodeinput.value, ...certinput.value.trim() !== "" ? { certificatefingerprint: certinput.value } : {}, ...maxclientsinput.value.trim() !== "" ? { maxclients: Number(maxclientsinput.value) } : {}, ...timeoutinput.value.trim() !== "" ? { approvaltimeoutms: Number(timeoutinput.value) } : {}, reviewed: true });
4847
+ status(`Saved the remote transport config for ${endpointinput.value} with the ${tlsmodeinput.value} tls mode.`);
4848
+ await refresh();
4849
+ }));
4850
+ remotebox.append(tlsactions);
4851
+ agentprotocolroot.append(remotebox);
4852
+ const allowlistbox = document.createElement("details");
4853
+ allowlistbox.className = "sessiongroup";
4854
+ const allowlistsummary = document.createElement("summary");
4855
+ allowlistsummary.textContent = `Client allowlist (${mcp.allowlist.length} entr${mcp.allowlist.length === 1 ? "y" : "ies"})`;
4856
+ allowlistbox.append(allowlistsummary);
4857
+ for (const entry of mcp.allowlist) {
4858
+ const line = document.createElement("p");
4859
+ line.textContent = `${entry.displayname} \xB7 ${entry.fingerprint.slice(0, 12)} \xB7 namespaces ${entry.namespaces.join(", ") || "none"} \xB7 granted ${new Date(entry.grantedat).toISOString()} \xB7 ${entry.history.length} grant change${entry.history.length === 1 ? "" : "s"}.`;
4860
+ allowlistbox.append(line);
4861
+ const actions = document.createElement("div");
4862
+ actions.className = "actions";
4863
+ actions.append(button(`Rescope to browser+memory`, async () => {
4864
+ await request({ kind: "mcpallowlist", fingerprint: entry.fingerprint, displayname: entry.displayname, namespaces: ["browser", "memory"] });
4865
+ status(`Rescoped the allowlist entry ${entry.displayname} to the browser and memory namespaces.`);
4866
+ await refresh();
4867
+ }));
4868
+ actions.append(" ", button("Refuse entry", async () => {
4869
+ await request({ kind: "mcpallowlist", fingerprint: entry.fingerprint, remove: true });
4870
+ status(`Refused the allowlist entry ${entry.displayname}; its fingerprint stops passing the check.`);
4871
+ await refresh();
4872
+ }));
4873
+ allowlistbox.append(actions);
4874
+ }
4875
+ const allowactions = document.createElement("div");
4876
+ allowactions.className = "actions";
4877
+ const fingerprintinput = document.createElement("input");
4878
+ fingerprintinput.placeholder = "client fingerprint";
4879
+ const displayinput = document.createElement("input");
4880
+ displayinput.placeholder = "display name";
4881
+ const scopesinput = document.createElement("input");
4882
+ scopesinput.placeholder = "namespaces: browser,workflow,memory,system";
4883
+ allowactions.append(fingerprintinput, " ", displayinput, " ", scopesinput, " ", button("Allow client", async () => {
4884
+ const namespaces = scopesinput.value.split(",").map((scope) => scope.trim()).filter((scope) => scope !== "");
4885
+ await request({ kind: "mcpallowlist", fingerprint: fingerprintinput.value, ...displayinput.value.trim() !== "" ? { displayname: displayinput.value } : {}, namespaces });
4886
+ status(`Allowed the client ${displayinput.value || fingerprintinput.value} the ${namespaces.join(", ") || "no"} namespaces.`);
4887
+ await refresh();
4888
+ }));
4889
+ allowlistbox.append(allowactions);
4890
+ if (mcp.allowlist.length === 0) {
4891
+ const empty = document.createElement("p");
4892
+ empty.textContent = "No allowlist entry yet; a remote client pairs through the one time code or the user allows a known fingerprint with its namespace scopes.";
4893
+ allowlistbox.append(empty);
4894
+ }
4895
+ agentprotocolroot.append(allowlistbox);
4735
4896
  const catalogbox = document.createElement("details");
4736
4897
  catalogbox.className = "sessiongroup";
4737
4898
  const catalogsummary = document.createElement("summary");
@@ -4752,25 +4913,254 @@ function renderagentprotocol(context) {
4752
4913
  domain.append(domainsummary);
4753
4914
  for (const tool of tools) {
4754
4915
  const line = document.createElement("p");
4755
- line.textContent = `${tool.name} v${tool.version} \xB7 ${tool.risk} \xB7 ${tool.description.split(".")[0] ?? tool.description}${tool.consentmeta !== void 0 ? ` \xB7 review: ${tool.consentmeta}` : ""}`;
4916
+ line.textContent = `${tool.name} v${tool.version} \xB7 ${tool.risk} \xB7 ${tool.description.split(".")[0] ?? tool.description}${tool.consentmeta !== void 0 ? ` \xB7 ${tool.consentmeta.riskclass} \xB7 approval gate ${tool.consentmeta.approvalrequired ? "required" : "off"} \xB7 ${tool.consentmeta.originscope} scope \xB7 review: ${tool.consentmeta.review}` : ""}`;
4756
4917
  domain.append(line);
4757
4918
  }
4758
4919
  catalogbox.append(domain);
4759
4920
  }
4760
4921
  agentprotocolroot.append(catalogbox);
4922
+ const runtimebox = document.createElement("details");
4923
+ runtimebox.className = "sessiongroup";
4924
+ const runtimesummary = document.createElement("summary");
4925
+ runtimesummary.textContent = `Call runtime (${mcp.inflight?.length ?? 0} in flight, ${mcp.dryruntoggle === true ? "dry run armed" : "dry run off"}, ${mcp.mocks?.length ?? 0} mock${(mcp.mocks?.length ?? 0) === 1 ? "" : "s"})`;
4926
+ runtimebox.append(runtimesummary);
4927
+ for (const call of mcp.inflight ?? []) {
4928
+ const line = document.createElement("p");
4929
+ line.textContent = `In flight ${call.tool} of ${call.clientid} \xB7 call ${call.callid.slice(0, 12)} \xB7 ${call.chunks} chunk${call.chunks === 1 ? "" : "s"} \xB7 open ${Math.max(0, Math.round((Date.now() - call.startedat) / 1e3))}s${call.dryrun === true ? " \xB7 dry run" : ""}.`;
4930
+ runtimebox.append(line);
4931
+ const actions = document.createElement("div");
4932
+ actions.className = "actions";
4933
+ actions.append(button("Cancel call", async () => {
4934
+ await request({ kind: "mcpcancelcall", callid: call.callid });
4935
+ status(`Cancelled the in flight ${call.tool} call; the partial result stays preserved.`);
4936
+ await refresh();
4937
+ }));
4938
+ runtimebox.append(actions);
4939
+ }
4940
+ if ((mcp.inflight?.length ?? 0) === 0) {
4941
+ const empty = document.createElement("p");
4942
+ empty.textContent = "No in flight tool call; a cancellation frame aborts a running call and preserves its partial result.";
4943
+ runtimebox.append(empty);
4944
+ }
4945
+ const runtimeactions = document.createElement("div");
4946
+ runtimeactions.className = "actions";
4947
+ runtimeactions.append(button(mcp.dryruntoggle === true ? "Disarm dry run" : "Dry run next call", async () => {
4948
+ await request({ kind: "mcpdryrun", enabled: mcp.dryruntoggle !== true });
4949
+ status(mcp.dryruntoggle === true ? "Disarmed the tool dry run; the next call executes behind the gates." : "Armed the tool dry run of the next call: it evaluates arguments and consent with no side effects.");
4950
+ await refresh();
4951
+ }));
4952
+ const mockinput = document.createElement("input");
4953
+ mockinput.placeholder = "tool name to mock (test context)";
4954
+ runtimeactions.append(" ", mockinput, " ", button("Add mock", async () => {
4955
+ if (mockinput.value.trim() === "") {
4956
+ status("The tool mock needs the namespaced tool name.", true);
4957
+ return;
4958
+ }
4959
+ await request({ kind: "mcpmock", tool: mockinput.value.trim() });
4960
+ status(`Registered the ${mockinput.value.trim()} tool mock of a test context; it never touches the browser.`);
4961
+ await refresh();
4962
+ }));
4963
+ runtimebox.append(runtimeactions);
4964
+ for (const mock of mcp.mocks ?? []) {
4965
+ const line = document.createElement("p");
4966
+ line.textContent = `Tool mock ${mock.tool} \xB7 test context \xB7 created ${new Date(mock.createdat).toISOString()}.`;
4967
+ runtimebox.append(line);
4968
+ const actions = document.createElement("div");
4969
+ actions.className = "actions";
4970
+ actions.append(button("Remove mock", async () => {
4971
+ await request({ kind: "mcpmock", tool: mock.tool, remove: true });
4972
+ status(`Removed the ${mock.tool} tool mock; the tool returns to the real gates.`);
4973
+ await refresh();
4974
+ }));
4975
+ runtimebox.append(actions);
4976
+ }
4977
+ agentprotocolroot.append(runtimebox);
4978
+ const limitsbox = document.createElement("details");
4979
+ limitsbox.className = "sessiongroup";
4980
+ const limitssummary = document.createElement("summary");
4981
+ limitssummary.textContent = `Rate limits (${mcp.limits?.length ?? 0} client${(mcp.limits?.length ?? 0) === 1 ? "" : "s"} limited)`;
4982
+ limitsbox.append(limitssummary);
4983
+ for (const limit of mcp.limits ?? []) {
4984
+ const line = document.createElement("p");
4985
+ const used = limit.budget !== void 0 ? Math.min(100, Math.round(limit.used / limit.budget * 100)) : 0;
4986
+ const bar = limit.budget !== void 0 ? " " + "\u2588".repeat(Math.round(used / 10)) + "\xB7".repeat(10 - Math.round(used / 10)) : "";
4987
+ line.textContent = `${limit.clientid} \xB7 ${limit.used}${limit.budget !== void 0 ? ` of ${limit.budget}` : " of unbounded"} call${limit.used === 1 ? "" : "s"} in the ${limit.windowms}ms window${bar} \xB7 resets in ${Math.max(0, Math.round((limit.windowstartedat + limit.windowms - Date.now()) / 1e3))}s.`;
4988
+ limitsbox.append(line);
4989
+ const actions = document.createElement("div");
4990
+ actions.className = "actions";
4991
+ actions.append(button("Remove limit", async () => {
4992
+ await request({ kind: "mcpratelimit", clientid: limit.clientid, remove: true });
4993
+ status(`Removed the rate limit of the client ${limit.clientid}; the client stays unbounded because no silent default applies.`);
4994
+ await refresh();
4995
+ }));
4996
+ limitsbox.append(actions);
4997
+ }
4998
+ const limtactions = document.createElement("div");
4999
+ limtactions.className = "actions";
5000
+ const limitclientinput = document.createElement("input");
5001
+ limitclientinput.placeholder = "client id";
5002
+ const limitwindowinput = document.createElement("input");
5003
+ limitwindowinput.placeholder = "window ms";
5004
+ const limitbudgetinput = document.createElement("input");
5005
+ limitbudgetinput.placeholder = "call budget (empty: unbounded)";
5006
+ limtactions.append(limitclientinput, " ", limitwindowinput, " ", limitbudgetinput, " ", button("Limit client", async () => {
5007
+ if (limitclientinput.value.trim() === "") {
5008
+ status("The rate limit needs the client id.", true);
5009
+ return;
5010
+ }
5011
+ await request({ kind: "mcpratelimit", clientid: limitclientinput.value.trim(), windowms: Number(limitwindowinput.value) || 6e4, ...limitbudgetinput.value.trim() !== "" ? { budget: Number(limitbudgetinput.value) } : {} });
5012
+ status(`Limited the client ${limitclientinput.value.trim()} inside its window; the budget stays the user choice.`);
5013
+ await refresh();
5014
+ }));
5015
+ limitsbox.append(limtactions);
5016
+ if ((mcp.limits?.length ?? 0) === 0) {
5017
+ const empty = document.createElement("p");
5018
+ empty.textContent = "No rate limit configured; an absent limit keeps the client unbounded because no silent default ever applies.";
5019
+ limitsbox.append(empty);
5020
+ }
5021
+ agentprotocolroot.append(limitsbox);
5022
+ const streambox = document.createElement("details");
5023
+ streambox.className = "sessiongroup";
5024
+ const streamsummary = document.createElement("summary");
5025
+ streamsummary.textContent = `Streaming and progress (${mcp.chunks?.length ?? 0} chunk${(mcp.chunks?.length ?? 0) === 1 ? "" : "s"}, ${mcp.progressnotices?.length ?? 0} notice${(mcp.progressnotices?.length ?? 0) === 1 ? "" : "s"})`;
5026
+ streambox.append(streamsummary);
5027
+ for (const notice of (mcp.progressnotices ?? []).slice(0, 5)) {
5028
+ const line = document.createElement("p");
5029
+ line.textContent = `Progress ${notice.percent !== void 0 ? `${notice.percent}%` : ""} of the call ${notice.callid.slice(0, 12)} \xB7 ${notice.message}${notice.cancellable ? " \xB7 cancellable" : ""} \xB7 ${new Date(notice.at).toISOString()}.`;
5030
+ streambox.append(line);
5031
+ }
5032
+ for (const chunk of (mcp.chunks ?? []).slice(0, 10)) {
5033
+ const line = document.createElement("p");
5034
+ line.textContent = `Chunk ${chunk.seq}${chunk.done ? " (done)" : ""} of the call ${chunk.callid.slice(0, 12)} \xB7 ${chunk.content.slice(0, 60)}${chunk.content.length > 60 ? "\u2026" : ""}`;
5035
+ streambox.append(line);
5036
+ }
5037
+ if ((mcp.chunks?.length ?? 0) === 0 && (mcp.progressnotices?.length ?? 0) === 0) {
5038
+ const empty = document.createElement("p");
5039
+ empty.textContent = "No streamed result yet; a streaming call delivers its content in ordered chunks with progress notices in between.";
5040
+ streambox.append(empty);
5041
+ }
5042
+ agentprotocolroot.append(streambox);
5043
+ const samplingbox = document.createElement("details");
5044
+ samplingbox.className = "sessiongroup";
5045
+ const samplingsummary = document.createElement("summary");
5046
+ samplingsummary.textContent = `Sampling callbacks (${mcp.sampling?.length ?? 0})`;
5047
+ samplingbox.append(samplingsummary);
5048
+ for (const callback of mcp.sampling ?? []) {
5049
+ const line = document.createElement("p");
5050
+ line.textContent = `${callback.state} \xB7 client ${callback.clientid} \xB7 requested ${new Date(callback.requestedat).toISOString()}${callback.answeredat !== void 0 ? ` \xB7 answered ${new Date(callback.answeredat).toISOString()}` : ""}${callback.pagecontent !== void 0 ? " \xB7 page content granted" : " \xB7 page content stripped"}.`;
5051
+ samplingbox.append(line);
5052
+ const payload = document.createElement("p");
5053
+ payload.textContent = `Prompt: ${callback.prompt}${callback.system !== void 0 ? ` System: ${callback.system}` : ""}${callback.answer !== void 0 ? ` Answer: ${callback.answer}` : ""}`;
5054
+ samplingbox.append(payload);
5055
+ if (callback.state === "pending") {
5056
+ const actions = document.createElement("div");
5057
+ actions.className = "actions";
5058
+ const answerinput = document.createElement("input");
5059
+ answerinput.placeholder = "client answer for the local test";
5060
+ actions.append(answerinput, " ", button("Record answer", async () => {
5061
+ await request({ kind: "mcpsampling", respond: true, samplingid: callback.id, ...answerinput.value.trim() !== "" ? { answer: answerinput.value.trim() } : { refused: true } });
5062
+ status(`Closed the sampling round trip ${callback.id.slice(0, 12)}.`);
5063
+ await refresh();
5064
+ }));
5065
+ samplingbox.append(actions);
5066
+ }
5067
+ }
5068
+ if ((mcp.sampling?.length ?? 0) === 0) {
5069
+ const empty = document.createElement("p");
5070
+ empty.textContent = "No sampling callback yet; a callback asks a paired client model for a completion and the page content rides it only behind the user grant.";
5071
+ samplingbox.append(empty);
5072
+ }
5073
+ const samplingactions = document.createElement("div");
5074
+ samplingactions.className = "actions";
5075
+ const samplingclientinput = document.createElement("input");
5076
+ samplingclientinput.placeholder = "client id";
5077
+ const samplingpromptinput = document.createElement("input");
5078
+ samplingpromptinput.placeholder = "prompt for the client model";
5079
+ const samplingpageinput = document.createElement("input");
5080
+ samplingpageinput.placeholder = "page content (empty: none)";
5081
+ samplingactions.append(samplingclientinput, " ", samplingpromptinput, " ", samplingpageinput, " ", button("Ask client model", async () => {
5082
+ if (samplingclientinput.value.trim() === "" || samplingpromptinput.value.trim() === "") {
5083
+ status("The sampling callback needs the client id and the prompt.", true);
5084
+ return;
5085
+ }
5086
+ await request({ kind: "mcpsampling", clientid: samplingclientinput.value.trim(), prompt: samplingpromptinput.value.trim(), ...samplingpageinput.value.trim() !== "" ? { pagecontent: samplingpageinput.value.trim(), pagegrant: true } : {} });
5087
+ status(`Sent one sampling callback to the client ${samplingclientinput.value.trim()}; the exact payload rides this view.`);
5088
+ await refresh();
5089
+ }));
5090
+ samplingbox.append(samplingactions);
5091
+ agentprotocolroot.append(samplingbox);
5092
+ const subsbox = document.createElement("details");
5093
+ subsbox.className = "sessiongroup";
5094
+ const subssummary = document.createElement("summary");
5095
+ subssummary.textContent = `Subscriptions (${mcp.subscriptions?.length ?? 0} event, ${mcp.watches?.length ?? 0} resource)`;
5096
+ subsbox.append(subssummary);
5097
+ for (const subscription of mcp.subscriptions ?? []) {
5098
+ const line = document.createElement("p");
5099
+ line.textContent = `Client ${subscription.clientid} \xB7 kinds ${subscription.kinds.join(", ")}${subscription.origin !== void 0 ? ` \xB7 origin ${subscription.origin}` : ""}${subscription.tool !== void 0 ? ` \xB7 tool ${subscription.tool}` : ""} \xB7 subscribed ${new Date(subscription.createdat).toISOString()}${subscription.lastdeliveredat !== void 0 ? ` \xB7 last delivery ${new Date(subscription.lastdeliveredat).toISOString()}` : ""}.`;
5100
+ subsbox.append(line);
5101
+ const actions = document.createElement("div");
5102
+ actions.className = "actions";
5103
+ actions.append(button("Unsubscribe", async () => {
5104
+ await request({ kind: "mcpsubscribe", clientid: subscription.clientid, subscriptionid: subscription.id, unsubscribe: true });
5105
+ status(`Cancelled the event subscription of the client ${subscription.clientid}.`);
5106
+ await refresh();
5107
+ }));
5108
+ subsbox.append(actions);
5109
+ }
5110
+ for (const watch of mcp.watches ?? []) {
5111
+ const line = document.createElement("p");
5112
+ line.textContent = `Client ${watch.clientid} watches the ${watch.resource} resource \xB7 since ${new Date(watch.createdat).toISOString()}${watch.lastdeliveredat !== void 0 ? ` \xB7 last delta ${new Date(watch.lastdeliveredat).toISOString()}` : ""}.`;
5113
+ subsbox.append(line);
5114
+ const actions = document.createElement("div");
5115
+ actions.className = "actions";
5116
+ actions.append(button("Unwatch", async () => {
5117
+ await request({ kind: "mcpresource", clientid: watch.clientid, watchid: watch.id, unwatch: true });
5118
+ status(`Cancelled the ${watch.resource} resource watcher of the client ${watch.clientid}.`);
5119
+ await refresh();
5120
+ }));
5121
+ subsbox.append(actions);
5122
+ }
5123
+ if ((mcp.subscriptions?.length ?? 0) === 0 && (mcp.watches?.length ?? 0) === 0) {
5124
+ const empty = document.createElement("p");
5125
+ empty.textContent = "No subscription yet; a paired client subscribes its event kinds and watches page state resources through the frame intake.";
5126
+ subsbox.append(empty);
5127
+ }
5128
+ agentprotocolroot.append(subsbox);
5129
+ const batchbox = document.createElement("details");
5130
+ batchbox.className = "sessiongroup";
5131
+ const batchsummary = document.createElement("summary");
5132
+ batchsummary.textContent = `Batch calls (${mcp.batches?.length ?? 0})`;
5133
+ batchbox.append(batchsummary);
5134
+ for (const batch of mcp.batches ?? []) {
5135
+ const line = document.createElement("p");
5136
+ const done = batch.outcomes.length;
5137
+ line.textContent = `${batch.state} \xB7 client ${batch.clientid} \xB7 ${done} of ${batch.calls.length} member${batch.calls.length === 1 ? "" : "s"} done \xB7 stop on error ${batch.stoponerror ? "on" : "off"} \xB7 created ${new Date(batch.createdat).toISOString()}.`;
5138
+ batchbox.append(line);
5139
+ for (const outcome of batch.outcomes.slice(0, 10)) {
5140
+ const item = document.createElement("p");
5141
+ item.textContent = ` ${outcome.ok ? "ran" : "failed"} \xB7 ${outcome.tool} (${outcome.callid}).`;
5142
+ batchbox.append(item);
5143
+ }
5144
+ }
5145
+ if ((mcp.batches?.length ?? 0) === 0) {
5146
+ const empty = document.createElement("p");
5147
+ empty.textContent = "No batch call yet; an ordered batch runs its members behind the gates and stops at the first error when the flag requests it.";
5148
+ batchbox.append(empty);
5149
+ }
5150
+ agentprotocolroot.append(batchbox);
4761
5151
  const callsbox = document.createElement("details");
4762
5152
  callsbox.className = "sessiongroup";
4763
5153
  const callssummary = document.createElement("summary");
4764
- callssummary.textContent = `Recent tool calls (${mcp.calls.length})`;
5154
+ callssummary.textContent = `Audited tool calls (${(mcp.calllog ?? mcp.calls).length} shown, every call logged)`;
4765
5155
  callsbox.append(callssummary);
4766
- for (const call of mcp.calls.slice(0, 25)) {
5156
+ for (const call of (mcp.calllog ?? mcp.calls).slice(0, 25)) {
4767
5157
  const line = document.createElement("p");
4768
- line.textContent = `${new Date(call.at).toISOString()} \xB7 ${call.clientid} \xB7 ${call.tool} \xB7 ${call.origin} \xB7 ${call.ok ? "ran behind the gates" : `refused${call.code !== void 0 ? ` (${call.code})` : ""}`}`;
5158
+ line.textContent = `${new Date(call.at).toISOString()} \xB7 ${call.clientid} \xB7 ${call.tool} \xB7 ${call.origin} \xB7 ${call.ok ? "ran behind the gates" : `refused${call.code !== void 0 ? ` (${call.code})` : ""}`}${call.dryrun === true ? " \xB7 dry run" : ""}${call.mocked === true ? " \xB7 mocked" : ""}${call.batchid !== void 0 ? " \xB7 batch member" : ""}${call.idempotencykey !== void 0 ? ` \xB7 key ${call.idempotencykey.slice(0, 10)}` : ""}${call.replayed === true ? " \xB7 idempotent replay" : ""}`;
4769
5159
  callsbox.append(line);
4770
5160
  }
4771
- if (mcp.calls.length === 0) {
5161
+ if ((mcp.calllog ?? mcp.calls).length === 0) {
4772
5162
  const empty = document.createElement("p");
4773
- empty.textContent = "No tool call yet; every call records the client, tool and outcome without payloads.";
5163
+ empty.textContent = "No tool call yet; every call records the client, tool, outcome and idempotency key without payloads.";
4774
5164
  callsbox.append(empty);
4775
5165
  }
4776
5166
  agentprotocolroot.append(callsbox);