@adapt-toolkit/a2adapt 0.11.4 → 0.11.5
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/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +390 -28
- package/dist/mufl_code/D317A840D97E3B17E4EC644D50BDBB19DE3B78F9941C25853E1F86164A234593.muflo +0 -0
- package/dist/mufl_code/actor.mu +387 -7
- package/package.json +1 -1
- package/dist/mufl_code/264E6E0FF6F2C1587AD5725F9B2E788CE792B617B6141D097F38CBB41EF3AB6A.muflo +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "a2adapt",
|
|
4
4
|
"displayName": "a2adapt",
|
|
5
5
|
"description": "Secure agent-to-agent communication channel over ADAPT: self-sovereign pubkey identity, end-to-end encryption, plan-first execution.",
|
|
6
|
-
"version": "0.11.
|
|
6
|
+
"version": "0.11.5",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Adapt Toolkit"
|
|
9
9
|
},
|
package/dist/index.js
CHANGED
|
@@ -22429,7 +22429,7 @@ var StreamableHTTPServerTransport = class {
|
|
|
22429
22429
|
// src/index.ts
|
|
22430
22430
|
import { resolve as resolve2, join as join2, dirname as dirname2, isAbsolute } from "node:path";
|
|
22431
22431
|
import { fileURLToPath } from "node:url";
|
|
22432
|
-
import { randomBytes, randomUUID } from "node:crypto";
|
|
22432
|
+
import { randomBytes, randomInt, randomUUID } from "node:crypto";
|
|
22433
22433
|
import { createServer as createHttpServer } from "node:http";
|
|
22434
22434
|
import * as fs2 from "node:fs";
|
|
22435
22435
|
import { brotliCompressSync, brotliDecompressSync, constants as zlibConstants } from "node:zlib";
|
|
@@ -22512,7 +22512,7 @@ function writeIdentityFile(target, opts, overwrite = false) {
|
|
|
22512
22512
|
}
|
|
22513
22513
|
|
|
22514
22514
|
// src/index.ts
|
|
22515
|
-
var VERSION = true ? "0.11.
|
|
22515
|
+
var VERSION = true ? "0.11.5" : "0.0.0-dev";
|
|
22516
22516
|
var CONFIG = loadConfig();
|
|
22517
22517
|
var STATE_DIR = CONFIG.stateDir;
|
|
22518
22518
|
var BROKER_URL = CONFIG.brokerUrl;
|
|
@@ -22668,7 +22668,8 @@ function describeIdentity(id) {
|
|
|
22668
22668
|
bio: v.Reduce("bio").Visualize(),
|
|
22669
22669
|
roleId: v.Reduce("role_id").Visualize(),
|
|
22670
22670
|
rootCid: v.Reduce("root_cid").Visualize(),
|
|
22671
|
-
rootName: v.Reduce("root_name").Visualize()
|
|
22671
|
+
rootName: v.Reduce("root_name").Visualize(),
|
|
22672
|
+
monitoringEnabled: v.Reduce("monitoring_enabled").GetBoolean()
|
|
22672
22673
|
};
|
|
22673
22674
|
}
|
|
22674
22675
|
async function delegateRole(root, role) {
|
|
@@ -22735,6 +22736,282 @@ async function sendViaLocalBook(id, contact, text) {
|
|
|
22735
22736
|
});
|
|
22736
22737
|
return `"${entry.name}" was not a contact yet \u2014 connected via the local contact book and sent the message with the introduction. If "${entry.name}" requires approval for local introductions, delivery completes once they approve.`;
|
|
22737
22738
|
}
|
|
22739
|
+
function monitoringStatus(id) {
|
|
22740
|
+
const v = readonlyTx(id, "::actor::get_monitoring_status");
|
|
22741
|
+
return {
|
|
22742
|
+
enabled: v.Reduce("monitoring_enabled").GetBoolean(),
|
|
22743
|
+
proxyCid: v.Reduce("proxy_cid").Visualize(),
|
|
22744
|
+
proxyPending: v.Reduce("proxy_pending").GetBoolean(),
|
|
22745
|
+
copiesQueued: parseInt(v.Reduce("copies_queued").Visualize(), 10) || 0,
|
|
22746
|
+
controlQueued: parseInt(v.Reduce("control_queued").Visualize(), 10) || 0
|
|
22747
|
+
};
|
|
22748
|
+
}
|
|
22749
|
+
async function setAgentMonitoring(root, role, enabled) {
|
|
22750
|
+
if (enabled) {
|
|
22751
|
+
const rootAd = exportAdBlob(root);
|
|
22752
|
+
await mutatingTx(role, "::actor::connect_sibling", {
|
|
22753
|
+
name: root.name,
|
|
22754
|
+
target_ad: role.pw.packet.NewBinaryFromBuffer(rootAd)
|
|
22755
|
+
});
|
|
22756
|
+
}
|
|
22757
|
+
const roleAd = exportAdBlob(role);
|
|
22758
|
+
const authData = await mutatingTx(root, "::actor::sign_monitoring_auth", {
|
|
22759
|
+
role_ad: root.pw.packet.NewBinaryFromBuffer(roleAd),
|
|
22760
|
+
enabled
|
|
22761
|
+
});
|
|
22762
|
+
const authBlob = Buffer.from(authData.Reduce("auth").GetBinary());
|
|
22763
|
+
await mutatingTx(role, "::actor::set_monitoring", {
|
|
22764
|
+
auth: role.pw.packet.NewBinaryFromBuffer(authBlob)
|
|
22765
|
+
});
|
|
22766
|
+
log(`[${role.name}] monitoring ${enabled ? "enabled" : "disabled"} (authorized by root "${root.name}")`);
|
|
22767
|
+
}
|
|
22768
|
+
async function sendControl(id, contactRef, payload) {
|
|
22769
|
+
await mutatingTx(id, "::a2a_control::send_control", {
|
|
22770
|
+
contact: contactRef,
|
|
22771
|
+
payload: JSON.stringify(payload)
|
|
22772
|
+
});
|
|
22773
|
+
}
|
|
22774
|
+
function renderCopies(v) {
|
|
22775
|
+
const out = [];
|
|
22776
|
+
if (v.IsNil()) return out;
|
|
22777
|
+
for (let i = 0; ; i++) {
|
|
22778
|
+
const m = v.Reduce(i);
|
|
22779
|
+
if (m.IsNil()) break;
|
|
22780
|
+
out.push({
|
|
22781
|
+
source_cid: m.Reduce("source_cid").Visualize(),
|
|
22782
|
+
source_name: m.Reduce("source_name").Visualize(),
|
|
22783
|
+
direction: m.Reduce("direction").Visualize(),
|
|
22784
|
+
peer_cid: m.Reduce("peer_cid").Visualize(),
|
|
22785
|
+
peer_name: m.Reduce("peer_name").Visualize(),
|
|
22786
|
+
date: m.Reduce("date").Visualize(),
|
|
22787
|
+
body: m.Reduce("body").Visualize()
|
|
22788
|
+
});
|
|
22789
|
+
}
|
|
22790
|
+
return out;
|
|
22791
|
+
}
|
|
22792
|
+
function renderControlRequests(v) {
|
|
22793
|
+
const out = [];
|
|
22794
|
+
if (v.IsNil()) return out;
|
|
22795
|
+
for (let i = 0; ; i++) {
|
|
22796
|
+
const m = v.Reduce(i);
|
|
22797
|
+
if (m.IsNil()) break;
|
|
22798
|
+
out.push({
|
|
22799
|
+
senderCid: m.Reduce("sender_cid").Visualize(),
|
|
22800
|
+
senderName: m.Reduce("sender_name").Visualize(),
|
|
22801
|
+
payload: m.Reduce("payload").Visualize(),
|
|
22802
|
+
date: m.Reduce("date").Visualize()
|
|
22803
|
+
});
|
|
22804
|
+
}
|
|
22805
|
+
return out;
|
|
22806
|
+
}
|
|
22807
|
+
var forwardBusy = /* @__PURE__ */ new Set();
|
|
22808
|
+
async function forwardMonitoring(root) {
|
|
22809
|
+
if (forwardBusy.has(root.name)) return;
|
|
22810
|
+
forwardBusy.add(root.name);
|
|
22811
|
+
try {
|
|
22812
|
+
const st = monitoringStatus(root);
|
|
22813
|
+
if (!st.proxyCid || st.copiesQueued === 0) return;
|
|
22814
|
+
const data = await mutatingTx(root, "::actor::get_monitoring_copies", {});
|
|
22815
|
+
const copies = renderCopies(data.Reduce("copies"));
|
|
22816
|
+
if (copies.length === 0) return;
|
|
22817
|
+
await sendControl(root, st.proxyCid, { v: 1, t: "monitoring", copies });
|
|
22818
|
+
} catch (err) {
|
|
22819
|
+
log(`[${root.name}] monitoring forward failed:`, String(err));
|
|
22820
|
+
} finally {
|
|
22821
|
+
forwardBusy.delete(root.name);
|
|
22822
|
+
}
|
|
22823
|
+
}
|
|
22824
|
+
function listAgentsFor(root) {
|
|
22825
|
+
const agents = [];
|
|
22826
|
+
for (const id of identities.values()) {
|
|
22827
|
+
if (id.name === root.name) continue;
|
|
22828
|
+
const info = describeIdentity(id);
|
|
22829
|
+
if (info.rootCid !== root.cid) continue;
|
|
22830
|
+
agents.push({
|
|
22831
|
+
name: id.name,
|
|
22832
|
+
cid: id.cid,
|
|
22833
|
+
role_id: info.roleId,
|
|
22834
|
+
bio: info.bio,
|
|
22835
|
+
monitoring: info.monitoringEnabled
|
|
22836
|
+
});
|
|
22837
|
+
}
|
|
22838
|
+
return agents;
|
|
22839
|
+
}
|
|
22840
|
+
function findAgentOf(root, ref) {
|
|
22841
|
+
const id = identities.get(ref) ?? [...identities.values()].find((i) => i.cid === ref);
|
|
22842
|
+
if (!id || id.name === root.name) return null;
|
|
22843
|
+
return describeIdentity(id).rootCid === root.cid ? id : null;
|
|
22844
|
+
}
|
|
22845
|
+
function deleteIdentityCompletely(id) {
|
|
22846
|
+
try {
|
|
22847
|
+
wrapper.remove_packet(id.cid);
|
|
22848
|
+
} catch (err) {
|
|
22849
|
+
log(`remove_packet(${id.cid}) failed:`, String(err));
|
|
22850
|
+
}
|
|
22851
|
+
identities.delete(id.name);
|
|
22852
|
+
try {
|
|
22853
|
+
unpublishFromBook(id.name);
|
|
22854
|
+
} catch (err) {
|
|
22855
|
+
log(`failed to unpublish "${id.name}" from the contact book:`, String(err));
|
|
22856
|
+
}
|
|
22857
|
+
const holder = bindingOwner.get(id.name);
|
|
22858
|
+
if (holder) {
|
|
22859
|
+
bindingOwner.delete(id.name);
|
|
22860
|
+
sessionBinding.delete(holder);
|
|
22861
|
+
persistBindings();
|
|
22862
|
+
}
|
|
22863
|
+
if (id.name === rootName) {
|
|
22864
|
+
rootName = null;
|
|
22865
|
+
clearRootMarker();
|
|
22866
|
+
}
|
|
22867
|
+
try {
|
|
22868
|
+
fs2.rmSync(id.dir, { recursive: true, force: true });
|
|
22869
|
+
} catch (err) {
|
|
22870
|
+
return `deleting ${id.dir} failed: ${String(err)}`;
|
|
22871
|
+
}
|
|
22872
|
+
return null;
|
|
22873
|
+
}
|
|
22874
|
+
async function handleControlRequest(root, req) {
|
|
22875
|
+
let msg;
|
|
22876
|
+
try {
|
|
22877
|
+
const parsed = JSON.parse(req.payload);
|
|
22878
|
+
if (!parsed || typeof parsed !== "object") throw new Error("not an object");
|
|
22879
|
+
msg = parsed;
|
|
22880
|
+
} catch {
|
|
22881
|
+
log(`[${root.name}] dropping unparseable control request from ${req.senderName}`);
|
|
22882
|
+
return;
|
|
22883
|
+
}
|
|
22884
|
+
if (msg.v !== 1 || typeof msg.t !== "string") {
|
|
22885
|
+
log(`[${root.name}] dropping control request with unknown envelope from ${req.senderName}`);
|
|
22886
|
+
return;
|
|
22887
|
+
}
|
|
22888
|
+
const reply = async (data) => {
|
|
22889
|
+
try {
|
|
22890
|
+
await sendControl(root, req.senderCid, { v: 1, t: "res", id: msg.id ?? null, req: msg.t, ...data });
|
|
22891
|
+
} catch (err) {
|
|
22892
|
+
log(`[${root.name}] control reply to ${req.senderName} failed:`, String(err));
|
|
22893
|
+
}
|
|
22894
|
+
};
|
|
22895
|
+
try {
|
|
22896
|
+
if (msg.t === "bind") {
|
|
22897
|
+
const data = await mutatingTx(root, "::actor::verify_proxy_code", {
|
|
22898
|
+
code: String(msg.code ?? ""),
|
|
22899
|
+
sender: req.senderCid
|
|
22900
|
+
});
|
|
22901
|
+
if (!data.Reduce("verified").GetBoolean()) {
|
|
22902
|
+
const reason = data.Reduce("reason").Visualize();
|
|
22903
|
+
log(`[${root.name}] proxy bind attempt from ${req.senderName} rejected (${reason})`);
|
|
22904
|
+
await reply({ ok: false, error: reason });
|
|
22905
|
+
return;
|
|
22906
|
+
}
|
|
22907
|
+
appendNotifyLog(root, { event: "monitoring_proxy_bound", from: req.senderName });
|
|
22908
|
+
log(`[${root.name}] monitoring proxy bound: ${req.senderName} (${req.senderCid})`);
|
|
22909
|
+
await reply({ ok: true, root: { name: root.name, cid: root.cid }, agents: listAgentsFor(root) });
|
|
22910
|
+
await forwardMonitoring(root);
|
|
22911
|
+
return;
|
|
22912
|
+
}
|
|
22913
|
+
const st = monitoringStatus(root);
|
|
22914
|
+
if (!st.proxyCid || st.proxyCid !== req.senderCid) {
|
|
22915
|
+
await reply({ ok: false, error: "not_authorized" });
|
|
22916
|
+
return;
|
|
22917
|
+
}
|
|
22918
|
+
switch (msg.t) {
|
|
22919
|
+
case "list_agents": {
|
|
22920
|
+
await reply({ ok: true, root: { name: root.name, cid: root.cid }, agents: listAgentsFor(root) });
|
|
22921
|
+
return;
|
|
22922
|
+
}
|
|
22923
|
+
case "create_agent": {
|
|
22924
|
+
const name = String(msg.name ?? "").trim();
|
|
22925
|
+
const bio = String(msg.bio ?? "");
|
|
22926
|
+
const bad = validateName(name);
|
|
22927
|
+
if (bad) {
|
|
22928
|
+
await reply({ ok: false, error: bad });
|
|
22929
|
+
return;
|
|
22930
|
+
}
|
|
22931
|
+
if (identities.has(name)) {
|
|
22932
|
+
await reply({ ok: false, error: `an identity named "${name}" already exists` });
|
|
22933
|
+
return;
|
|
22934
|
+
}
|
|
22935
|
+
const agent = await provisionIdentity(name);
|
|
22936
|
+
if (bio) await mutatingTx(agent, "::a2a_messaging::set_my_bio", { bio });
|
|
22937
|
+
await delegateRole(root, agent);
|
|
22938
|
+
await reply({ ok: true, agents: listAgentsFor(root) });
|
|
22939
|
+
return;
|
|
22940
|
+
}
|
|
22941
|
+
case "update_role": {
|
|
22942
|
+
const agent = findAgentOf(root, String(msg.agent ?? ""));
|
|
22943
|
+
if (!agent) {
|
|
22944
|
+
await reply({ ok: false, error: "no such agent under this root" });
|
|
22945
|
+
return;
|
|
22946
|
+
}
|
|
22947
|
+
await mutatingTx(agent, "::a2a_messaging::set_my_bio", { bio: String(msg.bio ?? "") });
|
|
22948
|
+
await reply({ ok: true, agents: listAgentsFor(root) });
|
|
22949
|
+
return;
|
|
22950
|
+
}
|
|
22951
|
+
case "set_monitoring": {
|
|
22952
|
+
const agent = findAgentOf(root, String(msg.agent ?? ""));
|
|
22953
|
+
if (!agent) {
|
|
22954
|
+
await reply({ ok: false, error: "no such agent under this root" });
|
|
22955
|
+
return;
|
|
22956
|
+
}
|
|
22957
|
+
await setAgentMonitoring(root, agent, Boolean(msg.enabled));
|
|
22958
|
+
await reply({ ok: true, agents: listAgentsFor(root) });
|
|
22959
|
+
return;
|
|
22960
|
+
}
|
|
22961
|
+
case "contact_agent": {
|
|
22962
|
+
const agent = findAgentOf(root, String(msg.agent ?? ""));
|
|
22963
|
+
if (!agent) {
|
|
22964
|
+
await reply({ ok: false, error: "no such agent under this root" });
|
|
22965
|
+
return;
|
|
22966
|
+
}
|
|
22967
|
+
const inv = await mutatingTx(agent, "::a2a_messaging::generate_invite", {});
|
|
22968
|
+
const blob = packInvite(Buffer.from(inv.Reduce("invite").GetBinary()));
|
|
22969
|
+
await reply({ ok: true, agent: agent.name, invite: blob });
|
|
22970
|
+
return;
|
|
22971
|
+
}
|
|
22972
|
+
case "remove_agent": {
|
|
22973
|
+
const agent = findAgentOf(root, String(msg.agent ?? ""));
|
|
22974
|
+
if (!agent) {
|
|
22975
|
+
await reply({ ok: false, error: "no such agent under this root" });
|
|
22976
|
+
return;
|
|
22977
|
+
}
|
|
22978
|
+
const fail = deleteIdentityCompletely(agent);
|
|
22979
|
+
if (fail) {
|
|
22980
|
+
await reply({ ok: false, error: fail });
|
|
22981
|
+
return;
|
|
22982
|
+
}
|
|
22983
|
+
await reply({ ok: true, agents: listAgentsFor(root) });
|
|
22984
|
+
return;
|
|
22985
|
+
}
|
|
22986
|
+
default:
|
|
22987
|
+
await reply({ ok: false, error: `unknown request type "${msg.t}"` });
|
|
22988
|
+
}
|
|
22989
|
+
} catch (err) {
|
|
22990
|
+
await reply({ ok: false, error: String(err) });
|
|
22991
|
+
}
|
|
22992
|
+
}
|
|
22993
|
+
var controlBusy = /* @__PURE__ */ new Set();
|
|
22994
|
+
async function processControlRequests(root) {
|
|
22995
|
+
if (controlBusy.has(root.name)) return;
|
|
22996
|
+
controlBusy.add(root.name);
|
|
22997
|
+
try {
|
|
22998
|
+
for (; ; ) {
|
|
22999
|
+
const data = await mutatingTx(root, "::actor::get_control_requests", {});
|
|
23000
|
+
const reqs = renderControlRequests(data.Reduce("requests"));
|
|
23001
|
+
if (reqs.length === 0) return;
|
|
23002
|
+
for (const req of reqs) {
|
|
23003
|
+
await handleControlRequest(root, req);
|
|
23004
|
+
}
|
|
23005
|
+
}
|
|
23006
|
+
} catch (err) {
|
|
23007
|
+
log(`[${root.name}] control dispatch failed:`, String(err));
|
|
23008
|
+
} finally {
|
|
23009
|
+
controlBusy.delete(root.name);
|
|
23010
|
+
}
|
|
23011
|
+
if (identities.has(root.name) && monitoringStatus(root).controlQueued > 0) {
|
|
23012
|
+
return processControlRequests(root);
|
|
23013
|
+
}
|
|
23014
|
+
}
|
|
22738
23015
|
async function ensureRegistrar() {
|
|
22739
23016
|
fs2.mkdirSync(bookDir(), { recursive: true });
|
|
22740
23017
|
let seed;
|
|
@@ -22897,6 +23174,12 @@ function wireHandlers(id) {
|
|
|
22897
23174
|
process.nextTick(
|
|
22898
23175
|
() => pushNotification(id.name, `[${id.name}] "${name}" queued a message awaiting introduction approval (${queued} queued).`)
|
|
22899
23176
|
);
|
|
23177
|
+
} else if (event === "control_request") {
|
|
23178
|
+
const from = payload.Reduce("sender_name").Visualize();
|
|
23179
|
+
log(`[${id.name}] control request queued by ${from}`);
|
|
23180
|
+
process.nextTick(() => void processControlRequests(id));
|
|
23181
|
+
} else if (event === "monitoring_copy") {
|
|
23182
|
+
process.nextTick(() => void forwardMonitoring(id));
|
|
22900
23183
|
}
|
|
22901
23184
|
return;
|
|
22902
23185
|
}
|
|
@@ -23034,6 +23317,16 @@ async function bootWrapper() {
|
|
|
23034
23317
|
clearRootMarker();
|
|
23035
23318
|
}
|
|
23036
23319
|
if (rootName) log(`root identity: ${rootName}`);
|
|
23320
|
+
const root = rootName ? identities.get(rootName) : void 0;
|
|
23321
|
+
if (root) {
|
|
23322
|
+
try {
|
|
23323
|
+
const st = monitoringStatus(root);
|
|
23324
|
+
if (st.controlQueued > 0) void processControlRequests(root);
|
|
23325
|
+
else if (st.copiesQueued > 0) void forwardMonitoring(root);
|
|
23326
|
+
} catch (err) {
|
|
23327
|
+
log(`boot-time monitoring/control drain failed:`, String(err));
|
|
23328
|
+
}
|
|
23329
|
+
}
|
|
23037
23330
|
persistBindings();
|
|
23038
23331
|
}
|
|
23039
23332
|
function resolveBound(sessionId) {
|
|
@@ -23391,31 +23684,9 @@ Bio: ${info.bio}` : "";
|
|
|
23391
23684
|
);
|
|
23392
23685
|
}
|
|
23393
23686
|
}
|
|
23394
|
-
|
|
23395
|
-
|
|
23396
|
-
|
|
23397
|
-
log(`remove_packet(${id.cid}) failed:`, String(err));
|
|
23398
|
-
}
|
|
23399
|
-
identities.delete(name);
|
|
23400
|
-
try {
|
|
23401
|
-
unpublishFromBook(name);
|
|
23402
|
-
} catch (err) {
|
|
23403
|
-
log(`failed to unpublish "${name}" from the contact book:`, String(err));
|
|
23404
|
-
}
|
|
23405
|
-
const holder = bindingOwner.get(name);
|
|
23406
|
-
if (holder) {
|
|
23407
|
-
bindingOwner.delete(name);
|
|
23408
|
-
sessionBinding.delete(holder);
|
|
23409
|
-
persistBindings();
|
|
23410
|
-
}
|
|
23411
|
-
if (name === rootName) {
|
|
23412
|
-
rootName = null;
|
|
23413
|
-
clearRootMarker();
|
|
23414
|
-
}
|
|
23415
|
-
try {
|
|
23416
|
-
fs2.rmSync(id.dir, { recursive: true, force: true });
|
|
23417
|
-
} catch (err) {
|
|
23418
|
-
return textResult(`Identity "${name}" removed from memory, but deleting ${id.dir} failed: ${String(err)}`, true);
|
|
23687
|
+
const fail = deleteIdentityCompletely(id);
|
|
23688
|
+
if (fail) {
|
|
23689
|
+
return textResult(`Identity "${name}" removed from memory, but ${fail}`, true);
|
|
23419
23690
|
}
|
|
23420
23691
|
return textResult(`Removed identity "${name}" and its state.`);
|
|
23421
23692
|
}
|
|
@@ -23731,6 +24002,97 @@ These are now marked processed (auto-GC'd later). To hand any back to another se
|
|
|
23731
24002
|
}
|
|
23732
24003
|
}
|
|
23733
24004
|
);
|
|
24005
|
+
const rootOr = () => {
|
|
24006
|
+
const root = rootName ? identities.get(rootName) : void 0;
|
|
24007
|
+
if (!root) {
|
|
24008
|
+
return { err: textResult("No root identity exists on this host \u2014 create one with create_root_identity first.", true) };
|
|
24009
|
+
}
|
|
24010
|
+
return { root };
|
|
24011
|
+
};
|
|
24012
|
+
server.tool(
|
|
24013
|
+
"enable_monitoring",
|
|
24014
|
+
"Enable monitoring on one of this host's agents (a role under the root identity): from now on the agent reports a copy of every message it sends or receives to the root, which forwards them to the bound browser proxy (see bind_monitoring_proxy). Authorized by a root-signed blob the role verifies against its delegation chain. Forward-only: past messages are never reported.",
|
|
24015
|
+
{ agent: external_exports.string().min(1).describe("Agent (role) name or container id.") },
|
|
24016
|
+
async ({ agent }) => {
|
|
24017
|
+
const { root, err } = rootOr();
|
|
24018
|
+
if (err) return err;
|
|
24019
|
+
const role = findAgentOf(root, agent);
|
|
24020
|
+
if (!role) return textResult(`enable_monitoring failed: "${agent}" is not a role under root "${root.name}".`, true);
|
|
24021
|
+
try {
|
|
24022
|
+
await setAgentMonitoring(root, role, true);
|
|
24023
|
+
return textResult(`Monitoring enabled on "${role.name}" \u2014 its message traffic now reports to root "${root.name}".`);
|
|
24024
|
+
} catch (e) {
|
|
24025
|
+
return textResult(`enable_monitoring failed: ${String(e)}`, true);
|
|
24026
|
+
}
|
|
24027
|
+
}
|
|
24028
|
+
);
|
|
24029
|
+
server.tool(
|
|
24030
|
+
"disable_monitoring",
|
|
24031
|
+
"Disable monitoring on one of this host's agents (see enable_monitoring).",
|
|
24032
|
+
{ agent: external_exports.string().min(1).describe("Agent (role) name or container id.") },
|
|
24033
|
+
async ({ agent }) => {
|
|
24034
|
+
const { root, err } = rootOr();
|
|
24035
|
+
if (err) return err;
|
|
24036
|
+
const role = findAgentOf(root, agent);
|
|
24037
|
+
if (!role) return textResult(`disable_monitoring failed: "${agent}" is not a role under root "${root.name}".`, true);
|
|
24038
|
+
try {
|
|
24039
|
+
await setAgentMonitoring(root, role, false);
|
|
24040
|
+
return textResult(`Monitoring disabled on "${role.name}".`);
|
|
24041
|
+
} catch (e) {
|
|
24042
|
+
return textResult(`disable_monitoring failed: ${String(e)}`, true);
|
|
24043
|
+
}
|
|
24044
|
+
}
|
|
24045
|
+
);
|
|
24046
|
+
server.tool(
|
|
24047
|
+
"bind_monitoring_proxy",
|
|
24048
|
+
"Start binding a browser (messenger) account as this host's monitoring & control proxy. PREREQUISITE: the browser account must already be a contact of the ROOT identity (invite exchange). This generates a 6-digit code (5-minute expiry, 3 attempts) bound to that contact and shows it HERE \u2014 read it to the user, who enters it in the messenger's Control Panel. On a successful code verification the contact becomes the monitoring proxy: it receives the monitoring feed and may manage agents (create, edit bios, toggle monitoring, request invites) through the root.",
|
|
24049
|
+
{ contact: external_exports.string().min(1).describe("The root's contact (name or container id) to bind as the proxy.") },
|
|
24050
|
+
async ({ contact }) => {
|
|
24051
|
+
const { root, err } = rootOr();
|
|
24052
|
+
if (err) return err;
|
|
24053
|
+
try {
|
|
24054
|
+
const code = String(randomInt(0, 1e6)).padStart(6, "0");
|
|
24055
|
+
const data = await mutatingTx(root, "::actor::set_proxy_pending", { code, proxy: contact });
|
|
24056
|
+
const cid = data.Reduce("proxy_cid").Visualize();
|
|
24057
|
+
return textResult(
|
|
24058
|
+
`Proxy binding started for contact "${contact}" (${cid}).
|
|
24059
|
+
|
|
24060
|
+
Verification code: ${code}
|
|
24061
|
+
|
|
24062
|
+
Tell the user to enter this code in the messenger's Control Panel within 5 minutes (3 attempts). Do NOT send the code over a2adapt \u2014 it must travel out-of-band (this terminal counts).`
|
|
24063
|
+
);
|
|
24064
|
+
} catch (e) {
|
|
24065
|
+
return textResult(`bind_monitoring_proxy failed: ${String(e)}`, true);
|
|
24066
|
+
}
|
|
24067
|
+
}
|
|
24068
|
+
);
|
|
24069
|
+
server.tool(
|
|
24070
|
+
"get_monitoring_status",
|
|
24071
|
+
"Report the monitoring & control state of this host: the root's bound proxy (if any), a pending proxy verification, queued copies/requests, and which agents have monitoring enabled.",
|
|
24072
|
+
{},
|
|
24073
|
+
async () => {
|
|
24074
|
+
const { root, err } = rootOr();
|
|
24075
|
+
if (err) return err;
|
|
24076
|
+
try {
|
|
24077
|
+
const st = monitoringStatus(root);
|
|
24078
|
+
const lines = [];
|
|
24079
|
+
lines.push(`Root "${root.name}" (${root.cid}):`);
|
|
24080
|
+
lines.push(st.proxyCid ? `\u2022 monitoring proxy bound: ${st.proxyCid}` : "\u2022 no monitoring proxy bound");
|
|
24081
|
+
if (st.proxyPending) lines.push("\u2022 a proxy code verification is pending");
|
|
24082
|
+
if (st.copiesQueued > 0) lines.push(`\u2022 ${st.copiesQueued} monitoring cop${st.copiesQueued === 1 ? "y" : "ies"} queued for forwarding`);
|
|
24083
|
+
if (st.controlQueued > 0) lines.push(`\u2022 ${st.controlQueued} control request(s) queued`);
|
|
24084
|
+
const agents = listAgentsFor(root);
|
|
24085
|
+
lines.push("");
|
|
24086
|
+
lines.push(
|
|
24087
|
+
agents.length === 0 ? "No agents (roles) under this root." : `Agents (${agents.length}):
|
|
24088
|
+
${agents.map((a) => `\u2022 ${a.name} \u2014 monitoring ${a.monitoring ? "ON" : "off"}${a.bio ? ` \u2014 ${a.bio}` : ""}`).join("\n")}`
|
|
24089
|
+
);
|
|
24090
|
+
return textResult(lines.join("\n"));
|
|
24091
|
+
} catch (e) {
|
|
24092
|
+
return textResult(`get_monitoring_status failed: ${String(e)}`, true);
|
|
24093
|
+
}
|
|
24094
|
+
}
|
|
24095
|
+
);
|
|
23734
24096
|
return server;
|
|
23735
24097
|
}
|
|
23736
24098
|
function readBody(req) {
|
|
Binary file
|
package/dist/mufl_code/actor.mu
CHANGED
|
@@ -62,9 +62,23 @@
|
|
|
62
62
|
// reject_introduction — drop a pending local introduction
|
|
63
63
|
// list_pending_introductions — (readonly) pending introductions (names + queue sizes)
|
|
64
64
|
//
|
|
65
|
+
// Monitoring + control plane (host-fired unless noted; see
|
|
66
|
+
// MONITORING-AND-SHARED-LIBRARY-DESIGN.md):
|
|
67
|
+
// get_monitoring_status — (readonly) enabled flag / proxy binding / queue sizes
|
|
68
|
+
// sign_monitoring_auth — root-only: sign an enable/disable auth for a role
|
|
69
|
+
// set_monitoring — role: verify the root-signed auth, set the flag
|
|
70
|
+
// get_monitoring_copies — root: drain queued copies for proxy forwarding
|
|
71
|
+
// get_control_requests — root: drain queued proxy control requests
|
|
72
|
+
// set_proxy_pending — root: store the host-generated 6-digit code
|
|
73
|
+
// verify_proxy_code — root: check a bind attempt (atomic attempts/expiry)
|
|
74
|
+
// clear_monitoring_proxy — root: drop the proxy binding
|
|
75
|
+
// ::a2a_control::send_control — send an opaque control payload to a contact
|
|
76
|
+
//
|
|
65
77
|
// External transactions (inbound, not exposed as tools):
|
|
66
78
|
// accept_contact — inviter learns the joiner's identity + name (core shim)
|
|
67
79
|
// receive_message — store a decrypted inbound message (core shim)
|
|
80
|
+
// receive_monitoring_copy — a monitored role of mine reports a message copy
|
|
81
|
+
// ::a2a_control::control_message — control payload from a contact (queued for the daemon)
|
|
68
82
|
// local_introduce — same-host peer connects via the local contact book
|
|
69
83
|
// sibling_introduce — intra-root peer connects, authorized by its delegation cert
|
|
70
84
|
|
|
@@ -82,6 +96,7 @@ application actor loads libraries
|
|
|
82
96
|
current_transaction_info,
|
|
83
97
|
a2a_protocol,
|
|
84
98
|
a2a_messaging,
|
|
99
|
+
a2a_control,
|
|
85
100
|
version
|
|
86
101
|
uses transactions
|
|
87
102
|
{
|
|
@@ -113,6 +128,35 @@ application actor loads libraries
|
|
|
113
128
|
seen_nonce_cap is int = 1024.
|
|
114
129
|
pending_queue_cap is int = 50.
|
|
115
130
|
|
|
131
|
+
// ---- monitoring shapes + limits (see MONITORING-AND-SHARED-LIBRARY-DESIGN.md) ----
|
|
132
|
+
// Root-signed authorization for flipping a role's monitoring flag. Like
|
|
133
|
+
// a delegation cert, verified against the role's pinned root keys, so
|
|
134
|
+
// only this hierarchy's root can change what its roles report.
|
|
135
|
+
metadef monitoring_auth_core_t: ($version -> int, $role_cid -> global_id, $enabled -> bool, $issued_at -> time).
|
|
136
|
+
metadef monitoring_auth_t: ($c -> monitoring_auth_core_t, $s -> crypto_signature).
|
|
137
|
+
// One monitored message, as the role reports it to its root.
|
|
138
|
+
metadef monitoring_copy_t: (
|
|
139
|
+
$version -> int,
|
|
140
|
+
$source_cid -> global_id,
|
|
141
|
+
$source_name -> str,
|
|
142
|
+
$direction -> str,
|
|
143
|
+
$peer_cid -> global_id,
|
|
144
|
+
$peer_name -> str,
|
|
145
|
+
$date -> time,
|
|
146
|
+
$body -> str
|
|
147
|
+
).
|
|
148
|
+
// Proxy binding (root only): a pending 6-digit-code verification and
|
|
149
|
+
// the verified binding that replaces it on success.
|
|
150
|
+
metadef proxy_pending_t: ($code -> str, $proxy_cid -> global_id, $created_at -> time, $attempts -> int).
|
|
151
|
+
metadef proxy_binding_t: ($proxy_cid -> global_id, $bound_at -> time).
|
|
152
|
+
// One queued control request from the bound browser proxy.
|
|
153
|
+
metadef control_req_t: ($sender_cid -> global_id, $sender_name -> str, $payload -> str, $date -> time).
|
|
154
|
+
|
|
155
|
+
proxy_code_max_age_seconds is int = 300.
|
|
156
|
+
proxy_max_attempts is int = 3.
|
|
157
|
+
monitoring_inbox_cap is int = 500.
|
|
158
|
+
control_inbox_cap is int = 200.
|
|
159
|
+
|
|
116
160
|
// Wire the deserialization primitive into the libraries that need it.
|
|
117
161
|
_read_or_abort = grab( _read_or_abort ).
|
|
118
162
|
key_storage::init ($_read_or_abort -> _read_or_abort).
|
|
@@ -147,6 +191,23 @@ application actor loads libraries
|
|
|
147
191
|
// each with a bounded queue of messages awaiting approval.
|
|
148
192
|
pending_introductions is (global_id ->> pending_intro_t) = (,).
|
|
149
193
|
|
|
194
|
+
// ---- monitoring state -------------------------------------------------
|
|
195
|
+
// Whether THIS packet (a role) reports its message traffic to its root.
|
|
196
|
+
// Only flipped by a root-signed authorization (set_monitoring).
|
|
197
|
+
monitoring_enabled is bool = FALSE.
|
|
198
|
+
// Root only: copies received from monitored roles, awaiting the host's
|
|
199
|
+
// get_monitoring_copies pull (forwarded to the bound proxy). Capped;
|
|
200
|
+
// oldest copies are dropped first when no proxy drains the queue.
|
|
201
|
+
monitoring_inbox is monitoring_copy_t[] = [].
|
|
202
|
+
// Root only: the in-flight proxy binding (6-digit code verification)
|
|
203
|
+
// and the verified proxy that monitoring traffic is forwarded to.
|
|
204
|
+
proxy_pending is proxy_pending_t+ = NIL.
|
|
205
|
+
monitoring_proxy is proxy_binding_t+ = NIL.
|
|
206
|
+
// Root only: control requests from the bound proxy, awaiting the
|
|
207
|
+
// host's get_control_requests pull. Kept out of the message inbox so
|
|
208
|
+
// agent sessions never see them.
|
|
209
|
+
control_inbox is control_req_t[] = [].
|
|
210
|
+
|
|
150
211
|
// Signal the host to persist the packet. Only emitted at the end of a
|
|
151
212
|
// complete procedure — intermediate states (e.g. channel handshake) are
|
|
152
213
|
// never saved, so a crash mid-handshake restores to the last stable point.
|
|
@@ -170,6 +231,39 @@ application actor loads libraries
|
|
|
170
231
|
return mid.
|
|
171
232
|
}
|
|
172
233
|
|
|
234
|
+
// Build the monitoring-copy action for one message IF this packet is a
|
|
235
|
+
// monitored role with a live encrypted channel to its root; [] otherwise.
|
|
236
|
+
// The is_container_registered guard makes a missing/lost root channel
|
|
237
|
+
// degrade to "no copy" instead of failing the user's message — the
|
|
238
|
+
// enable flow (host-side) establishes the channel via connect_sibling.
|
|
239
|
+
fn monitor_copy_actions (direction: str, peer_cid: global_id, text: str, msg_date: time) -> transaction::action::type[]
|
|
240
|
+
{
|
|
241
|
+
if monitoring_enabled == FALSE || a2a_messaging::delegation_cert == NIL { return []. }
|
|
242
|
+
root_cid = a2a_messaging::delegation_cert? $c $root_cid.
|
|
243
|
+
if key_storage::is_container_registered(root_cid) != TRUE { return []. }
|
|
244
|
+
|
|
245
|
+
peer_name is str = "".
|
|
246
|
+
p = a2a_messaging::contacts peer_cid.
|
|
247
|
+
if p != NIL { peer_name -> p? $name. }
|
|
248
|
+
|
|
249
|
+
copy is monitoring_copy_t = (
|
|
250
|
+
$version -> 1,
|
|
251
|
+
$source_cid -> _get_container_id(),
|
|
252
|
+
$source_name -> a2a_messaging::my_name,
|
|
253
|
+
$direction -> direction,
|
|
254
|
+
$peer_cid -> peer_cid,
|
|
255
|
+
$peer_name -> peer_name,
|
|
256
|
+
$date -> msg_date,
|
|
257
|
+
$body -> text
|
|
258
|
+
).
|
|
259
|
+
return [
|
|
260
|
+
encrypted_channel::send_encrypted_tx root_cid (
|
|
261
|
+
$name -> "::actor::receive_monitoring_copy",
|
|
262
|
+
$targ -> ($copy -> copy)
|
|
263
|
+
)
|
|
264
|
+
].
|
|
265
|
+
}
|
|
266
|
+
|
|
173
267
|
// Resolve a pending introduction by joiner name or stringified container
|
|
174
268
|
// id; aborts when nothing matches.
|
|
175
269
|
fn resolve_pending (ref: str) -> global_id
|
|
@@ -217,13 +311,44 @@ application actor loads libraries
|
|
|
217
311
|
|
|
218
312
|
sender_name = (arg $sender_name) safe str.
|
|
219
313
|
mid = deposit_message sender_id sender_name text msg_date.
|
|
314
|
+
actions is transaction::action::type[] = [].
|
|
315
|
+
sc monitor_copy_actions "in" sender_id text msg_date -- ( -> a)
|
|
316
|
+
{
|
|
317
|
+
actions (_count actions|) -> a.
|
|
318
|
+
}
|
|
319
|
+
actions (_count actions|) -> _notify_agent ($event -> $message_received, $sender_name -> sender_name, $msg_id -> mid, $date -> msg_date).
|
|
320
|
+
actions (_count actions|) -> _save_state NIL.
|
|
321
|
+
return actions.
|
|
322
|
+
},
|
|
323
|
+
$on_message_sent -> fn (arg: any) -> transaction::action::type[]
|
|
324
|
+
{
|
|
325
|
+
return monitor_copy_actions "out" ((arg $target_id) safe global_id) ((arg $text) safe str) ((arg $date) safe time).
|
|
326
|
+
},
|
|
327
|
+
$on_contact_removed -> fn (_: any) -> transaction::action::type[] { return []. }
|
|
328
|
+
).
|
|
329
|
+
|
|
330
|
+
// Wire the control plane (see MONITORING-AND-SHARED-LIBRARY-DESIGN.md
|
|
331
|
+
// Part 4): control requests from the bound browser proxy queue in
|
|
332
|
+
// control_inbox — NEVER the message inbox, so agent sessions don't see
|
|
333
|
+
// them — and the notify event wakes the daemon's dispatcher. The
|
|
334
|
+
// payload stays opaque here; sender authorization happens in the
|
|
335
|
+
// daemon against the packet's monitoring_proxy / proxy_pending state.
|
|
336
|
+
a2a_control::init (
|
|
337
|
+
$on_control_received -> fn (arg: any) -> transaction::action::type[]
|
|
338
|
+
{
|
|
339
|
+
abort "Control queue is full." when (_count control_inbox|) >= control_inbox_cap.
|
|
340
|
+
sender_name = (arg $sender_name) safe str.
|
|
341
|
+
control_inbox (_count control_inbox|) -> (
|
|
342
|
+
$sender_cid -> (arg $sender_id) safe global_id,
|
|
343
|
+
$sender_name -> sender_name,
|
|
344
|
+
$payload -> (arg $payload) safe str,
|
|
345
|
+
$date -> (arg $date) safe time
|
|
346
|
+
).
|
|
220
347
|
return [
|
|
221
|
-
_notify_agent ($event -> $
|
|
348
|
+
_notify_agent ($event -> $control_request, $sender_name -> sender_name, $queued -> _count control_inbox|),
|
|
222
349
|
_save_state NIL
|
|
223
350
|
].
|
|
224
|
-
}
|
|
225
|
-
$on_message_sent -> fn (_: any) -> transaction::action::type[] { return []. },
|
|
226
|
-
$on_contact_removed -> fn (_: any) -> transaction::action::type[] { return []. }
|
|
351
|
+
}
|
|
227
352
|
).
|
|
228
353
|
}
|
|
229
354
|
|
|
@@ -633,7 +758,7 @@ application actor loads libraries
|
|
|
633
758
|
{
|
|
634
759
|
if a2a_messaging::delegation_cert == NIL
|
|
635
760
|
{
|
|
636
|
-
return ($name -> a2a_messaging::my_name, $bio -> a2a_messaging::my_bio, $has_cert -> FALSE, $role_id -> "", $root_cid -> "", $root_name -> "").
|
|
761
|
+
return ($name -> a2a_messaging::my_name, $bio -> a2a_messaging::my_bio, $has_cert -> FALSE, $role_id -> "", $root_cid -> "", $root_name -> "", $monitoring_enabled -> monitoring_enabled).
|
|
637
762
|
}
|
|
638
763
|
cert = a2a_messaging::delegation_cert?.
|
|
639
764
|
rname is str = "".
|
|
@@ -644,7 +769,8 @@ application actor loads libraries
|
|
|
644
769
|
$has_cert -> TRUE,
|
|
645
770
|
$role_id -> cert $c $role_id,
|
|
646
771
|
$root_cid -> (_str (cert $c $root_cid)),
|
|
647
|
-
$root_name -> rname
|
|
772
|
+
$root_name -> rname,
|
|
773
|
+
$monitoring_enabled -> monitoring_enabled
|
|
648
774
|
).
|
|
649
775
|
}
|
|
650
776
|
|
|
@@ -692,6 +818,232 @@ application actor loads libraries
|
|
|
692
818
|
}).
|
|
693
819
|
}
|
|
694
820
|
|
|
821
|
+
// ---- monitoring + control plane -------------------------------------------
|
|
822
|
+
// (see MONITORING-AND-SHARED-LIBRARY-DESIGN.md). A monitored ROLE reports
|
|
823
|
+
// every message it sends/receives to its ROOT (the monitor_copy_actions
|
|
824
|
+
// branches in the storage hooks above); the root queues the copies and the
|
|
825
|
+
// host forwards them to a human proxy bound via 6-digit-code verification.
|
|
826
|
+
// The proxy's control requests (create agent, update role, …) queue in
|
|
827
|
+
// control_inbox and are executed by the host daemon.
|
|
828
|
+
|
|
829
|
+
trn readonly get_monitoring_status _
|
|
830
|
+
{
|
|
831
|
+
pending is bool = FALSE.
|
|
832
|
+
if proxy_pending != NIL { pending -> TRUE. }
|
|
833
|
+
proxy_out is str = "".
|
|
834
|
+
if monitoring_proxy != NIL { proxy_out -> _str (monitoring_proxy? $proxy_cid). }
|
|
835
|
+
return (
|
|
836
|
+
$monitoring_enabled -> monitoring_enabled,
|
|
837
|
+
$proxy_cid -> proxy_out,
|
|
838
|
+
$proxy_pending -> pending,
|
|
839
|
+
$copies_queued -> _count monitoring_inbox|,
|
|
840
|
+
$control_queued -> _count control_inbox|
|
|
841
|
+
).
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Sign a monitoring authorization for a role (ROOT packet only — the role
|
|
845
|
+
// verifies the signature against its pinned root keys, so an auth minted
|
|
846
|
+
// by any other packet fails). Stateless, mirrors sign_delegation.
|
|
847
|
+
trn sign_monitoring_auth _:($role_ad -> role_ad_blob: bin, $enabled -> enabled: bool)
|
|
848
|
+
{
|
|
849
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
850
|
+
abort "Only a root identity can sign monitoring authorizations." when a2a_messaging::delegation_cert != NIL.
|
|
851
|
+
|
|
852
|
+
role_ad = (_read_or_abort role_ad_blob) safe address_document_types::t_address_document.
|
|
853
|
+
core is monitoring_auth_core_t = (
|
|
854
|
+
$version -> 1,
|
|
855
|
+
$role_cid -> role_ad $identity $container_id,
|
|
856
|
+
$enabled -> enabled,
|
|
857
|
+
$issued_at -> (current_transaction_info::get_transaction_time())?
|
|
858
|
+
).
|
|
859
|
+
auth is monitoring_auth_t = ($c -> core, $s -> key_storage::default_sign (_value_id core)).
|
|
860
|
+
return transaction::success [
|
|
861
|
+
_return_data ($auth -> (_write auth))
|
|
862
|
+
].
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Store a verified monitoring flag (ROLE packet, host-fired after the root
|
|
866
|
+
// signed the auth). An auth that does not name me or was not signed by my
|
|
867
|
+
// root is rejected — so even a compromised host process cannot silently
|
|
868
|
+
// flip monitoring without the root packet's keys.
|
|
869
|
+
trn set_monitoring _:($auth -> auth_blob: bin)
|
|
870
|
+
{
|
|
871
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
872
|
+
abort "Only a delegated role can be monitored." when a2a_messaging::delegation_cert == NIL || a2a_messaging::root_ad == NIL.
|
|
873
|
+
|
|
874
|
+
auth = (_read_or_abort auth_blob) safe monitoring_auth_t.
|
|
875
|
+
abort "Unsupported monitoring authorization version." when (auth $c $version) != 1.
|
|
876
|
+
abort "This monitoring authorization was issued to a different identity." when (auth $c $role_cid) != _get_container_id().
|
|
877
|
+
abort "The monitoring authorization was not signed by my root." when key_storage::check_signature_new_container (_value_id (auth $c)) (auth $s) (a2a_messaging::root_ad? $identity $key_list) != TRUE.
|
|
878
|
+
|
|
879
|
+
monitoring_enabled -> auth $c $enabled.
|
|
880
|
+
return transaction::success [
|
|
881
|
+
_return_data ($monitoring_enabled -> monitoring_enabled),
|
|
882
|
+
_save_state NIL
|
|
883
|
+
].
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// A monitored role reports one message copy (ROOT packet, inbound). Only
|
|
887
|
+
// accepted from a verified role of THIS root (the contact_roots linkage
|
|
888
|
+
// recorded by sibling_introduce), and the copy must name its actual sender
|
|
889
|
+
// — a role cannot forge copies on another role's behalf.
|
|
890
|
+
trn receive_monitoring_copy _:($copy -> copy: monitoring_copy_t)
|
|
891
|
+
{
|
|
892
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::external,).
|
|
893
|
+
encrypted_channel::check_encrypted_or_abort().
|
|
894
|
+
|
|
895
|
+
sender_id = current_transaction_info::get_external_envelope_or_abort() $from.
|
|
896
|
+
link = a2a_messaging::contact_roots sender_id.
|
|
897
|
+
abort "Monitoring copies are only accepted from my own roles." when link == NIL || (link? $root_cid) != _get_container_id().
|
|
898
|
+
abort "Monitoring copy does not name its sender as the source." when (copy $source_cid) != sender_id.
|
|
899
|
+
abort "Unsupported monitoring copy version." when (copy $version) != 1.
|
|
900
|
+
|
|
901
|
+
// Capped queue, oldest first out: if no proxy drains the root, recent
|
|
902
|
+
// traffic wins over old.
|
|
903
|
+
if (_count monitoring_inbox|) >= monitoring_inbox_cap
|
|
904
|
+
{
|
|
905
|
+
trimmed is monitoring_copy_t[] = [].
|
|
906
|
+
i is int = 0.
|
|
907
|
+
sc monitoring_inbox -- ( -> m)
|
|
908
|
+
{
|
|
909
|
+
if i > 0 { trimmed (_count trimmed|) -> m. }
|
|
910
|
+
i -> i + 1.
|
|
911
|
+
}
|
|
912
|
+
monitoring_inbox -> trimmed.
|
|
913
|
+
}
|
|
914
|
+
monitoring_inbox (_count monitoring_inbox|) -> copy.
|
|
915
|
+
|
|
916
|
+
return transaction::success [
|
|
917
|
+
_notify_agent ($event -> $monitoring_copy, $source_name -> copy $source_name, $queued -> _count monitoring_inbox|),
|
|
918
|
+
_save_state NIL
|
|
919
|
+
].
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Drain the queued monitoring copies (ROOT packet, host-fired before
|
|
923
|
+
// forwarding to the bound proxy). Cleared on read, like get_messages.
|
|
924
|
+
trn get_monitoring_copies _
|
|
925
|
+
{
|
|
926
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
927
|
+
|
|
928
|
+
copies = monitoring_inbox.
|
|
929
|
+
monitoring_inbox -> [].
|
|
930
|
+
return transaction::success [
|
|
931
|
+
_return_data ($copies -> copies),
|
|
932
|
+
_save_state NIL
|
|
933
|
+
].
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// Drain the queued control requests (ROOT packet, host-fired by the
|
|
937
|
+
// control dispatcher). Cleared on read.
|
|
938
|
+
trn get_control_requests _
|
|
939
|
+
{
|
|
940
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
941
|
+
|
|
942
|
+
reqs = control_inbox.
|
|
943
|
+
control_inbox -> [].
|
|
944
|
+
return transaction::success [
|
|
945
|
+
_return_data ($requests -> reqs),
|
|
946
|
+
_save_state NIL
|
|
947
|
+
].
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Start a proxy binding (ROOT packet, host-fired): remember the code the
|
|
951
|
+
// host generated (MUFL has no random source) for one specific contact.
|
|
952
|
+
// Restarting overwrites any previous pending binding.
|
|
953
|
+
trn set_proxy_pending _:($code -> code: str, $proxy -> proxy_ref: str)
|
|
954
|
+
{
|
|
955
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
956
|
+
abort "Only a root identity can bind a monitoring proxy." when a2a_messaging::delegation_cert != NIL.
|
|
957
|
+
|
|
958
|
+
pid = a2a_messaging::resolve_contact proxy_ref.
|
|
959
|
+
proxy_pending -> (
|
|
960
|
+
$code -> code,
|
|
961
|
+
$proxy_cid -> pid,
|
|
962
|
+
$created_at -> (current_transaction_info::get_transaction_time())?,
|
|
963
|
+
$attempts -> 0
|
|
964
|
+
).
|
|
965
|
+
return transaction::success [
|
|
966
|
+
_return_data ($pending -> TRUE, $proxy_cid -> (_str pid)),
|
|
967
|
+
_save_state NIL
|
|
968
|
+
].
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Verify a proxy's code attempt (ROOT packet, host-fired when a `bind`
|
|
972
|
+
// control request arrives). Failures are returned as DATA — not aborts —
|
|
973
|
+
// so the attempt counter and expiry clearing persist atomically; an abort
|
|
974
|
+
// would roll them back and reopen the brute-force window.
|
|
975
|
+
trn verify_proxy_code _:($code -> code: str, $sender -> sender_ref: str)
|
|
976
|
+
{
|
|
977
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
978
|
+
|
|
979
|
+
if proxy_pending == NIL
|
|
980
|
+
{
|
|
981
|
+
return transaction::success [ _return_data ($verified -> FALSE, $reason -> "no_pending") ].
|
|
982
|
+
}
|
|
983
|
+
p = proxy_pending?.
|
|
984
|
+
now = (current_transaction_info::get_transaction_time())?.
|
|
985
|
+
|
|
986
|
+
if (_substract_seconds now (p $created_at)) > proxy_code_max_age_seconds
|
|
987
|
+
{
|
|
988
|
+
proxy_pending -> NIL.
|
|
989
|
+
return transaction::success [
|
|
990
|
+
_return_data ($verified -> FALSE, $reason -> "expired"),
|
|
991
|
+
_save_state NIL
|
|
992
|
+
].
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
sid = a2a_messaging::resolve_contact sender_ref.
|
|
996
|
+
if sid != (p $proxy_cid)
|
|
997
|
+
{
|
|
998
|
+
// Not the contact this binding was started for: reject without
|
|
999
|
+
// burning an attempt (the code was never compared).
|
|
1000
|
+
return transaction::success [ _return_data ($verified -> FALSE, $reason -> "wrong_sender") ].
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if code != (p $code)
|
|
1004
|
+
{
|
|
1005
|
+
attempts = (p $attempts) + 1.
|
|
1006
|
+
if attempts >= proxy_max_attempts
|
|
1007
|
+
{
|
|
1008
|
+
proxy_pending -> NIL.
|
|
1009
|
+
return transaction::success [
|
|
1010
|
+
_return_data ($verified -> FALSE, $reason -> "too_many_attempts"),
|
|
1011
|
+
_save_state NIL
|
|
1012
|
+
].
|
|
1013
|
+
}
|
|
1014
|
+
proxy_pending -> (
|
|
1015
|
+
$code -> p $code,
|
|
1016
|
+
$proxy_cid -> p $proxy_cid,
|
|
1017
|
+
$created_at -> p $created_at,
|
|
1018
|
+
$attempts -> attempts
|
|
1019
|
+
).
|
|
1020
|
+
return transaction::success [
|
|
1021
|
+
_return_data ($verified -> FALSE, $reason -> "wrong_code", $attempts_left -> proxy_max_attempts - attempts),
|
|
1022
|
+
_save_state NIL
|
|
1023
|
+
].
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
monitoring_proxy -> ($proxy_cid -> sid, $bound_at -> now).
|
|
1027
|
+
proxy_pending -> NIL.
|
|
1028
|
+
return transaction::success [
|
|
1029
|
+
_return_data ($verified -> TRUE, $proxy_cid -> (_str sid)),
|
|
1030
|
+
_save_state NIL
|
|
1031
|
+
].
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Drop the proxy binding (and any in-flight code verification).
|
|
1035
|
+
trn clear_monitoring_proxy _
|
|
1036
|
+
{
|
|
1037
|
+
current_transaction_info::validate_origin_or_abort (transaction::envelope::origin::user,).
|
|
1038
|
+
|
|
1039
|
+
monitoring_proxy -> NIL.
|
|
1040
|
+
proxy_pending -> NIL.
|
|
1041
|
+
return transaction::success [
|
|
1042
|
+
_return_data ($cleared -> TRUE),
|
|
1043
|
+
_save_state NIL
|
|
1044
|
+
].
|
|
1045
|
+
}
|
|
1046
|
+
|
|
695
1047
|
// ---- upgrade: state export / import -------------------------------------
|
|
696
1048
|
// The host persists state by calling export_state (readonly) and serializing
|
|
697
1049
|
// the returned value to a code-independent blob. On a code upgrade it recreates
|
|
@@ -725,7 +1077,12 @@ application actor loads libraries
|
|
|
725
1077
|
$delegation_cert -> core_state $delegation_cert,
|
|
726
1078
|
$root_ad -> core_state $root_ad,
|
|
727
1079
|
$root_profile -> core_state $root_profile,
|
|
728
|
-
$contact_roots -> core_state $contact_roots
|
|
1080
|
+
$contact_roots -> core_state $contact_roots,
|
|
1081
|
+
$monitoring_enabled -> monitoring_enabled,
|
|
1082
|
+
$monitoring_inbox -> monitoring_inbox,
|
|
1083
|
+
$proxy_pending -> proxy_pending,
|
|
1084
|
+
$monitoring_proxy -> monitoring_proxy,
|
|
1085
|
+
$control_inbox -> control_inbox
|
|
729
1086
|
).
|
|
730
1087
|
}
|
|
731
1088
|
|
|
@@ -808,6 +1165,29 @@ application actor loads libraries
|
|
|
808
1165
|
pending_introductions -> (data $pending_introductions) safe (global_id ->> pending_intro_t).
|
|
809
1166
|
}
|
|
810
1167
|
|
|
1168
|
+
// Monitoring + control state arrived after the local-book schema —
|
|
1169
|
+
// optional in old blobs the same way.
|
|
1170
|
+
if (data $monitoring_enabled) != NIL
|
|
1171
|
+
{
|
|
1172
|
+
monitoring_enabled -> (data $monitoring_enabled) safe bool.
|
|
1173
|
+
}
|
|
1174
|
+
if (data $monitoring_inbox) != NIL
|
|
1175
|
+
{
|
|
1176
|
+
monitoring_inbox -> (data $monitoring_inbox) safe (monitoring_copy_t[]).
|
|
1177
|
+
}
|
|
1178
|
+
if (data $proxy_pending) != NIL
|
|
1179
|
+
{
|
|
1180
|
+
proxy_pending -> (data $proxy_pending) safe proxy_pending_t.
|
|
1181
|
+
}
|
|
1182
|
+
if (data $monitoring_proxy) != NIL
|
|
1183
|
+
{
|
|
1184
|
+
monitoring_proxy -> (data $monitoring_proxy) safe proxy_binding_t.
|
|
1185
|
+
}
|
|
1186
|
+
if (data $control_inbox) != NIL
|
|
1187
|
+
{
|
|
1188
|
+
control_inbox -> (data $control_inbox) safe (control_req_t[]).
|
|
1189
|
+
}
|
|
1190
|
+
|
|
811
1191
|
// Pending introducers' keys too: their channel to me predates approval,
|
|
812
1192
|
// so it must survive an upgrade exactly like an approved contact's.
|
|
813
1193
|
sc pending_introductions -- ( -> p)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adapt-toolkit/a2adapt",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.5",
|
|
4
4
|
"description": "MCP server daemon for a2adapt — one native ADAPT wrapper hosting N self-sovereign identities, exposing secure agent-to-agent messaging tools over HTTP (Streamable HTTP). Run `a2adapt-mcp start`.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|