@ouro.bot/friends 0.1.0-alpha.5 → 0.1.0-alpha.7

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.
Files changed (52) hide show
  1. package/README.md +65 -16
  2. package/changelog.json +12 -0
  3. package/dist/a2a-client/index.d.ts +1 -0
  4. package/dist/a2a-client/index.js +5 -1
  5. package/dist/a2a-client/roster-verify.d.ts +15 -0
  6. package/dist/a2a-client/roster-verify.js +61 -0
  7. package/dist/account-roster.d.ts +52 -0
  8. package/dist/account-roster.js +108 -0
  9. package/dist/agent-peer.js +5 -1
  10. package/dist/audit.d.ts +42 -0
  11. package/dist/audit.js +86 -0
  12. package/dist/connect-authority.d.ts +43 -0
  13. package/dist/connect-authority.js +84 -0
  14. package/dist/connect.d.ts +55 -0
  15. package/dist/connect.js +160 -0
  16. package/dist/coordination.d.ts +17 -1
  17. package/dist/coordination.js +80 -6
  18. package/dist/file-bundle.d.ts +5 -0
  19. package/dist/file-bundle.js +4 -0
  20. package/dist/friend-lookup.d.ts +9 -0
  21. package/dist/friend-lookup.js +69 -0
  22. package/dist/identity.d.ts +17 -0
  23. package/dist/identity.js +68 -0
  24. package/dist/index.d.ts +20 -0
  25. package/dist/index.js +51 -2
  26. package/dist/mailbox/index.d.ts +13 -10
  27. package/dist/mailbox/index.js +1 -1
  28. package/dist/mcp/dispatch.d.ts +27 -1
  29. package/dist/mcp/dispatch.js +110 -3
  30. package/dist/mcp/run-main.js +8 -5
  31. package/dist/mcp/schemas.js +51 -4
  32. package/dist/mcp/server.d.ts +9 -0
  33. package/dist/mcp/server.js +2 -2
  34. package/dist/mission-result.d.ts +82 -0
  35. package/dist/mission-result.js +200 -0
  36. package/dist/mission-store-file.js +8 -0
  37. package/dist/resolver.d.ts +32 -1
  38. package/dist/resolver.js +50 -3
  39. package/dist/roster-store-file.d.ts +16 -0
  40. package/dist/roster-store-file.js +125 -0
  41. package/dist/roster-store-memory.d.ts +9 -0
  42. package/dist/roster-store-memory.js +20 -0
  43. package/dist/roster-store.d.ts +29 -0
  44. package/dist/roster-store.js +9 -0
  45. package/dist/roster-verifier.d.ts +23 -0
  46. package/dist/roster-verifier.js +47 -0
  47. package/dist/trust-explanation.d.ts +7 -1
  48. package/dist/trust-explanation.js +52 -34
  49. package/dist/trust-mutation.d.ts +13 -1
  50. package/dist/trust-mutation.js +31 -2
  51. package/dist/types.d.ts +64 -0
  52. package/package.json +2 -1
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAgentIdentity = resolveAgentIdentity;
4
+ exports.withMigratedIdentity = withMigratedIdentity;
5
+ // Agent identity migrate-on-read (p11 Item 2 — the DID re-key).
6
+ //
7
+ // The durable identity home is `AgentMeta.identity` ({ did, pinnedKey?, handle?,
8
+ // pinnedAt? }). Legacy records carry only the optional `a2a.did` hint (or nothing).
9
+ // `resolveAgentIdentity` reads either, preferring the durable home and lifting
10
+ // `a2a.did` on a miss (migrate-on-read), mirroring FileGrantStore.normalize's
11
+ // legacy-field handling. `withMigratedIdentity` backfills `identity.did` from
12
+ // `a2a.did` so the next `put` persists it forward (migrate-on-write), matching the
13
+ // resolver's local-id migration + the grant subjectFriendId→subjectKey pattern.
14
+ const observability_1 = require("./observability");
15
+ /** Read an agent's durable identity, preferring `meta.identity` and lifting the
16
+ * legacy `meta.a2a.did` on a miss (migrate-on-read). Returns `{}` for a did-less
17
+ * or absent meta. (Unit 4a stub — not implemented.) */
18
+ function resolveAgentIdentity(meta) {
19
+ if (!meta)
20
+ return {};
21
+ // Durable home wins (authoritative). Spread only the present optional fields so
22
+ // a partial identity ({ did } only) doesn't surface undefined keys. SECURITY
23
+ // (finding 6, LOW): an empty-string did is NOT a did — omit it so it can never be a
24
+ // matchable identity key (ties to findFriendByDid's falsy-did guard, finding 4).
25
+ //
26
+ // NOTE (finding 5-D): when BOTH meta.identity.did and a legacy meta.a2a.did are
27
+ // present and DIVERGE, the durable home silently wins here. That divergence can be a
28
+ // tampering signal (a record's legacy hint disagreeing with its pinned identity).
29
+ // We don't warn from this hot path (resolveAgentIdentity runs per-record in the
30
+ // findFriendByDid scan and per-audit-derivation); a divergence audit belongs in a
31
+ // write-time reconciliation (e.g. withMigratedIdentity / the onboard path), not here.
32
+ if (meta.identity) {
33
+ const { did, pinnedKey, handle, pinnedAt } = meta.identity;
34
+ return {
35
+ ...(did ? { did } : {}),
36
+ ...(pinnedKey !== undefined ? { pinnedKey } : {}),
37
+ ...(handle !== undefined ? { handle } : {}),
38
+ ...(pinnedAt !== undefined ? { pinnedAt } : {}),
39
+ };
40
+ }
41
+ // Migrate-on-read: lift the legacy a2a.did hint when the durable home is absent.
42
+ // A falsy (absent or empty-string) hint is treated as no-did.
43
+ if (meta.a2a?.did)
44
+ return { did: meta.a2a.did };
45
+ return {};
46
+ }
47
+ /** Return a meta whose `identity.did` is backfilled from `a2a.did` when the durable
48
+ * home is absent (migrate-on-write); a meta already carrying `identity` is returned
49
+ * unchanged (no clobber). Absent meta is returned as-is. (Unit 4a stub.) */
50
+ function withMigratedIdentity(meta) {
51
+ if (!meta)
52
+ return undefined;
53
+ // Already carries the durable home → no clobber.
54
+ if (meta.identity)
55
+ return meta;
56
+ // Nothing to migrate from → unchanged. A falsy (absent or empty-string) a2a.did is
57
+ // not a real did to backfill (finding 6).
58
+ if (!meta.a2a?.did)
59
+ return meta;
60
+ // Backfill identity.did from the legacy a2a.did so the next put persists forward.
61
+ (0, observability_1.emitNervesEvent)({
62
+ component: "friends",
63
+ event: "friends.identity_migrated",
64
+ message: "backfilled AgentMeta.identity.did from legacy a2a.did",
65
+ meta: { did: meta.a2a.did },
66
+ });
67
+ return { ...meta, identity: { did: meta.a2a.did } };
68
+ }
package/dist/index.d.ts CHANGED
@@ -29,8 +29,25 @@ export { upsertGroupContextParticipants } from "./group-context";
29
29
  export { accumulateFriendTokens } from "./tokens";
30
30
  export { applyFriendNote } from "./notes";
31
31
  export { setFriendTrust } from "./trust-mutation";
32
+ export type { SetFriendTrustContext } from "./trust-mutation";
33
+ export { MemoryAuditSink, FileAuditSink, auditPathFor } from "./audit";
34
+ export type { AuditSink, ControlPlaneAuditRecord } from "./audit";
32
35
  export { linkExternalId, unlinkExternalId } from "./link-identity";
33
36
  export { upsertAgentPeer } from "./agent-peer";
37
+ export { connectAgents } from "./connect";
38
+ export type { ConnectPeer, ConnectAgentsInput, ConnectAgentsDeps, ConnectResult, ConnectStatus } from "./connect";
39
+ export { authorizeConnect } from "./connect-authority";
40
+ export type { AuthorizeConnectInput, ConnectAuthorization } from "./connect-authority";
41
+ export { resolveAgentIdentity, withMigratedIdentity } from "./identity";
42
+ export type { ResolvedAgentIdentity } from "./identity";
43
+ export { findFriendByDid } from "./friend-lookup";
44
+ export { FileRosterStore, rostersDirFor } from "./roster-store-file";
45
+ export type { RosterStore, AccountRoster, RosterPin } from "./roster-store";
46
+ export { identityRosterVerifier, DEFAULT_ROSTER_VERIFIER } from "./roster-verifier";
47
+ export type { RosterVerifier } from "./roster-verifier";
48
+ export { MemoryRosterStore } from "./roster-store-memory";
49
+ export { evaluateAccountMembership, verifiedCandidate, _resetRosterVerifierWarningForTest } from "./account-roster";
50
+ export type { AccountMembershipDecision, AccountMembershipResult, EvaluateAccountMembershipInput, VerifiedCandidate, } from "./account-roster";
34
51
  export { recordRelationshipOutcome } from "./outcomes";
35
52
  export { recordMission } from "./missions";
36
53
  export type { RecordMissionInput } from "./missions";
@@ -44,6 +61,9 @@ export { prepareMissionShare, importMissionShare } from "./mission-share";
44
61
  export type { MissionShareEnvelope, SharedLearning, PrepareMissionShareInput, PrepareMissionShareResult, PrepareMissionShareStatus, ImportMissionShareInput, ImportMissionShareOptions, ImportMissionShareResult, ImportMissionShareStatus, } from "./mission-share";
45
62
  export { prepareCoordination, importCoordination } from "./coordination";
46
63
  export type { CoordinationEnvelope, PrepareCoordinationInput, PrepareCoordinationResult, PrepareCoordinationStatus, ImportCoordinationInput, ImportCoordinationOptions, ImportCoordinationResult, ImportCoordinationStatus, } from "./coordination";
64
+ export { prepareMissionResult, importMissionResult } from "./mission-result";
65
+ export type { PrepareMissionResultInput, PrepareMissionResultResult, PrepareMissionResultStatus, ImportMissionResultInput, ImportMissionResultOptions, ImportMissionResultResult, ImportMissionResultStatus, } from "./mission-result";
66
+ export type { MissionTaskSpec, MissionResult, MissionResultEnvelope } from "./types";
47
67
  export { grantShare, revokeShare, listShares, isGrantEffective } from "./grants";
48
68
  export { emitNervesEvent, setNervesEmitter, } from "./observability";
49
69
  export type { NervesEvent, NervesEmitter, LogLevel } from "./observability";
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@
6
6
  // multi-agent (a2a peer) aware, consumed through the FriendStore interface +
7
7
  // FriendResolver.
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.revokeShare = exports.grantShare = exports.importCoordination = exports.prepareCoordination = exports.importMissionShare = exports.prepareMissionShare = exports.importProfileShare = exports.prepareProfileShare = exports.DEFAULT_AGENT_VERIFIER = exports.tofuVerifier = exports.DEFAULT_CONSENT_POLICY = exports.tieredPolicy = exports.trustImpliedPolicy = exports.strictPolicy = exports.resolveRoom = exports.whoami = exports.recordMission = exports.recordRelationshipOutcome = exports.upsertAgentPeer = exports.unlinkExternalId = exports.linkExternalId = exports.setFriendTrust = exports.applyFriendNote = exports.accumulateFriendTokens = exports.upsertGroupContextParticipants = exports.DEFAULT_STANDING_RULE = exports.explainStanding = exports.assessStanding = exports.describeTrustContext = exports.getAlwaysOnSenseNames = exports.isRemoteChannel = exports.channelToFacing = exports.getChannelCapabilities = exports._setMachineOwnerUsernameForTest = exports.isLocalMachineOwnerIdentity = exports.machineOwnerUsername = exports.FriendResolver = exports.openFileBundle = exports.missionsDirFor = exports.FileMissionStore = exports.grantsDirFor = exports.FileGrantStore = exports.FileFriendStore = exports.isCoordinationIntent = exports.isShareScope = exports.isIntegration = exports.isIdentityProvider = exports.isTrustedLevel = exports.IDENTITY_SCOPES = exports.TRUSTED_LEVELS = void 0;
10
- exports.setNervesEmitter = exports.emitNervesEvent = exports.isGrantEffective = exports.listShares = void 0;
9
+ exports.recordMission = exports.recordRelationshipOutcome = exports._resetRosterVerifierWarningForTest = exports.verifiedCandidate = exports.evaluateAccountMembership = exports.MemoryRosterStore = exports.DEFAULT_ROSTER_VERIFIER = exports.identityRosterVerifier = exports.rostersDirFor = exports.FileRosterStore = exports.findFriendByDid = exports.withMigratedIdentity = exports.resolveAgentIdentity = exports.authorizeConnect = exports.connectAgents = exports.upsertAgentPeer = exports.unlinkExternalId = exports.linkExternalId = exports.auditPathFor = exports.FileAuditSink = exports.MemoryAuditSink = exports.setFriendTrust = exports.applyFriendNote = exports.accumulateFriendTokens = exports.upsertGroupContextParticipants = exports.DEFAULT_STANDING_RULE = exports.explainStanding = exports.assessStanding = exports.describeTrustContext = exports.getAlwaysOnSenseNames = exports.isRemoteChannel = exports.channelToFacing = exports.getChannelCapabilities = exports._setMachineOwnerUsernameForTest = exports.isLocalMachineOwnerIdentity = exports.machineOwnerUsername = exports.FriendResolver = exports.openFileBundle = exports.missionsDirFor = exports.FileMissionStore = exports.grantsDirFor = exports.FileGrantStore = exports.FileFriendStore = exports.isCoordinationIntent = exports.isShareScope = exports.isIntegration = exports.isIdentityProvider = exports.isTrustedLevel = exports.IDENTITY_SCOPES = exports.TRUSTED_LEVELS = void 0;
10
+ exports.setNervesEmitter = exports.emitNervesEvent = exports.isGrantEffective = exports.listShares = exports.revokeShare = exports.grantShare = exports.importMissionResult = exports.prepareMissionResult = exports.importCoordination = exports.prepareCoordination = exports.importMissionShare = exports.prepareMissionShare = exports.importProfileShare = exports.prepareProfileShare = exports.DEFAULT_AGENT_VERIFIER = exports.tofuVerifier = exports.DEFAULT_CONSENT_POLICY = exports.tieredPolicy = exports.trustImpliedPolicy = exports.strictPolicy = exports.resolveRoom = exports.whoami = void 0;
11
11
  // -- Values --
12
12
  var types_1 = require("./types");
13
13
  Object.defineProperty(exports, "TRUSTED_LEVELS", { enumerable: true, get: function () { return types_1.TRUSTED_LEVELS; } });
@@ -52,11 +52,50 @@ var notes_1 = require("./notes");
52
52
  Object.defineProperty(exports, "applyFriendNote", { enumerable: true, get: function () { return notes_1.applyFriendNote; } });
53
53
  var trust_mutation_1 = require("./trust-mutation");
54
54
  Object.defineProperty(exports, "setFriendTrust", { enumerable: true, get: function () { return trust_mutation_1.setFriendTrust; } });
55
+ // -- Control-plane audit (Bug B): append-only record of trust mutations --
56
+ var audit_1 = require("./audit");
57
+ Object.defineProperty(exports, "MemoryAuditSink", { enumerable: true, get: function () { return audit_1.MemoryAuditSink; } });
58
+ Object.defineProperty(exports, "FileAuditSink", { enumerable: true, get: function () { return audit_1.FileAuditSink; } });
59
+ Object.defineProperty(exports, "auditPathFor", { enumerable: true, get: function () { return audit_1.auditPathFor; } });
55
60
  var link_identity_1 = require("./link-identity");
56
61
  Object.defineProperty(exports, "linkExternalId", { enumerable: true, get: function () { return link_identity_1.linkExternalId; } });
57
62
  Object.defineProperty(exports, "unlinkExternalId", { enumerable: true, get: function () { return link_identity_1.unlinkExternalId; } });
58
63
  var agent_peer_1 = require("./agent-peer");
59
64
  Object.defineProperty(exports, "upsertAgentPeer", { enumerable: true, get: function () { return agent_peer_1.upsertAgentPeer; } });
65
+ // -- connect_to (brick 8, p11 inc2): the owner links one of their OWN agents into the
66
+ // fleet, gated to a management sense (local/closed) — `local` commits; an `open` sense
67
+ // downgrades to a confirm-prompt; `closed` is gated by a roster/membership check (never a
68
+ // blanket allow); a bare name with no resolvable handle/DID returns needs_handle (never
69
+ // fabricated). Writes one `action:"connect"` control-plane audit. `authorizeConnect` is the
70
+ // pure authority predicate (consumes a pre-computed AccountMembershipResult — core-clean).
71
+ var connect_1 = require("./connect");
72
+ Object.defineProperty(exports, "connectAgents", { enumerable: true, get: function () { return connect_1.connectAgents; } });
73
+ var connect_authority_1 = require("./connect-authority");
74
+ Object.defineProperty(exports, "authorizeConnect", { enumerable: true, get: function () { return connect_authority_1.authorizeConnect; } });
75
+ // -- Agent identity (p11 Item 2 — DID re-key): durable home + migrate-on-read --
76
+ var identity_1 = require("./identity");
77
+ Object.defineProperty(exports, "resolveAgentIdentity", { enumerable: true, get: function () { return identity_1.resolveAgentIdentity; } });
78
+ Object.defineProperty(exports, "withMigratedIdentity", { enumerable: true, get: function () { return identity_1.withMigratedIdentity; } });
79
+ // did-aware friend lookup (the durable cross-agent primary key is the DID).
80
+ var friend_lookup_1 = require("./friend-lookup");
81
+ Object.defineProperty(exports, "findFriendByDid", { enumerable: true, get: function () { return friend_lookup_1.findFriendByDid; } });
82
+ // -- Account roster (p11 Item 3): pinned roster + TOFU roster-key storage seam --
83
+ var roster_store_file_1 = require("./roster-store-file");
84
+ Object.defineProperty(exports, "FileRosterStore", { enumerable: true, get: function () { return roster_store_file_1.FileRosterStore; } });
85
+ Object.defineProperty(exports, "rostersDirFor", { enumerable: true, get: function () { return roster_store_file_1.rostersDirFor; } });
86
+ // -- RosterVerifier seam (Q1): core declares the interface + identity-only default;
87
+ // the a2a-client provides the Ed25519 impl (host-injected). Core stays crypto-free.
88
+ var roster_verifier_1 = require("./roster-verifier");
89
+ Object.defineProperty(exports, "identityRosterVerifier", { enumerable: true, get: function () { return roster_verifier_1.identityRosterVerifier; } });
90
+ Object.defineProperty(exports, "DEFAULT_ROSTER_VERIFIER", { enumerable: true, get: function () { return roster_verifier_1.DEFAULT_ROSTER_VERIFIER; } });
91
+ var roster_store_memory_1 = require("./roster-store-memory");
92
+ Object.defineProperty(exports, "MemoryRosterStore", { enumerable: true, get: function () { return roster_store_memory_1.MemoryRosterStore; } });
93
+ // -- Account-roster membership (Item 3 payoff): family via same_account for a
94
+ // key-verified, TOFU-pinned roster member; changed roster key hard-fails. --
95
+ var account_roster_1 = require("./account-roster");
96
+ Object.defineProperty(exports, "evaluateAccountMembership", { enumerable: true, get: function () { return account_roster_1.evaluateAccountMembership; } });
97
+ Object.defineProperty(exports, "verifiedCandidate", { enumerable: true, get: function () { return account_roster_1.verifiedCandidate; } });
98
+ Object.defineProperty(exports, "_resetRosterVerifierWarningForTest", { enumerable: true, get: function () { return account_roster_1._resetRosterVerifierWarningForTest; } });
60
99
  var outcomes_1 = require("./outcomes");
61
100
  Object.defineProperty(exports, "recordRelationshipOutcome", { enumerable: true, get: function () { return outcomes_1.recordRelationshipOutcome; } });
62
101
  var missions_1 = require("./missions");
@@ -91,6 +130,16 @@ Object.defineProperty(exports, "importMissionShare", { enumerable: true, get: fu
91
130
  var coordination_1 = require("./coordination");
92
131
  Object.defineProperty(exports, "prepareCoordination", { enumerable: true, get: function () { return coordination_1.prepareCoordination; } });
93
132
  Object.defineProperty(exports, "importCoordination", { enumerable: true, get: function () { return coordination_1.importCoordination; } });
133
+ // -- Result-return / delegation deliverable (gap-2, p11 inc2): B returns its result --
134
+ // prepareMissionResult (producer) / importMissionResult (consumer) carry B's actual
135
+ // produced deliverable back to A — attributed to B, correlated to A's delegation via
136
+ // missionKey + requestId, consent-gated via the existing "coordinate" scope, lands
137
+ // quarantined + attributed on import, trust-capped, non-transitive, first-party-inviolable.
138
+ // `MissionTaskSpec` (gap-1) rides the CoordinationEnvelope; the new MissionRecord fields
139
+ // (delegations / importedDelegations / results / importedResults) are additive.
140
+ var mission_result_1 = require("./mission-result");
141
+ Object.defineProperty(exports, "prepareMissionResult", { enumerable: true, get: function () { return mission_result_1.prepareMissionResult; } });
142
+ Object.defineProperty(exports, "importMissionResult", { enumerable: true, get: function () { return mission_result_1.importMissionResult; } });
94
143
  var grants_1 = require("./grants");
95
144
  Object.defineProperty(exports, "grantShare", { enumerable: true, get: function () { return grants_1.grantShare; } });
96
145
  Object.defineProperty(exports, "revokeShare", { enumerable: true, get: function () { return grants_1.revokeShare; } });
@@ -1,6 +1,7 @@
1
1
  import type { ProfileShareEnvelope } from "../share";
2
2
  import type { MissionShareEnvelope } from "../mission-share";
3
3
  import type { CoordinationEnvelope } from "../coordination";
4
+ import type { MissionResultEnvelope } from "../types";
4
5
  /** The mailbox wire-format version. Bumped only on a breaking message change. */
5
6
  export declare const MAILBOX_VERSION = 1;
6
7
  /** A mailbox message: the TRANSPORT wrapper around a verbatim share envelope. The
@@ -14,19 +15,21 @@ export interface MailboxMessage {
14
15
  toAgentId: string;
15
16
  issuedAt: string;
16
17
  /** The payload discriminant. The host branches on it to call importProfileShare
17
- * vs importMissionShare vs importCoordination. The mailbox itself is
18
- * payload-agnostic — this union grows by one leaf per brick (additive, backward-
19
- * compatible); buildOutgoing/readIncoming carry any of them unchanged. */
20
- kind: "profile_share" | "mission_share" | "coordination";
21
- envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope;
18
+ * vs importMissionShare vs importCoordination vs importMissionResult. The mailbox
19
+ * itself is payload-agnostic — this union grows by one leaf per brick (additive,
20
+ * backward-compatible); buildOutgoing/readIncoming carry any of them unchanged.
21
+ * `mission_result` (gap-2) carries B's delegation deliverable back to A. */
22
+ kind: "profile_share" | "mission_share" | "coordination" | "mission_result";
23
+ envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope | MissionResultEnvelope;
22
24
  }
23
25
  export interface BuildOutgoingInput {
24
- envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope;
26
+ envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope | MissionResultEnvelope;
25
27
  fromAgentId: string;
26
28
  toAgentId: string;
27
29
  /** The payload discriminant. Defaults to "profile_share" for backward-compat;
28
- * a mission share passes "mission_share", a coordination message "coordination". */
29
- kind?: "profile_share" | "mission_share" | "coordination";
30
+ * a mission share passes "mission_share", a coordination message "coordination",
31
+ * a result-return "mission_result" (gap-2). */
32
+ kind?: "profile_share" | "mission_share" | "coordination" | "mission_result";
30
33
  /** Injectable ISO clock for deterministic tests; defaults to now. */
31
34
  now?: string;
32
35
  }
@@ -54,8 +57,8 @@ export interface IncomingMessage {
54
57
  fromAgentId: string;
55
58
  toAgentId: string;
56
59
  issuedAt: string;
57
- kind: "profile_share" | "mission_share" | "coordination";
58
- envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope;
60
+ kind: "profile_share" | "mission_share" | "coordination" | "mission_result";
61
+ envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope | MissionResultEnvelope;
59
62
  relativePath: string;
60
63
  }
61
64
  export interface ReadIncomingInput {
@@ -86,7 +86,7 @@ function isWellFormedWrapper(value) {
86
86
  typeof value.fromAgentId === "string" &&
87
87
  typeof value.toAgentId === "string" &&
88
88
  typeof value.issuedAt === "string" &&
89
- (value.kind === "profile_share" || value.kind === "mission_share" || value.kind === "coordination") &&
89
+ (value.kind === "profile_share" || value.kind === "mission_share" || value.kind === "coordination" || value.kind === "mission_result") &&
90
90
  typeof value.envelope === "object" &&
91
91
  value.envelope !== null &&
92
92
  !Array.isArray(value.envelope));
@@ -1,14 +1,40 @@
1
1
  import type { FriendStore } from "../store";
2
2
  import type { GrantStore } from "../grant-store";
3
3
  import type { MissionStore } from "../mission-store";
4
+ import type { AuditSink } from "../audit";
5
+ import type { SenseType } from "../types";
6
+ import type { AccountMembershipResult } from "../account-roster";
4
7
  type Args = Record<string, unknown>;
5
8
  export interface DispatchResult {
6
9
  result: unknown;
7
10
  isError: boolean;
8
11
  }
12
+ /** WHO/WHENCE context stamped onto a control-plane audit record (finding 3). The MCP
13
+ * server passes the local owner/sense it was constructed with. */
14
+ export interface ControlPlaneContext {
15
+ actor?: string;
16
+ originSense?: string;
17
+ /** The management SENSE the gate evaluates for `connect_to` (p11 inc2, brick 8).
18
+ * The stdio path is owner-only, so this defaults to `local` (`?? "local"`) — a
19
+ * `local` management sense COMMITS. A network/multi-tenant transport that constructs
20
+ * the server MUST pass its real senseType (`open` ⇒ confirm-prompt downgrade; `closed`
21
+ * ⇒ gated by `membership`). Distinct from `originSense` (a free-form audit string like
22
+ * "stdio"); this is the typed SenseType the authority predicate consumes. */
23
+ senseType?: SenseType;
24
+ /** The PRE-COMPUTED account-roster membership for a `closed`-sense `connect_to`
25
+ * (p11 inc2). The stdio `local` path never consults it (left `undefined`); a `closed`
26
+ * network transport supplies the membership it already evaluated against the roster.
27
+ * The boundary stays thin — it forwards this to the library, computing no membership
28
+ * itself (the MCP `resolve_party` path does not wire a roster context). */
29
+ membership?: AccountMembershipResult;
30
+ }
31
+ /** Whether wiring an AuditSink should also stamp a record for an `onboard_agent` trust
32
+ * seat: only when the owner explicitly set a trustLevel (a deliberate trust decision).
33
+ * A cold contact with no trustLevel lands at the safe `stranger` default (Bug A) and is
34
+ * NOT an owner trust mutation, so it is not audited. */
9
35
  export declare function coerceBool(v: unknown): boolean;
10
36
  export declare function coerceInt(v: unknown): number | undefined;
11
37
  export declare function coerceString(v: unknown): string;
12
38
  export declare function coerceOptionalString(v: unknown): string | undefined;
13
- export declare function dispatchTool(store: FriendStore, name: string, args: Args, grants?: GrantStore, missions?: MissionStore): Promise<DispatchResult>;
39
+ export declare function dispatchTool(store: FriendStore, name: string, args: Args, grants?: GrantStore, missions?: MissionStore, audit?: AuditSink, controlContext?: ControlPlaneContext): Promise<DispatchResult>;
14
40
  export {};
@@ -11,6 +11,7 @@ exports.dispatchTool = dispatchTool;
11
11
  // the library fns. `dispatchTool` is a flat tool → library-fn map (D9/D10) with
12
12
  // NO domain logic of its own — every behavior lives in the friends library.
13
13
  const observability_1 = require("../observability");
14
+ const identity_1 = require("../identity");
14
15
  const types_1 = require("../types");
15
16
  const resolver_1 = require("../resolver");
16
17
  const trust_explanation_1 = require("../trust-explanation");
@@ -31,6 +32,19 @@ const missions_1 = require("../missions");
31
32
  const mission_share_1 = require("../mission-share");
32
33
  const coordination_1 = require("../coordination");
33
34
  const types_2 = require("../types");
35
+ const connect_1 = require("../connect");
36
+ const mission_result_1 = require("../mission-result");
37
+ /** SECURITY (finding 3-A): the friends MCP server speaks JSON-RPC over **stdio**, and
38
+ * stdio is an owner-only channel — the local user who launched the process is the only
39
+ * actor. So when no explicit controlContext is wired, audited mutations are attributed
40
+ * to the stdio owner boundary rather than the generic "unknown". A network/multi-tenant
41
+ * transport MUST pass its own authenticated actor instead of relying on these. */
42
+ const STDIO_OWNER_ACTOR = "owner:stdio";
43
+ const STDIO_ORIGIN_SENSE = "stdio";
44
+ /** Whether wiring an AuditSink should also stamp a record for an `onboard_agent` trust
45
+ * seat: only when the owner explicitly set a trustLevel (a deliberate trust decision).
46
+ * A cold contact with no trustLevel lands at the safe `stranger` default (Bug A) and is
47
+ * NOT an owner trust mutation, so it is not audited. */
34
48
  function coerceBool(v) {
35
49
  return v === true || v === "true";
36
50
  }
@@ -69,13 +83,18 @@ const NO_GRANT_STORE = { ok: false, status: "unsupported", message: "no grant st
69
83
  * embedding). The mission ledger needs mission persistence, so report it cleanly
70
84
  * rather than guessing. */
71
85
  const NO_MISSION_STORE = { ok: false, status: "unsupported", message: "no mission store configured (mission tools require one)" };
72
- async function dispatchTool(store, name, args, grants, missions) {
86
+ async function dispatchTool(store, name, args, grants, missions, audit, controlContext) {
73
87
  (0, observability_1.emitNervesEvent)({
74
88
  component: "clients",
75
89
  event: "clients.mcp_dispatch",
76
90
  message: "dispatching friends mcp tool",
77
91
  meta: { tool: name },
78
92
  });
93
+ // SECURITY (finding 3 / 3-A): resolve the WHO/WHENCE for an audited mutation. With
94
+ // no explicit context, attribute to the stdio owner boundary (the only actor on an
95
+ // owner-only stdio channel) rather than the generic "unknown".
96
+ const auditActor = controlContext?.actor ?? STDIO_OWNER_ACTOR;
97
+ const auditOriginSense = controlContext?.originSense ?? STDIO_ORIGIN_SENSE;
79
98
  switch (name) {
80
99
  case "resolve_party": {
81
100
  const provider = coerceString(args.provider);
@@ -188,7 +207,14 @@ async function dispatchTool(store, name, args, grants, missions) {
188
207
  return { result: results, isError: false };
189
208
  }
190
209
  case "set_trust": {
191
- const result = await (0, trust_mutation_1.setFriendTrust)(store, coerceString(args.friendId), coerceString(args.trustLevel));
210
+ // SECURITY (finding 3): thread the audit sink + owner/sense context so the LIVE
211
+ // trust mutation actually writes a control-plane record. With no sink wired,
212
+ // setFriendTrust treats the ctx as a no-op (back-compat).
213
+ const result = await (0, trust_mutation_1.setFriendTrust)(store, coerceString(args.friendId), coerceString(args.trustLevel), {
214
+ ...(audit ? { sink: audit } : {}),
215
+ actor: auditActor,
216
+ originSense: auditOriginSense,
217
+ });
192
218
  return { result, isError: result.ok === false };
193
219
  }
194
220
  case "link_identity": {
@@ -207,16 +233,59 @@ async function dispatchTool(store, name, args, grants, missions) {
207
233
  return { result, isError: result.ok === false };
208
234
  }
209
235
  case "onboard_agent": {
236
+ const explicitTrustLevel = coerceOptionalString(args.trustLevel);
210
237
  const record = await (0, agent_peer_1.upsertAgentPeer)(store, {
211
238
  name: coerceString(args.name),
212
239
  agentId: coerceString(args.agentId),
213
- trustLevel: coerceOptionalString(args.trustLevel),
240
+ trustLevel: explicitTrustLevel,
214
241
  a2a: parseMaybeJson(args.a2a),
215
242
  mailbox: parseMaybeJson(args.mailbox),
216
243
  bundleName: coerceOptionalString(args.bundleName),
217
244
  });
245
+ // SECURITY (finding 3): an owner-initiated trust SEAT (an explicit trustLevel) is
246
+ // a control-plane trust mutation, so audit it through the wired sink. A cold
247
+ // contact with no trustLevel falls to the safe `stranger` default (Bug A) — not
248
+ // an owner trust decision — so it is left unaudited.
249
+ if (audit && explicitTrustLevel !== undefined) {
250
+ const targetDid = (0, identity_1.resolveAgentIdentity)(record.agentMeta).did;
251
+ const auditRecord = {
252
+ action: "set_trust",
253
+ targetId: record.id,
254
+ ...(targetDid !== undefined ? { targetDid } : {}),
255
+ level: explicitTrustLevel,
256
+ actor: auditActor,
257
+ originSense: auditOriginSense,
258
+ ts: record.updatedAt,
259
+ };
260
+ await audit.append(auditRecord);
261
+ }
218
262
  return { result: record, isError: false };
219
263
  }
264
+ case "connect_to": {
265
+ // The management-sense control plane (p11 inc2, brick 8). The boundary stays
266
+ // thin — coerce the peer handles + level, resolve the gate's management sense
267
+ // (the stdio path is owner-only ⇒ `local`), and forward to the library, which
268
+ // owns the authority gate + disambiguation + introduce + audit. `isError` reflects
269
+ // ok===false (a `downgraded` / `needs_handle_or_introduction` result is an error
270
+ // result like the other mutation cases).
271
+ const result = await (0, connect_1.connectAgents)(store, {
272
+ peer: {
273
+ agentId: coerceOptionalString(args.agentId),
274
+ did: coerceOptionalString(args.did),
275
+ name: coerceOptionalString(args.name),
276
+ },
277
+ // The stdio default is `local` (owner-only); a network transport supplies its
278
+ // real senseType via controlContext. The proof's stdio path commits.
279
+ senseType: controlContext?.senseType ?? "local",
280
+ ...(controlContext?.membership ? { membership: controlContext.membership } : {}),
281
+ trustLevel: coerceOptionalString(args.trustLevel),
282
+ }, {
283
+ ...(audit ? { audit } : {}),
284
+ actor: auditActor,
285
+ originSense: auditOriginSense,
286
+ });
287
+ return { result, isError: result.ok === false };
288
+ }
220
289
  case "whoami": {
221
290
  return { result: await (0, whoami_1.whoami)(store), isError: false };
222
291
  }
@@ -396,6 +465,9 @@ async function dispatchTool(store, name, args, grants, missions) {
396
465
  note: coerceOptionalString(args.note),
397
466
  proposedAssignee: parseMaybeJson(args.proposedAssignee),
398
467
  selfAgentId,
468
+ // gap-1 (p11 inc2): an optional task-spec, meaningful only on a `request`. The
469
+ // library mints the requestId + records the delegation first-party.
470
+ task: parseMaybeJson(args.task),
399
471
  proof: coerceOptionalString(args.proof),
400
472
  });
401
473
  return { result, isError: result.ok === false };
@@ -425,6 +497,41 @@ async function dispatchTool(store, name, args, grants, missions) {
425
497
  }
426
498
  return { result: record.coordination ?? { assignee: undefined, log: [] }, isError: false };
427
499
  }
500
+ case "send_result": {
501
+ // Producer (gap-2): B returns its deliverable. Self identity comes from whoami
502
+ // (the dispatch is store-only); the mission is named by its missionKey inside the
503
+ // library. Gated on BOTH a GrantStore (consent via the "coordinate" scope) and a
504
+ // MissionStore — exactly like share_mission.
505
+ if (!missions || !grants)
506
+ return { result: NO_MISSION_STORE, isError: true };
507
+ const self = await (0, whoami_1.whoami)(store);
508
+ const selfAgentId = self.selfFriendId ?? "";
509
+ const result = await (0, mission_result_1.prepareMissionResult)(missions, store, grants, {
510
+ missionId: coerceString(args.missionId),
511
+ toAgentId: coerceString(args.toAgentId),
512
+ requestId: coerceString(args.requestId),
513
+ result: parseMaybeJson(args.result) ?? { summary: "" },
514
+ selfAgentId,
515
+ proof: coerceOptionalString(args.proof),
516
+ });
517
+ return { result, isError: result.ok === false };
518
+ }
519
+ case "import_result": {
520
+ // Consumer (gap-2): A imports B's deliverable. Gated on the MissionStore, like
521
+ // import_mission. An invalid/malformed envelope is a clean `invalid` result.
522
+ if (!missions)
523
+ return { result: NO_MISSION_STORE, isError: true };
524
+ const envelope = parseMaybeJson(args.envelope);
525
+ if (!envelope || typeof envelope !== "object") {
526
+ return { result: { ok: false, status: "invalid", message: "an envelope object is required" }, isError: true };
527
+ }
528
+ const result = await (0, mission_result_1.importMissionResult)(missions, {
529
+ envelope,
530
+ fromAgentId: coerceString(args.fromAgentId),
531
+ trustOfSource: coerceString(args.trustOfSource),
532
+ });
533
+ return { result, isError: result.ok === false };
534
+ }
428
535
  default: {
429
536
  return { result: { error: `Unknown tool: ${name}` }, isError: true };
430
537
  }
@@ -35,11 +35,14 @@ function runMain(argv, env, io) {
35
35
  message: "friends mcp run-main",
36
36
  meta: { source },
37
37
  });
38
- // The consent-grant + mission collections are sibling `_grants/` + `_missions/`
39
- // dirs under the friends dir, so the single `--dir` wires the whole substrate
40
- // (friends + consent + missions). `openFileBundle` encapsulates that convention.
41
- const { store, grants, missions } = (0, file_bundle_1.openFileBundle)(dir);
42
- const server = (0, server_1.createFriendsMcpServer)({ store, grants, missions, stdin: io.stdin, stdout: io.stdout });
38
+ // The consent-grant + mission + audit collections are sibling `_grants/`,
39
+ // `_missions/`, `_audit/` dirs under the friends dir, so the single `--dir` wires the
40
+ // whole substrate. `openFileBundle` encapsulates that convention.
41
+ const { store, grants, missions, audit } = (0, file_bundle_1.openFileBundle)(dir);
42
+ // SECURITY (finding 3 / 3-A): thread the FileAuditSink into the live server so trust
43
+ // mutations are audited. The `friends-mcp` bin speaks owner-only stdio, so the
44
+ // default actor/originSense (the stdio owner boundary) is the correct attribution.
45
+ const server = (0, server_1.createFriendsMcpServer)({ store, grants, missions, audit, stdin: io.stdin, stdout: io.stdout });
43
46
  server.start();
44
47
  return server;
45
48
  }
@@ -3,14 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getToolSchemas = getToolSchemas;
4
4
  // MCP tool schemas for the friends server.
5
5
  //
6
- // 29 tools — a thin 1:1 surface over the friends library (D7): the original 14,
6
+ // 32 tools — a thin 1:1 surface over the friends library (D7): the original 14,
7
7
  // the cross-agent moat surface (resolve_room, import_profile, grant_share,
8
8
  // revoke_share, list_shares; share_profile is de-stubbed in place), the brick-3
9
9
  // mission ledger (record_mission, get_mission, list_missions, share_mission,
10
10
  // import_mission), the brick-4 earned-standing lenses (assess_standing,
11
11
  // explain_standing — read-only, advisory; never write trust, never on the wire),
12
- // and the brick-5 coordination verbs (coordinate, import_coordination,
13
- // get_coordination — negotiate WHO does a mission; advisory assignment metadata).
12
+ // the brick-5 coordination verbs (coordinate, import_coordination,
13
+ // get_coordination — negotiate WHO does a mission; advisory assignment metadata),
14
+ // and the p11-inc2 own-fleet delegation surface (connect_to — the management-sense
15
+ // control plane that links two own agents + audits action:"connect"; send_result,
16
+ // import_result — the result-return / delegation deliverable channel that carries
17
+ // B's produced artifact back to A, attributed + correlated + quarantined on import).
14
18
  // Each schema follows JSON Schema for
15
19
  // `inputSchema` as required by MCP. The shape mirrors the harness's McpToolSchema
16
20
  // so the same client tooling consumes both.
@@ -187,7 +191,7 @@ function getToolSchemas() {
187
191
  properties: {
188
192
  name: { type: "string", description: "the peer agent's name" },
189
193
  agentId: { type: "string", description: "the a2a agent id" },
190
- trustLevel: { type: "string", description: "trust level (default acquaintance)" },
194
+ trustLevel: { type: "string", description: "trust level (default stranger (cold contact))" },
191
195
  a2a: { type: "object", description: "a2a coordinates { cardUrl?, endpointUrl?, protocolVersion? }" },
192
196
  mailbox: { type: "object", description: "optional A2A git-mailbox coords { repo, selfOutboxAgentId }" },
193
197
  bundleName: { type: "string", description: "optional bundle name" },
@@ -195,6 +199,20 @@ function getToolSchemas() {
195
199
  required: ["name", "agentId"],
196
200
  },
197
201
  },
202
+ {
203
+ name: "connect_to",
204
+ description: "Management-sense control plane (brick 8): the owner links one of their OWN agents into this agent's fleet — introduce a peer by agentId/did/name at a trust level (default family for own-fleet). Authority-gated: commits inline ONLY from a local (owner-only stdio) or roster-verified same-account closed sense; an open sense never commits inline (it downgrades to a confirm-prompt); a bare name with no resolvable handle/DID and no record hit returns needs_handle_or_introduction (never fabricates a target). Writes one control-plane audit record (action:'connect') through the wired sink. Returns { ok:true, status:'connected', record } or { ok:false, status:'downgraded'|'needs_handle_or_introduction', downgrade? }.",
205
+ inputSchema: {
206
+ type: "object",
207
+ properties: {
208
+ agentId: { type: "string", description: "the peer agent's join-key agentId (an owner-supplied handle)" },
209
+ did: { type: "string", description: "the peer's DID (an alternative handle; must resolve to an existing record)" },
210
+ name: { type: "string", description: "the peer's colloquial name (resolves ONLY by matching an existing record — never fabricated)" },
211
+ trustLevel: { type: "string", enum: ["family", "friend", "acquaintance", "stranger"], description: "the trust to link at (default family for own-fleet linked agents)" },
212
+ proof: { type: "string", description: "optional opaque proof slot (reserved; the TOFU path ignores it)" },
213
+ },
214
+ },
215
+ },
198
216
  {
199
217
  name: "whoami",
200
218
  description: "Resolve who the machine owner is and which friend record represents the self.",
@@ -365,6 +383,7 @@ function getToolSchemas() {
365
383
  intent: { type: "string", enum: ["request", "offer", "accept", "decline", "handoff"], description: "the coordination verb: request (will you take this?) / offer (I'll take this) / accept (yes, I'm on it — sets assignee=self) / decline (no) / handoff (it's yours now — you must hold the assignment; proposes a new assignee)" },
366
384
  note: { type: "string", description: "optional free text carried on the message + logged" },
367
385
  proposedAssignee: { type: "object", description: "the proposed new assignee { agentId?, agentName? } — meaningful ONLY on intent=handoff" },
386
+ task: { type: "object", description: "optional delegation task-spec { summary, details?, inputs? } — meaningful ONLY on intent=request (gap-2); the producer mints a requestId, stamps it on the envelope, and records the delegation first-party for the result-return to correlate against" },
368
387
  proof: { type: "string", description: "optional opaque proof to stamp on the envelope (for a non-TOFU recipient verifier)" },
369
388
  },
370
389
  required: ["missionId", "toAgentId", "intent"],
@@ -394,5 +413,33 @@ function getToolSchemas() {
394
413
  required: ["missionId"],
395
414
  },
396
415
  },
416
+ {
417
+ name: "send_result",
418
+ description: "Producer (gap-2 — the result-return): B returns its DELIVERABLE for a delegation, attributed to B (from whoami) + correlated to A's task-spec by requestId, named by the mission's missionKey (never the local uuid). Consent-gated via the identity-tier 'coordinate' scope (a result is B answering A's own delegation — trust ≥ friend suffices; NO new scope). Records the result first-party on B's own mission. Returns { ok, envelope } or { ok:false, status: not_found|no_consent }.",
419
+ inputSchema: {
420
+ type: "object",
421
+ properties: {
422
+ missionId: { type: "string", description: "the local mission B is returning a result for (its local uuid id)" },
423
+ toAgentId: { type: "string", description: "the recipient agent's join-key agentId — A, the delegator" },
424
+ requestId: { type: "string", description: "the delegation correlation key (the task-spec's requestId)" },
425
+ result: { type: "object", description: "B's deliverable { summary, artifact?, outputs? }" },
426
+ proof: { type: "string", description: "optional opaque proof to stamp on the envelope (for a non-TOFU recipient verifier)" },
427
+ },
428
+ required: ["missionId", "toAgentId", "requestId", "result"],
429
+ },
430
+ },
431
+ {
432
+ name: "import_result",
433
+ description: "Consumer (gap-2, non-clobbering merge): A imports B's result-return. Resolves the mission by missionKey; lands B's deliverable QUARANTINED + attributed under importedResults WITHOUT touching first-party; source trust caps acceptance (checked before correlation); a result whose requestId matches no prior first-party delegation is REJECTED (no_delegation — A only accepts results for work it delegated); an unknown mission is no_mission (a result never seeds a mission); never recomputes status/participants. Returns { ok, status, record } or { ok:false, status: untrusted_source|no_mission|no_delegation|invalid }.",
434
+ inputSchema: {
435
+ type: "object",
436
+ properties: {
437
+ envelope: { type: "object", description: "the MissionResultEnvelope to import" },
438
+ fromAgentId: { type: "string", description: "the agent the envelope arrived from (join-key agentId) — B" },
439
+ trustOfSource: { type: "string", enum: ["family", "friend", "acquaintance", "stranger"], description: "this agent's resolved trust in the source agent — the acceptance cap" },
440
+ },
441
+ required: ["envelope", "fromAgentId", "trustOfSource"],
442
+ },
443
+ },
397
444
  ];
398
445
  }
@@ -1,6 +1,8 @@
1
1
  import type { FriendStore } from "../store";
2
2
  import type { GrantStore } from "../grant-store";
3
3
  import type { MissionStore } from "../mission-store";
4
+ import type { AuditSink } from "../audit";
5
+ import type { ControlPlaneContext } from "./dispatch";
4
6
  export interface FriendsMcpServerOptions {
5
7
  store: FriendStore;
6
8
  /** Optional consent-grant store. When omitted, the consent/share tools
@@ -11,6 +13,13 @@ export interface FriendsMcpServerOptions {
11
13
  * (record_mission / get_mission / list_missions / share_mission /
12
14
  * import_mission) report `unsupported`; everything else works without it. */
13
15
  missions?: MissionStore;
16
+ /** Optional control-plane audit sink (Bug B, finding 3). When wired, the LIVE
17
+ * trust mutations (`set_trust`, and an explicit-trust-seat `onboard_agent`) append
18
+ * an append-only audit record. When omitted, those mutations are unaudited. */
19
+ audit?: AuditSink;
20
+ /** Optional WHO/WHENCE context for audited mutations. Defaults to the stdio
21
+ * owner-only boundary (finding 3-A) when omitted. */
22
+ controlContext?: ControlPlaneContext;
14
23
  stdin: NodeJS.ReadableStream;
15
24
  stdout: NodeJS.WritableStream;
16
25
  }
@@ -13,7 +13,7 @@ const observability_1 = require("../observability");
13
13
  const schemas_1 = require("./schemas");
14
14
  const dispatch_1 = require("./dispatch");
15
15
  function createFriendsMcpServer(options) {
16
- const { store, grants, missions, stdin, stdout } = options;
16
+ const { store, grants, missions, audit, controlContext, stdin, stdout } = options;
17
17
  let buffer = "";
18
18
  let running = false;
19
19
  let useContentLengthFraming = true;
@@ -145,7 +145,7 @@ function createFriendsMcpServer(options) {
145
145
  const toolName = params.name ?? "";
146
146
  const toolArgs = params.arguments ?? {};
147
147
  try {
148
- const { result, isError } = await (0, dispatch_1.dispatchTool)(store, toolName, toolArgs, grants, missions);
148
+ const { result, isError } = await (0, dispatch_1.dispatchTool)(store, toolName, toolArgs, grants, missions, audit, controlContext);
149
149
  writeResponse({
150
150
  jsonrpc: "2.0",
151
151
  id: request.id,