@kybernesis/dispatch 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/governed-peers.js +28 -3
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -55,6 +55,23 @@ Your own project's deployments and `eve dev` keep working (current-project
|
|
|
55
55
|
OIDC bypass + `localDev()`); production browser traffic stays rejected unless
|
|
56
56
|
you pass your app's real auth via `extraAuth`.
|
|
57
57
|
|
|
58
|
+
## Asserted asker on governed calls
|
|
59
|
+
|
|
60
|
+
A dynamic `governedPeers()` call carries the authenticated identity of the
|
|
61
|
+
calling agent, plus that agent's optional claim about the human who prompted
|
|
62
|
+
it. The human's authenticated authority does not travel with this call. On the
|
|
63
|
+
receiver, `assertedAsker(session)` from `@kybernesis/enterprise` reads the
|
|
64
|
+
caller's asserted asker.
|
|
65
|
+
|
|
66
|
+
The value is asserted rather than authenticated: the control plane signs the
|
|
67
|
+
token containing the claim, but does not verify the named human. Use it only for
|
|
68
|
+
non-escalating purposes such as greeting or addressing someone, attribution or
|
|
69
|
+
logging, tailoring a non-sensitive response, or refusing work. Never use it to
|
|
70
|
+
authorize access, widen permissions, impersonate a user, select user-scoped
|
|
71
|
+
credentials, or permit access to personal data. A tool that reaches personal
|
|
72
|
+
data must use the authenticated session principal, which on this agent-to-agent
|
|
73
|
+
call is the calling agent.
|
|
74
|
+
|
|
58
75
|
## Bidirectional edges
|
|
59
76
|
|
|
60
77
|
Answering is built into one edge — the caller parks until the peer's terminal
|
package/dist/governed-peers.js
CHANGED
|
@@ -33,15 +33,40 @@ async function discover(options) {
|
|
|
33
33
|
return hit?.peers ?? [];
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Who is asking, from the verified session principal.
|
|
38
|
+
*
|
|
39
|
+
* The agent's own door authenticated this; the model did not choose it and
|
|
40
|
+
* cannot set it, which is the only reason it is worth sending at all. Undefined
|
|
41
|
+
* for an unattended turn — a schedule, a webhook — where there is no person.
|
|
42
|
+
*
|
|
43
|
+
* This travels to the peer as an ASSERTION, not as authentication. The peer
|
|
44
|
+
* learns who prompted the call and can address them by name or decline work
|
|
45
|
+
* that is not theirs to do, but the call still carries this agent's authority,
|
|
46
|
+
* not the person's. A peer must never widen what it will do because of this
|
|
47
|
+
* value: the caller could claim anything, and the control plane only records
|
|
48
|
+
* the claim. Reaching a peer AS the person is a different mechanism.
|
|
49
|
+
*/
|
|
50
|
+
function askedBy(ctx) {
|
|
51
|
+
const current = ctx?.session?.auth?.current;
|
|
52
|
+
const id = current?.principalId;
|
|
53
|
+
if (!id)
|
|
54
|
+
return undefined;
|
|
55
|
+
const attributes = current?.attributes ?? {};
|
|
56
|
+
const label = ["email", "user_name", "name"]
|
|
57
|
+
.map((key) => attributes[key])
|
|
58
|
+
.find((value) => typeof value === "string" && value.length > 0);
|
|
59
|
+
return label ? { id, label } : { id };
|
|
60
|
+
}
|
|
36
61
|
/** Mint a short-lived token for one callee, then hold the conversation. */
|
|
37
|
-
async function ask(options, peer, message) {
|
|
62
|
+
async function ask(options, peer, message, asker) {
|
|
38
63
|
const { issuer, credential } = config(options);
|
|
39
64
|
if (!credential)
|
|
40
65
|
throw new Error("This agent has no control-plane credential, so it cannot call another agent.");
|
|
41
66
|
const minted = await fetch(`${issuer}/api/agent/session`, {
|
|
42
67
|
method: "POST",
|
|
43
68
|
headers: { "content-type": "application/json", authorization: `Bearer ${credential}` },
|
|
44
|
-
body: JSON.stringify({ callee: peer.name }),
|
|
69
|
+
body: JSON.stringify({ callee: peer.name, ...(asker ? { onBehalfOf: asker } : {}) }),
|
|
45
70
|
signal: AbortSignal.timeout(20_000),
|
|
46
71
|
});
|
|
47
72
|
if (!minted.ok) {
|
|
@@ -176,7 +201,7 @@ export function governedPeers(options = {}) {
|
|
|
176
201
|
.string()
|
|
177
202
|
.describe("What to ask it. Write it as you would to a colleague — it has no view of this conversation."),
|
|
178
203
|
}),
|
|
179
|
-
execute: (input) => ask(options, peer, input.message),
|
|
204
|
+
execute: (input, ctx) => ask(options, peer, input.message, askedBy(ctx)),
|
|
180
205
|
});
|
|
181
206
|
}
|
|
182
207
|
return tools;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/dispatch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Agent-to-agent dispatch for eve agents: declared remote-peer edges with principal forwarding on by default, and a receiver channel that makes trusting the wrong caller hard to express.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -26,17 +26,18 @@
|
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsc -p tsconfig.build.json",
|
|
29
|
-
"
|
|
29
|
+
"prepack": "tsc -p tsconfig.build.json",
|
|
30
30
|
"typecheck": "tsc --noEmit",
|
|
31
31
|
"prepublishOnly": "node ../../scripts/prepublish.mjs"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
+
"@kybernesis/enterprise": "^0.8.0",
|
|
34
35
|
"@types/node": "24.x",
|
|
35
|
-
"eve": "0.
|
|
36
|
+
"eve": "0.49.0",
|
|
36
37
|
"typescript": "7.0.2"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
|
-
"eve": ">=0.
|
|
40
|
+
"eve": ">=0.49.0 <0.50.0",
|
|
40
41
|
"@kybernesis/enterprise": ">=0.2.0"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|