@ai-ecoverse/slicc-agent 6.16.0 → 6.17.1
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 +3 -3
- package/dist/guests/guard.js +60 -7
- package/dist/guests/guard.js.map +2 -2
- package/dist/guests/index.js +14 -6
- package/dist/guests/index.js.map +2 -2
- package/dist/guests/seats.js.map +2 -2
- package/dist/spectrum/approvals-port.js +26 -2
- package/dist/spectrum/approvals-port.js.map +2 -2
- package/dist/types/guests/guard.d.ts +4 -1
- package/dist/types/guests/seats.d.ts +3 -1
- package/dist/types/spectrum/approvals-port.d.ts +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -684,7 +684,7 @@ Every approval, of a guest's prompt, of a tool call in a run a guest started, or
|
|
|
684
684
|
- **Lifetime:** an item waits 5 minutes unless its requester sets `expiresAt`, then settles `unanswered`. Pending guest items survive a reload of the worker; pending `sudo` items are withdrawn (`restart`), because a reload ends the kernel's processes. The document keeps every pending item and the last 100 settled ones.
|
|
685
685
|
- **In the worker,** `AgentHost.approvals` has `request(draft, ctx)`, `decide(id, answer, reason, by, ctx)` (`by` is a `DecidedBy`, or `{ by, device }` to record the deciding device), `settled(id, ctx)` (resolves when the item leaves `pending`), `withdraw(id, reason, ctx)` and `withdrawSeat(seat, ctx)`, which withdraws a revoked seat's pending items.
|
|
686
686
|
- **Over the link,** `AgentTranscript.approvals` replicates the items, and `AgentControl.decide(id, decision, reason, ctx)` answers one, for every owner device: the owner's pages and followers with or without Control, all with the same powers. `decidedBy` comes from the link's seat (`owner` for the owner's pages, `follower` for the others), and is only a record of who answered. A guest never gets `decide`.
|
|
687
|
-
- **In spectrum** (1.84 on, which shows guest approvals in `<slicc-approvals>` with Always allow when `always` is true, as notices without `decide`, and an `unsure` verdict), `createAgentModel` returns `approvals`, an `ApprovalsAdapter` for `SliccModel.approvals`. `list()` has the pending `user` items, oldest first, as spectrum's `Approval`s, with the cone's name as `agentName` and the item's own `always`, so a guest approval offers Always allow only when the policy did when it was asked. `decide(id, decision, reason?)` rejects with the agent's message, and `ended` reports `answered`, `expired` or `withdrawn` for an item that left without this tab's decision. Every owner device's adapter has `decide`; a guest connection's doesn't. So a notice instead of a dialog shows only where nothing can answer.
|
|
687
|
+
- **In spectrum** (1.84 on, which shows guest approvals in `<slicc-approvals>` with Always allow when `always` is true, as notices without `decide`, and an `unsure` verdict), `createAgentModel` returns `approvals`, an `ApprovalsAdapter` for `SliccModel.approvals`. `list()` has the pending `user` items, oldest first, as spectrum's `Approval`s, with the cone's name as `agentName` and the item's own `always`, so a guest approval offers Always allow only when the policy did when it was asked. `decide(id, decision, reason?)` rejects with the agent's message, and `ended` reports `answered`, `expired` or `withdrawn` for an item that left without this tab's decision. Every owner device's adapter has `decide`; a guest connection's doesn't. So a notice instead of a dialog shows only where nothing can answer. A `guest-tool` approval also carries `args`, the call's arguments pretty-printed as JSON (two-space indent), so spectrum can show them uncut when `summary` is empty. The worker runs every string in the arguments through the secrets scrub before it files the item, so `summary`, `detail.arguments` and `args` show a secret as its placeholder; the seat's Always rules still match the call's own arguments. `args` is capped at 64 KiB (65,536 UTF-16 code units, `ARGS_LENGTH`, never splitting a surrogate pair): longer arguments are sent as that prefix with `argsTruncated: true`, and spectrum must then offer only Deny and Always deny, never Allow, because the end wasn't seen.
|
|
688
688
|
- **Kernel sudo:** `AgentOwner.approve(req, { signal, where? })` from `startAgent` takes slicc-kernel's sudo request as it comes to the kernel's `approve` hook and resolves `once`, `always` or `deny`. bios hands its hook to it while the agent runs, and to its own dialog otherwise. It rejects with `code: 'EAGAIN'` before anything is recorded when the agent can't take the request (released, restarting, or an older worker), so that's when to fall back; any other rejection is a deny. The request goes through `slicc.agent.sudo`, a service only the owner's pages get, and becomes a `sudo` item on the `user` tier with the kernel's `expiresAt`:
|
|
689
689
|
- **Requester** comes from the kernel's process tag: `{ kind: 'agent', agentId }` is the cone or scoop by its name, `{ kind: 'user', where }` is the kernel user with `where` (`terminal:…` reads "a terminal", `follower:<device>` the `where` the page passes, such as "on Pat's phone", or "a linked device", with the device id kept as `requester.device`), and anything else is the kernel user alone.
|
|
690
690
|
- **Detail:** the command as the kernel resolved it (`path` and the arguments, shell-quoted), `target`, `cwd`, and `alwaysScope: 'cwd'` when the request offers Always (`req.always === true`, which the page sets from its own checks), since the kernel's Always writes a rule for that directory.
|
|
@@ -745,7 +745,7 @@ All the limits on approvals and guests live in one owner-changeable policy, the
|
|
|
745
745
|
|
|
746
746
|
A guest seat (plan D13, PR 19) is a revocable seat on one cone: the guest sees that cone's live transcript and has a composer, and nothing else.
|
|
747
747
|
|
|
748
|
-
- **Minting.** The tray hub mints the seat and its one-time URL (v6's routes, D10); the page hands the agent adapter its routes: `createAgentModel(connection, { guests: { mint({ label, expiresAt, gates, agentId }) → { id, url }, revoke(id) } })`. Its `guests` port (spectrum's `GuestPort`) mints through the hub, then opens the seat in the worker with `AgentControl.guestOpen({ id, label, agentId, expiresAt, gates })`; if the worker refuses, the hub's seat is revoked again. `list()` is the transcript's `guests` state (`{ id, label, agentId, createdAt, expiresAt, gates, connected, pending, alwaysTools }`), and the URL is returned once and never kept. `unavailable()` says "Inviting guests needs the tray hub." without a hub, and "Only the owner's devices can invite guests." on a guest. Every owner device can invite, with or without Control. `gates()` (spectrum's `GuestPort.gates?()`) lists the gates the approval policy allows for messages and tools, in display order, for the invite pickers; `scoop:*`, for "any scoop", shows only when the policy offers the scoop tier.
|
|
748
|
+
- **Minting.** The tray hub mints the seat and its one-time URL (v6's routes, D10); the page hands the agent adapter its routes: `createAgentModel(connection, { guests: { mint({ label, expiresAt, gates, agentId }) → { id, url }, revoke(id) } })`. Its `guests` port (spectrum's `GuestPort`) mints through the hub, then opens the seat in the worker with `AgentControl.guestOpen({ id, label, agentId, expiresAt, gates })`; if the worker refuses, the hub's seat is revoked again. `list()` is the transcript's `guests` state (`{ id, label, agentId, createdAt, expiresAt, gates, connected, pending, alwaysTools }`), and the URL is returned once and never kept. Its `alwaysTools` rules carry no `argumentsHash`. `unavailable()` says "Inviting guests needs the tray hub." without a hub, and "Only the owner's devices can invite guests." on a guest. Every owner device can invite, with or without Control. `gates()` (spectrum's `GuestPort.gates?()`) lists the gates the approval policy allows for messages and tools, in display order, for the invite pickers; `scoop:*`, for "any scoop", shows only when the policy offers the scoop tier.
|
|
749
749
|
- **The record.** Seats live in the session document `slicc.guests`, so they survive reloads and restarts: id, label (control characters stripped, at most 64 characters), cone, expiry (at most the policy's `guests.maxSeatDays`, 30 by default), the two gates (`user`, `agent`, `cone`, `scoop:<name>` or `off`, fixed when the seat is opened and checked then against the policy's `allowedMessageGates` and `allowedToolGates`; by default the tools gate can't be `off` or `cone`, from any caller: "A guest's tool calls need a review: by a person, the judge or a scoop.") and an epoch. Revoking (`guestRevoke(id)`) keeps a tombstone, bumps the epoch, withdraws the guest's messages still queued in the cone, and ends every guest link of the seat (`invalidatePrincipal`). An expired seat ends the same way at its expiry. Tombstones go 365 days after they end, the longest a seat can be opened for.
|
|
750
750
|
- **Joining.** The hub resolves the guest's token to the seat's id and label, and the owner page opens `owner.port({ guest: { id, label } })` and bridges it like a follower's. Before Chord sees the link, the worker checks that the seat exists, isn't revoked or expired, and has that label; otherwise it closes the port. The link's principal is `{ seat: 'guest', link, guest: { id, name, epoch } }`, and anything else the port asks for (`readOnly: false`, `pages`) is ignored. A guest link has at most four calls in flight (Chord's `maxCalls`): further ones are answered at once with `unavailable` ("Too many calls in progress on this connection (4)"), and the link stays open. A revoked guest leaves nothing behind on the host: Chord 1.3 drops each disposed object, where 1.2 kept one per guest principal (seat and epoch), with its link, until the worker restarted.
|
|
751
751
|
- **What a guest gets.** Every contract served from the agent's host refuses a guest principal (`forbidden`) unless its factory is opened with `openToGuests`, so a contract added later is closed to guests until someone opens it. The only open one is `slicc.agent.guest`: its `guest` variant has the shared cone's transcript (its user, assistant, tool and compaction entries; `pi.live`; `pi.agent`'s model and thinking level; and only the guest's own queued messages) and the seat, plus `propose({ text, requestId })`. Everyone else gets its empty `none` variant.
|
|
@@ -755,7 +755,7 @@ A guest seat (plan D13, PR 19) is a revocable seat on one cone: the guest sees t
|
|
|
755
755
|
- **The tier decides.** `off` passes, if the policy's `allowedToolGates` still allows it (it doesn't by default). A tier the policy no longer allows is refused with the policy's sentence. A tier the agent doesn't know, a guest input with no recorded submission, and a seat that has ended all deny. So does `scoop:<name>`, at once, because no scoop can answer an approval yet. Otherwise the hook files a `guest-tool` approval on that tier (`user`, or `agent` for the judge), with `detail: { tool, summary, arguments, task }`, and waits for it on the approvals document, honouring the abort signal. If the seat ends while the call is filed or reviewed, the item is withdrawn and the call blocked, even after an approval. Anything else but approve blocks too, and the model sees `Tool call blocked: <reason>`, such as "The owner declined this tool call in a run a guest started." with the owner's reason.
|
|
756
756
|
- **Once per call.** The approval's id is a memo of the tool task (`slicc.guest-tool:<seat>:<tier>`), so after a crash or reload the hook reruns before the tool runs and rejoins the same review, without asking again.
|
|
757
757
|
- **Abort.** Once a waiting call's task ends without an answer, its approval is withdrawn (`aborted`), so aborting the conversation withdraws its pending guest tool calls. That also covers the calls of a script abandoned by a restart.
|
|
758
|
-
- **Always** is offered only while `guests.always.tools` is true. An Always answer adds a rule to the seat's `alwaysTools`: `{ approval, tool, arguments, by, at }
|
|
758
|
+
- **Always** is offered only while `guests.always.tools` is true. An Always answer adds a rule to the seat's `alwaysTools`: `{ approval, tool, argumentsHash, arguments, by, at }`. `argumentsHash` is the hex SHA-256 of the call's canonical JSON (keys sorted), and `arguments` is that JSON scrubbed of secrets, for display only, so no secret from the call is kept on the seat or published in the `guests` list. The hash stays in the seat document: the `guests` list leaves it out, because an unsalted hash of a short secret can be guessed offline. Rules from before the hash are hashed and scrubbed when the session opens. Later calls from that seat with the same tool and arguments of the same hash pass without asking, while the policy still offers Always. The transcript's `guests` list shows each seat's rules. `AgentControl.guestAlwaysForget(seat, approval)`, for owner devices, removes one, and revoking the seat drops them all.
|
|
759
759
|
- **Revocation and expiry clean up alike.** Revoking or expiring a seat withdraws its queued input and its pending approvals; a proposal still being placed is serialised with them per seat and rechecks the seat right before it's placed, so nothing lands after a revoke.
|
|
760
760
|
- **Every owner device manages guests.** The owner's pages and followers, with or without Control, open, list and revoke seats alike.
|
|
761
761
|
- **A guest managing guests** is refused by the approval policy (`guests.manageGuests`, `deny`). The guest contract has `guestOpen` and `guestRevoke`, and both are refused with `forbidden` ("The owner's guest policy doesn't let guests manage guests."). The approval policy itself is never a guest's to change: its write is on `AgentControl`, which a guest can't reach.
|
package/dist/guests/guard.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
ToolTask
|
|
7
7
|
} from "@earendil-works/pi-durable";
|
|
8
8
|
import { REASONS, scoopTier } from "../approvals/record.js";
|
|
9
|
+
import { scrubText, scrubTexts } from "../secrets/scrub.js";
|
|
9
10
|
import { gateKind, gateRefusal } from "./policy.js";
|
|
10
11
|
import { GuestsDoc, isGate, liveSeat } from "./seats.js";
|
|
11
12
|
const GUEST_GUARD = "slicc-guest-guard";
|
|
@@ -27,6 +28,48 @@ function canonical(value) {
|
|
|
27
28
|
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
|
|
28
29
|
return JSON.stringify(value ?? null);
|
|
29
30
|
}
|
|
31
|
+
function leaves(value, found) {
|
|
32
|
+
if (typeof value === "string") found.push(value);
|
|
33
|
+
else if (value && typeof value === "object")
|
|
34
|
+
for (const item of Object.values(value)) leaves(item, found);
|
|
35
|
+
}
|
|
36
|
+
function replaced(value, texts) {
|
|
37
|
+
if (typeof value === "string") return texts.shift();
|
|
38
|
+
if (Array.isArray(value)) return value.map((item) => replaced(item, texts));
|
|
39
|
+
if (value && typeof value === "object")
|
|
40
|
+
return Object.fromEntries(
|
|
41
|
+
Object.entries(value).map(([key, item]) => [key, replaced(item, texts)])
|
|
42
|
+
);
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
async function scrubbedArguments(args) {
|
|
46
|
+
const found = [];
|
|
47
|
+
leaves(args, found);
|
|
48
|
+
return replaced(args, await scrubTexts(found));
|
|
49
|
+
}
|
|
50
|
+
async function argumentsHash(args) {
|
|
51
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(args));
|
|
52
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
53
|
+
}
|
|
54
|
+
async function shownArguments(text) {
|
|
55
|
+
try {
|
|
56
|
+
return canonical(await scrubbedArguments(JSON.parse(text)));
|
|
57
|
+
} catch {
|
|
58
|
+
return scrubText(text);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function sealedRules(rules) {
|
|
62
|
+
if (rules.every((rule) => rule.argumentsHash)) return null;
|
|
63
|
+
return Promise.all(
|
|
64
|
+
rules.map(
|
|
65
|
+
async (rule) => rule.argumentsHash ? rule : {
|
|
66
|
+
...rule,
|
|
67
|
+
argumentsHash: await argumentsHash(rule.arguments),
|
|
68
|
+
arguments: await shownArguments(rule.arguments)
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
30
73
|
function summaryOf(args) {
|
|
31
74
|
const values = Object.values(args);
|
|
32
75
|
const text = values.length === 1 && typeof values[0] === "string" ? values[0] : canonical(args);
|
|
@@ -75,12 +118,12 @@ ${tier}`, { seat, tier: String(tier) });
|
|
|
75
118
|
}
|
|
76
119
|
return [...found.values()];
|
|
77
120
|
}
|
|
78
|
-
function settledEarly(seat, tier, policy, at, tool,
|
|
121
|
+
function settledEarly(seat, tier, policy, at, tool, hash) {
|
|
79
122
|
if (!policy.allowedToolGates.includes(gateKind(tier)))
|
|
80
123
|
return gateRefusal("a guest's tool calls", policy.allowedToolGates);
|
|
81
124
|
if (!liveSeat(seat, at)) return GATE_REASONS.ended;
|
|
82
125
|
if (tier === "off") return "";
|
|
83
|
-
const kept = seat.alwaysTools?.some((rule) => rule.tool === tool && rule.
|
|
126
|
+
const kept = seat.alwaysTools?.some((rule) => rule.tool === tool && rule.argumentsHash === hash);
|
|
84
127
|
return policy.always.tools && kept ? "" : null;
|
|
85
128
|
}
|
|
86
129
|
const ended = (status) => status === "terminal" || status === "completing";
|
|
@@ -103,7 +146,7 @@ function guestToolGate(harness) {
|
|
|
103
146
|
const record = (await tx.doc(GuestsDoc)).seats[seat];
|
|
104
147
|
if (!record || record.revoked !== null) return;
|
|
105
148
|
const kept = (record.alwaysTools ?? []).filter(
|
|
106
|
-
(entry) => entry.tool !== rule.tool || entry.
|
|
149
|
+
(entry) => entry.tool !== rule.tool || entry.argumentsHash !== rule.argumentsHash
|
|
107
150
|
);
|
|
108
151
|
record.alwaysTools = [...kept, rule];
|
|
109
152
|
}, BACKGROUND_CONTEXT);
|
|
@@ -113,9 +156,9 @@ function guestToolGate(harness) {
|
|
|
113
156
|
const host = await awaitWithContext(bound, context);
|
|
114
157
|
const policy = host.policy();
|
|
115
158
|
const now = host.now ?? Date.now;
|
|
116
|
-
const
|
|
159
|
+
const hash = await argumentsHash(canonical(call.arguments));
|
|
117
160
|
const always = policy.always.tools;
|
|
118
|
-
const early = settledEarly(seat, taint.tier, policy, now(), call.name,
|
|
161
|
+
const early = settledEarly(seat, taint.tier, policy, now(), call.name, hash);
|
|
119
162
|
if (early !== null) return early || void 0;
|
|
120
163
|
const id = await api.memo(
|
|
121
164
|
`${GUEST_TOOL_MEMO}:${seat.id}:${taint.tier}`,
|
|
@@ -123,6 +166,7 @@ function guestToolGate(harness) {
|
|
|
123
166
|
context
|
|
124
167
|
);
|
|
125
168
|
const task = Number(api.taskId);
|
|
169
|
+
const shown = await scrubbedArguments(call.arguments);
|
|
126
170
|
const asked = await host.approvals.request(
|
|
127
171
|
{
|
|
128
172
|
id,
|
|
@@ -131,7 +175,12 @@ function guestToolGate(harness) {
|
|
|
131
175
|
agentId: seat.agentId,
|
|
132
176
|
requester: { kind: "guest", name: seat.label, seat: seat.id },
|
|
133
177
|
tier: taint.tier,
|
|
134
|
-
detail: {
|
|
178
|
+
detail: {
|
|
179
|
+
tool: call.name,
|
|
180
|
+
summary: summaryOf(shown),
|
|
181
|
+
arguments: canonical(shown),
|
|
182
|
+
task
|
|
183
|
+
}
|
|
135
184
|
},
|
|
136
185
|
BACKGROUND_CONTEXT
|
|
137
186
|
);
|
|
@@ -148,7 +197,8 @@ function guestToolGate(harness) {
|
|
|
148
197
|
await remember(seat.id, {
|
|
149
198
|
approval: item.id,
|
|
150
199
|
tool: call.name,
|
|
151
|
-
|
|
200
|
+
argumentsHash: hash,
|
|
201
|
+
arguments: canonical(shown),
|
|
152
202
|
by: item.decidedBy,
|
|
153
203
|
at: item.decidedAt ?? now()
|
|
154
204
|
});
|
|
@@ -188,9 +238,12 @@ export {
|
|
|
188
238
|
GUEST_REQUEST,
|
|
189
239
|
GUEST_TOOL_MEMO,
|
|
190
240
|
SUMMARY_LENGTH,
|
|
241
|
+
argumentsHash,
|
|
191
242
|
canonical,
|
|
192
243
|
guestToolGate,
|
|
193
244
|
reasonOf,
|
|
245
|
+
scrubbedArguments,
|
|
246
|
+
sealedRules,
|
|
194
247
|
summaryOf,
|
|
195
248
|
taintsOf
|
|
196
249
|
};
|
package/dist/guests/guard.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/guests/guard.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { awaitWithContext, BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport {\n defineExtension,\n type Extension,\n type Harness,\n type HookApi,\n hook,\n LiveDoc,\n type SubmissionId,\n type TaskId,\n type ToolHookCall,\n ToolTask,\n} from '@earendil-works/pi-durable';\nimport type { Approval, Approvals } from '../approvals/index.ts';\nimport { REASONS, scoopTier } from '../approvals/record.ts';\nimport { type GuestPolicy, gateKind, gateRefusal } from './policy.ts';\nimport { type AlwaysTool, GuestsDoc, isGate, liveSeat, type SeatRecord } from './seats.ts';\n\nexport const GUEST_GUARD = 'slicc-guest-guard';\nexport const GUEST_REQUEST = 'guest:';\nexport const GUEST_TOOL_MEMO = 'slicc.guest-tool';\nexport const SUMMARY_LENGTH = 2000;\n\nexport const GATE_REASONS = {\n unknown:\n 'A guest started this run, and its seat names no review for tool calls that this agent knows.',\n ended: \"A guest started this run, and that guest's seat has ended.\",\n rejected: 'The owner declined this tool call in a run a guest started.',\n judged: 'The automatic check declined this tool call in a run a guest started',\n unanswered: 'Nobody approved this tool call in a run a guest started in time.',\n unroutable: \"The cone can't review a tool call it is itself waiting on.\",\n withdrawn: 'The review of this tool call was withdrawn.',\n} as const;\n\nexport interface GateHost {\n approvals: Pick<Approvals, 'state' | 'request' | 'settled' | 'withdraw'>;\n policy(): GuestPolicy;\n now?: () => number;\n}\n\nexport interface GuestToolGate {\n readonly extension: Extension;\n bind(host: GateHost): void;\n}\n\ntype Held = Pick<Harness, 'submission' | 'commit' | 'waitForTask'>;\n\ntype Taint = { seat: SeatRecord | null; tier: string };\n\nexport function canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;\n if (value && typeof value === 'object')\n return `{${Object.keys(value)\n .sort()\n .map((key) => `${JSON.stringify(key)}:${canonical((value as Record<string, unknown>)[key])}`)\n .join(',')}}`;\n return JSON.stringify(value ?? null);\n}\n\nexport function summaryOf(args: Record<string, unknown>): string {\n const values = Object.values(args);\n const text = values.length === 1 && typeof values[0] === 'string' ? values[0] : canonical(args);\n return text.length > SUMMARY_LENGTH ? `${text.slice(0, SUMMARY_LENGTH - 1)}\u2026` : text;\n}\n\nconst SCOOP_SAID: Record<string, (name: string) => string> = {\n [REASONS.noScoop]: (name) => `there is no scoop ${name} in this cone to review it.`,\n [REASONS.scoopStopped]: (name) => `the scoop ${name} is stopped, so it can't review it.`,\n [REASONS.scoopAsked]: (name) =>\n `the scoop ${name} can't review a call that asks something of it.`,\n [REASONS.scoopUndecided]: (name) => `the scoop ${name} ended its review without deciding.`,\n};\n\nfunction scoopReason(item: Approval): string | null {\n const name = item.tier.slice('scoop:'.length);\n if (item.decidedBy?.startsWith('scoop:'))\n return `The scoop ${name} declined this tool call in a run a guest started${item.reason ? `: ${item.reason}` : '.'}`;\n const said = SCOOP_SAID[item.reason ?? ''];\n return said ? `A guest started this run, and ${said(name)}` : null;\n}\n\nexport function reasonOf(item: Approval): string {\n const scooped = item.state === 'rejected' && scoopTier(item.tier) ? scoopReason(item) : null;\n if (scooped) return scooped;\n if (item.state === 'rejected' && item.decidedBy === 'judge')\n return item.verdict\n ? `${GATE_REASONS.judged}: ${item.verdict.reason}`\n : `${GATE_REASONS.judged}.`;\n if (item.state === 'rejected')\n return item.reason && item.reason !== REASONS.unknownAnswer\n ? `${GATE_REASONS.rejected} ${item.reason}`\n : GATE_REASONS.rejected;\n if (item.state === 'withdrawn') return GATE_REASONS.withdrawn;\n if (item.reason === REASONS.unknownTier) return GATE_REASONS.unknown;\n if (item.reason === REASONS.unroutable) return GATE_REASONS.unroutable;\n return GATE_REASONS.unanswered;\n}\n\nexport async function taintsOf(\n inputs: readonly SubmissionId[],\n seats: Readonly<Record<string, SeatRecord>>,\n harness: Pick<Harness, 'submission'>,\n context: Context\n): Promise<Taint[]> {\n const found = new Map<string, Taint>();\n for (const id of inputs) {\n const seat = Object.values(seats).find((record) =>\n record.submissions?.some((entry) => entry.id === String(id))\n );\n const tier = seat?.submissions?.find((entry) => entry.id === String(id))?.tools;\n if (seat) {\n found.set(`${seat.id}\\n${tier}`, { seat, tier: String(tier) });\n continue;\n }\n const submission = await harness.submission(id, context);\n const record = submission ? await submission.status(context) : undefined;\n if (record?.requestId?.startsWith(GUEST_REQUEST)) return [{ seat: null, tier: '' }];\n }\n return [...found.values()];\n}\n\nfunction settledEarly(\n seat: SeatRecord,\n tier: string,\n policy: GuestPolicy,\n at: number,\n tool: string,\n args: string\n): string | null {\n if (!policy.allowedToolGates.includes(gateKind(tier)))\n return gateRefusal(\"a guest's tool calls\", policy.allowedToolGates);\n if (!liveSeat(seat, at)) return GATE_REASONS.ended;\n if (tier === 'off') return '';\n const kept = seat.alwaysTools?.some((rule) => rule.tool === tool && rule.arguments === args);\n return policy.always.tools && kept ? '' : null;\n}\n\nconst ended = (status: string) => status === 'terminal' || status === 'completing';\n\nexport function guestToolGate(harness: () => Held | undefined): GuestToolGate {\n let resolveHost: (host: GateHost) => void = () => {};\n const bound = new Promise<GateHost>((resolve) => {\n resolveHost = resolve;\n });\n const watched = new Set<string>();\n const watch = (host: GateHost, id: string, task: number) => {\n const held = harness();\n if (!held || watched.has(id)) return;\n watched.add(id);\n void held\n .waitForTask(task as unknown as TaskId, BACKGROUND_CONTEXT)\n .then((record) =>\n ended(record.state.status)\n ? host.approvals.withdraw(id, REASONS.aborted, BACKGROUND_CONTEXT)\n : undefined\n )\n .catch(() => undefined)\n .finally(() => watched.delete(id));\n };\n const remember = (seat: string, rule: AlwaysTool) =>\n (harness() as Held).commit(async (tx) => {\n const record = (await tx.doc(GuestsDoc)).seats[seat];\n if (!record || record.revoked !== null) return;\n const kept = (record.alwaysTools ?? []).filter(\n (entry) => entry.tool !== rule.tool || entry.arguments !== rule.arguments\n );\n record.alwaysTools = [...kept, rule];\n }, BACKGROUND_CONTEXT);\n const review = async (\n taint: Taint,\n call: ToolHookCall,\n api: HookApi,\n context: Context\n ): Promise<string | undefined> => {\n const { seat } = taint;\n if (seat === null || !isGate(taint.tier)) return GATE_REASONS.unknown;\n const host = await awaitWithContext(bound, context);\n const policy = host.policy();\n const now = host.now ?? Date.now;\n const args = canonical(call.arguments);\n const always = policy.always.tools;\n const early = settledEarly(seat, taint.tier, policy, now(), call.name, args);\n if (early !== null) return early || undefined;\n const id = await api.memo<string>(\n `${GUEST_TOOL_MEMO}:${seat.id}:${taint.tier}`,\n `apr-${crypto.randomUUID()}`,\n context\n );\n const task = Number(api.taskId);\n const asked = await host.approvals.request(\n {\n id,\n kind: 'guest-tool',\n seat: seat.id,\n agentId: seat.agentId,\n requester: { kind: 'guest', name: seat.label, seat: seat.id },\n tier: taint.tier,\n detail: { tool: call.name, summary: summaryOf(call.arguments), arguments: args, task },\n },\n BACKGROUND_CONTEXT\n );\n watch(host, id, task);\n const live = async () =>\n liveSeat((await api.snapshot(GuestsDoc, context))?.seats[seat.id], now());\n if (!(await live())) {\n await host.approvals.withdraw(id, REASONS.revoked, BACKGROUND_CONTEXT);\n return GATE_REASONS.ended;\n }\n const item = asked.state === 'pending' ? await host.approvals.settled(id, context) : asked;\n if (item.state !== 'approved') return reasonOf(item);\n if (!(await live())) return GATE_REASONS.ended;\n if (item.answer === 'always' && always)\n await remember(seat.id, {\n approval: item.id,\n tool: call.name,\n arguments: args,\n by: item.decidedBy,\n at: item.decidedAt ?? now(),\n });\n return undefined;\n };\n return {\n extension: defineExtension({\n name: GUEST_GUARD,\n hooks: [\n hook(ToolTask, {\n async beforeTool(call, api, context) {\n const live = await api.snapshot(LiveDoc, api.conversationId, context);\n const inputs = live?.run?.inputs ?? [];\n if (!inputs.length) return undefined;\n const seats = (await api.snapshot(GuestsDoc, context))?.seats ?? {};\n const held = harness() as Held;\n for (const taint of await taintsOf(inputs, seats, held, context)) {\n const block = await review(taint, call, api, context);\n if (block) return { block };\n }\n return undefined;\n },\n }),\n ],\n }),\n bind(host) {\n resolveHost(host);\n for (const item of host.approvals.state.value ?? [])\n if (item.kind === 'guest-tool' && item.state === 'pending' && item.detail.task)\n watch(host, item.id, item.detail.task);\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { awaitWithContext, BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport {\n defineExtension,\n type Extension,\n type Harness,\n type HookApi,\n hook,\n LiveDoc,\n type SubmissionId,\n type TaskId,\n type ToolHookCall,\n ToolTask,\n} from '@earendil-works/pi-durable';\nimport type { Approval, Approvals } from '../approvals/index.ts';\nimport { REASONS, scoopTier } from '../approvals/record.ts';\nimport { scrubText, scrubTexts } from '../secrets/scrub.ts';\nimport { type GuestPolicy, gateKind, gateRefusal } from './policy.ts';\nimport { type AlwaysTool, GuestsDoc, isGate, liveSeat, type SeatRecord } from './seats.ts';\n\nexport const GUEST_GUARD = 'slicc-guest-guard';\nexport const GUEST_REQUEST = 'guest:';\nexport const GUEST_TOOL_MEMO = 'slicc.guest-tool';\nexport const SUMMARY_LENGTH = 2000;\n\nexport const GATE_REASONS = {\n unknown:\n 'A guest started this run, and its seat names no review for tool calls that this agent knows.',\n ended: \"A guest started this run, and that guest's seat has ended.\",\n rejected: 'The owner declined this tool call in a run a guest started.',\n judged: 'The automatic check declined this tool call in a run a guest started',\n unanswered: 'Nobody approved this tool call in a run a guest started in time.',\n unroutable: \"The cone can't review a tool call it is itself waiting on.\",\n withdrawn: 'The review of this tool call was withdrawn.',\n} as const;\n\nexport interface GateHost {\n approvals: Pick<Approvals, 'state' | 'request' | 'settled' | 'withdraw'>;\n policy(): GuestPolicy;\n now?: () => number;\n}\n\nexport interface GuestToolGate {\n readonly extension: Extension;\n bind(host: GateHost): void;\n}\n\ntype Held = Pick<Harness, 'submission' | 'commit' | 'waitForTask'>;\n\ntype Taint = { seat: SeatRecord | null; tier: string };\n\nexport function canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;\n if (value && typeof value === 'object')\n return `{${Object.keys(value)\n .sort()\n .map((key) => `${JSON.stringify(key)}:${canonical((value as Record<string, unknown>)[key])}`)\n .join(',')}}`;\n return JSON.stringify(value ?? null);\n}\n\nfunction leaves(value: unknown, found: string[]): void {\n if (typeof value === 'string') found.push(value);\n else if (value && typeof value === 'object')\n for (const item of Object.values(value)) leaves(item, found);\n}\n\nfunction replaced(value: unknown, texts: string[]): unknown {\n if (typeof value === 'string') return texts.shift();\n if (Array.isArray(value)) return value.map((item) => replaced(item, texts));\n if (value && typeof value === 'object')\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => [key, replaced(item, texts)])\n );\n return value;\n}\n\nexport async function scrubbedArguments(\n args: Record<string, unknown>\n): Promise<Record<string, unknown>> {\n const found: string[] = [];\n leaves(args, found);\n return replaced(args, await scrubTexts(found)) as Record<string, unknown>;\n}\n\nexport async function argumentsHash(args: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(args));\n return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nasync function shownArguments(text: string): Promise<string> {\n try {\n return canonical(await scrubbedArguments(JSON.parse(text)));\n } catch {\n return scrubText(text);\n }\n}\n\nexport async function sealedRules(rules: readonly AlwaysTool[]): Promise<AlwaysTool[] | null> {\n if (rules.every((rule) => rule.argumentsHash)) return null;\n return Promise.all(\n rules.map(async (rule) =>\n rule.argumentsHash\n ? rule\n : {\n ...rule,\n argumentsHash: await argumentsHash(rule.arguments),\n arguments: await shownArguments(rule.arguments),\n }\n )\n );\n}\n\nexport function summaryOf(args: Record<string, unknown>): string {\n const values = Object.values(args);\n const text = values.length === 1 && typeof values[0] === 'string' ? values[0] : canonical(args);\n return text.length > SUMMARY_LENGTH ? `${text.slice(0, SUMMARY_LENGTH - 1)}\u2026` : text;\n}\n\nconst SCOOP_SAID: Record<string, (name: string) => string> = {\n [REASONS.noScoop]: (name) => `there is no scoop ${name} in this cone to review it.`,\n [REASONS.scoopStopped]: (name) => `the scoop ${name} is stopped, so it can't review it.`,\n [REASONS.scoopAsked]: (name) =>\n `the scoop ${name} can't review a call that asks something of it.`,\n [REASONS.scoopUndecided]: (name) => `the scoop ${name} ended its review without deciding.`,\n};\n\nfunction scoopReason(item: Approval): string | null {\n const name = item.tier.slice('scoop:'.length);\n if (item.decidedBy?.startsWith('scoop:'))\n return `The scoop ${name} declined this tool call in a run a guest started${item.reason ? `: ${item.reason}` : '.'}`;\n const said = SCOOP_SAID[item.reason ?? ''];\n return said ? `A guest started this run, and ${said(name)}` : null;\n}\n\nexport function reasonOf(item: Approval): string {\n const scooped = item.state === 'rejected' && scoopTier(item.tier) ? scoopReason(item) : null;\n if (scooped) return scooped;\n if (item.state === 'rejected' && item.decidedBy === 'judge')\n return item.verdict\n ? `${GATE_REASONS.judged}: ${item.verdict.reason}`\n : `${GATE_REASONS.judged}.`;\n if (item.state === 'rejected')\n return item.reason && item.reason !== REASONS.unknownAnswer\n ? `${GATE_REASONS.rejected} ${item.reason}`\n : GATE_REASONS.rejected;\n if (item.state === 'withdrawn') return GATE_REASONS.withdrawn;\n if (item.reason === REASONS.unknownTier) return GATE_REASONS.unknown;\n if (item.reason === REASONS.unroutable) return GATE_REASONS.unroutable;\n return GATE_REASONS.unanswered;\n}\n\nexport async function taintsOf(\n inputs: readonly SubmissionId[],\n seats: Readonly<Record<string, SeatRecord>>,\n harness: Pick<Harness, 'submission'>,\n context: Context\n): Promise<Taint[]> {\n const found = new Map<string, Taint>();\n for (const id of inputs) {\n const seat = Object.values(seats).find((record) =>\n record.submissions?.some((entry) => entry.id === String(id))\n );\n const tier = seat?.submissions?.find((entry) => entry.id === String(id))?.tools;\n if (seat) {\n found.set(`${seat.id}\\n${tier}`, { seat, tier: String(tier) });\n continue;\n }\n const submission = await harness.submission(id, context);\n const record = submission ? await submission.status(context) : undefined;\n if (record?.requestId?.startsWith(GUEST_REQUEST)) return [{ seat: null, tier: '' }];\n }\n return [...found.values()];\n}\n\nfunction settledEarly(\n seat: SeatRecord,\n tier: string,\n policy: GuestPolicy,\n at: number,\n tool: string,\n hash: string\n): string | null {\n if (!policy.allowedToolGates.includes(gateKind(tier)))\n return gateRefusal(\"a guest's tool calls\", policy.allowedToolGates);\n if (!liveSeat(seat, at)) return GATE_REASONS.ended;\n if (tier === 'off') return '';\n const kept = seat.alwaysTools?.some((rule) => rule.tool === tool && rule.argumentsHash === hash);\n return policy.always.tools && kept ? '' : null;\n}\n\nconst ended = (status: string) => status === 'terminal' || status === 'completing';\n\nexport function guestToolGate(harness: () => Held | undefined): GuestToolGate {\n let resolveHost: (host: GateHost) => void = () => {};\n const bound = new Promise<GateHost>((resolve) => {\n resolveHost = resolve;\n });\n const watched = new Set<string>();\n const watch = (host: GateHost, id: string, task: number) => {\n const held = harness();\n if (!held || watched.has(id)) return;\n watched.add(id);\n void held\n .waitForTask(task as unknown as TaskId, BACKGROUND_CONTEXT)\n .then((record) =>\n ended(record.state.status)\n ? host.approvals.withdraw(id, REASONS.aborted, BACKGROUND_CONTEXT)\n : undefined\n )\n .catch(() => undefined)\n .finally(() => watched.delete(id));\n };\n const remember = (seat: string, rule: AlwaysTool) =>\n (harness() as Held).commit(async (tx) => {\n const record = (await tx.doc(GuestsDoc)).seats[seat];\n if (!record || record.revoked !== null) return;\n const kept = (record.alwaysTools ?? []).filter(\n (entry) => entry.tool !== rule.tool || entry.argumentsHash !== rule.argumentsHash\n );\n record.alwaysTools = [...kept, rule];\n }, BACKGROUND_CONTEXT);\n const review = async (\n taint: Taint,\n call: ToolHookCall,\n api: HookApi,\n context: Context\n ): Promise<string | undefined> => {\n const { seat } = taint;\n if (seat === null || !isGate(taint.tier)) return GATE_REASONS.unknown;\n const host = await awaitWithContext(bound, context);\n const policy = host.policy();\n const now = host.now ?? Date.now;\n const hash = await argumentsHash(canonical(call.arguments));\n const always = policy.always.tools;\n const early = settledEarly(seat, taint.tier, policy, now(), call.name, hash);\n if (early !== null) return early || undefined;\n const id = await api.memo<string>(\n `${GUEST_TOOL_MEMO}:${seat.id}:${taint.tier}`,\n `apr-${crypto.randomUUID()}`,\n context\n );\n const task = Number(api.taskId);\n const shown = await scrubbedArguments(call.arguments);\n const asked = await host.approvals.request(\n {\n id,\n kind: 'guest-tool',\n seat: seat.id,\n agentId: seat.agentId,\n requester: { kind: 'guest', name: seat.label, seat: seat.id },\n tier: taint.tier,\n detail: {\n tool: call.name,\n summary: summaryOf(shown),\n arguments: canonical(shown),\n task,\n },\n },\n BACKGROUND_CONTEXT\n );\n watch(host, id, task);\n const live = async () =>\n liveSeat((await api.snapshot(GuestsDoc, context))?.seats[seat.id], now());\n if (!(await live())) {\n await host.approvals.withdraw(id, REASONS.revoked, BACKGROUND_CONTEXT);\n return GATE_REASONS.ended;\n }\n const item = asked.state === 'pending' ? await host.approvals.settled(id, context) : asked;\n if (item.state !== 'approved') return reasonOf(item);\n if (!(await live())) return GATE_REASONS.ended;\n if (item.answer === 'always' && always)\n await remember(seat.id, {\n approval: item.id,\n tool: call.name,\n argumentsHash: hash,\n arguments: canonical(shown),\n by: item.decidedBy,\n at: item.decidedAt ?? now(),\n });\n return undefined;\n };\n return {\n extension: defineExtension({\n name: GUEST_GUARD,\n hooks: [\n hook(ToolTask, {\n async beforeTool(call, api, context) {\n const live = await api.snapshot(LiveDoc, api.conversationId, context);\n const inputs = live?.run?.inputs ?? [];\n if (!inputs.length) return undefined;\n const seats = (await api.snapshot(GuestsDoc, context))?.seats ?? {};\n const held = harness() as Held;\n for (const taint of await taintsOf(inputs, seats, held, context)) {\n const block = await review(taint, call, api, context);\n if (block) return { block };\n }\n return undefined;\n },\n }),\n ],\n }),\n bind(host) {\n resolveHost(host);\n for (const item of host.approvals.state.value ?? [])\n if (item.kind === 'guest-tool' && item.state === 'pending' && item.detail.task)\n watch(host, item.id, item.detail.task);\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["record"]
|
|
7
7
|
}
|
package/dist/guests/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
GUEST_INPUT_KIND,
|
|
8
8
|
guestContent
|
|
9
9
|
} from "./attribution.js";
|
|
10
|
-
import { GUEST_REQUEST } from "./guard.js";
|
|
10
|
+
import { GUEST_REQUEST, sealedRules } from "./guard.js";
|
|
11
11
|
import { DEFAULT_GUEST_POLICY } from "./policy.js";
|
|
12
12
|
import {
|
|
13
13
|
endReview,
|
|
@@ -168,18 +168,26 @@ function shownSeats(records, links, at, pendingOf) {
|
|
|
168
168
|
gates: { ...record.gates },
|
|
169
169
|
connected: (links.get(record.id) ?? 0) > 0,
|
|
170
170
|
pending: pendingOf(record.id),
|
|
171
|
-
alwaysTools:
|
|
171
|
+
alwaysTools: (record.alwaysTools ?? []).map(({ argumentsHash: _hash, ...rule }) => rule)
|
|
172
172
|
}));
|
|
173
173
|
}
|
|
174
|
+
function openedSeats(harness, using) {
|
|
175
|
+
return harness.commit(async (tx) => {
|
|
176
|
+
await tx.doc(GuestReviewsDoc);
|
|
177
|
+
const seats = (await tx.doc(GuestsDoc)).seats;
|
|
178
|
+
for (const record of Object.values(seats)) {
|
|
179
|
+
const rules = await sealedRules(record.alwaysTools ?? []);
|
|
180
|
+
if (rules) record.alwaysTools = rules;
|
|
181
|
+
}
|
|
182
|
+
return copy(seats);
|
|
183
|
+
}, using);
|
|
184
|
+
}
|
|
174
185
|
async function createGuests(options) {
|
|
175
186
|
const { harness, agents } = options;
|
|
176
187
|
const now = options.now ?? Date.now;
|
|
177
188
|
const context = options.context ?? BACKGROUND_CONTEXT;
|
|
178
189
|
const guestPolicy = () => options.policy?.current().guests ?? DEFAULT_GUEST_POLICY;
|
|
179
|
-
const records = await harness
|
|
180
|
-
await tx.doc(GuestReviewsDoc);
|
|
181
|
-
return copy((await tx.doc(GuestsDoc)).seats);
|
|
182
|
-
}, context);
|
|
190
|
+
const records = await openedSeats(harness, context);
|
|
183
191
|
const seatsDoc = await harness.documentState(GuestsDoc, context);
|
|
184
192
|
const reviews = await harness.documentState(
|
|
185
193
|
GuestReviewsDoc,
|
package/dist/guests/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/guests/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { type Context, type ReplicatedState, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { type DocumentState, type Harness, InboxDoc } from '@earendil-works/pi-durable';\nimport type { Agents } from '../agents.ts';\nimport type { Approvals } from '../approvals/index.ts';\nimport type { ApprovalPolicies } from '../approvals/policy.ts';\nimport { scrubText } from '../secrets/scrub.ts';\nimport type { GuestPrincipal } from '../serving.ts';\nimport {\n ASKED_KIND,\n GUEST_INPUT_KIND,\n type GuestAsked,\n type GuestInput,\n guestContent,\n} from './attribution.ts';\nimport { GUEST_REQUEST } from './guard.ts';\nimport { DEFAULT_GUEST_POLICY, type GuestPolicy } from './policy.ts';\nimport {\n endReview,\n type GuestReviews,\n GuestReviewsDoc,\n KEPT_SUBMISSIONS,\n liveReviews,\n MAX_QUEUED,\n pruneReviews,\n type ReviewsState,\n told,\n} from './review.ts';\nimport {\n checkDraft,\n cleanLabel,\n type GuestDraft,\n type GuestSeat,\n GuestsDoc,\n type GuestsState,\n liveSeat,\n MAX_SEAT_MS,\n type SeatRecord,\n} from './seats.ts';\n\nexport type GuestOpened = { seat: GuestSeat; error: null } | { seat: null; error: string };\n\nexport type Proposed =\n | { state: 'submitted'; submissionId: string; reason: null }\n | { state: 'queued'; submissionId: null; review: string; reason: null }\n | { state: 'unanswered'; submissionId: null; reason: string };\n\nexport interface ProposeRequest {\n text: string;\n requestId: string | null;\n}\n\nexport interface Guests {\n readonly seats: ReplicatedState<GuestSeat[]>;\n readonly reviews: ReplicatedState<ReviewsState | null>;\n open(draft: GuestDraft, context: Context): Promise<GuestOpened>;\n revoke(id: string, context: Context): Promise<{ revoked: boolean }>;\n forgetAlways(id: string, approval: string, context: Context): Promise<{ removed: boolean }>;\n admit(id: string, label: string): GuestPrincipal | null;\n record(id: string): SeatRecord | undefined;\n current(guest: GuestPrincipal): SeatRecord | undefined;\n joined(id: string): () => void;\n onEnd(listener: (id: string) => void): () => void;\n propose(guest: GuestPrincipal, request: ProposeRequest, context: Context): Promise<Proposed>;\n policy(): GuestPolicy;\n dispose(): void;\n}\n\nexport interface GuestsOptions {\n harness: Pick<Harness, 'commit' | 'abortSubmission' | 'documentState'>;\n agents: Pick<Agents, 'state' | 'conversation'>;\n approvals?: Pick<Approvals, 'state' | 'request' | 'withdrawSeat' | 'expire' | 'withdraw'>;\n policy?: Pick<ApprovalPolicies, 'current'>;\n reviews?: Pick<GuestReviews, 'task' | 'bind'>;\n now?: () => number;\n context?: Context;\n}\n\nexport const MAX_PROPOSAL = 32_000;\nconst MAX_REQUEST = 128;\nconst LONGEST_TIMER = 2 ** 31 - 1;\n\nconst copy = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;\n\nconst unanswered = (reason: string): Proposed => ({\n state: 'unanswered',\n submissionId: null,\n reason,\n});\n\nfunction prune(all: Record<string, SeatRecord>, at: number) {\n for (const [id, record] of Object.entries(all)) {\n const over = record.revoked ?? record.expiresAt;\n if (over !== null && at - over > MAX_SEAT_MS) delete all[id];\n }\n}\n\nasync function withdrawQueued(\n harness: GuestsOptions['harness'],\n agents: GuestsOptions['agents'],\n record: SeatRecord,\n using: Context\n) {\n const conversation = await agents.conversation(record.agentId, using);\n if (!conversation) return;\n const queued = await harness.commit(\n async (tx) =>\n (await tx.doc(InboxDoc, conversation.id)).items\n .filter((item) => {\n const entry = item.entry as { kind?: string; data?: { seat?: unknown } } | undefined;\n return entry?.kind === GUEST_INPUT_KIND && entry.data?.seat === record.id;\n })\n .map((item) => item.id),\n using\n );\n for (const id of queued) await harness.abortSubmission(id, using, conversation.id);\n}\n\nasync function deliver(\n record: SeatRecord,\n text: string,\n requestId: string,\n options: GuestsOptions,\n using: Context\n): Promise<Proposed> {\n const conversation = await options.agents.conversation(record.agentId, using);\n if (!conversation) return unanswered('gone');\n const data: GuestInput = { seat: record.id, name: record.label, approval: null };\n const submitted = await conversation.submit(\n {\n type: 'input',\n content: guestContent(record.label, text),\n whenBusy: 'followUp',\n entry: { kind: GUEST_INPUT_KIND, data: { ...data } },\n requestId,\n },\n using\n );\n const id = String(submitted.id);\n await options.harness.commit(async (tx) => {\n const kept = (await tx.doc(GuestsDoc)).seats[record.id];\n if (!kept || kept.submissions?.some((entry) => entry.id === id)) return;\n kept.submissions = [\n ...(kept.submissions ?? []),\n { id, approval: null, tools: record.gates.tools },\n ].slice(-KEPT_SUBMISSIONS);\n }, using);\n return { state: 'submitted', submissionId: id, reason: null };\n}\n\ntype Prepared = { text: string; requestId: string };\n\nasync function prepare(record: SeatRecord, request: ProposeRequest): Promise<Prepared | Proposed> {\n const text = typeof request?.text === 'string' ? request.text.trim() : '';\n if (!text) return unanswered('empty');\n if (text.length > MAX_PROPOSAL) return unanswered('too-long');\n const asked =\n typeof request.requestId === 'string' &&\n request.requestId &&\n request.requestId.length <= MAX_REQUEST\n ? request.requestId\n : crypto.randomUUID();\n return { text: await scrubText(text), requestId: `${GUEST_REQUEST}${record.id}:${asked}` };\n}\n\nasync function place(\n record: SeatRecord,\n prepared: Prepared,\n options: GuestsOptions,\n using: Context,\n at: number\n): Promise<Proposed> {\n const gate = record.gates.messages;\n if (gate === 'off') return deliver(record, prepared.text, prepared.requestId, options, using);\n const { reviews } = options;\n if (!reviews || !options.approvals) return unanswered('unavailable');\n const conversation = await options.agents.conversation(record.agentId, using);\n if (!conversation) return unanswered('gone');\n return options.harness.commit(async (tx): Promise<Proposed> => {\n const doc = await tx.doc(GuestReviewsDoc);\n const again = Object.values(doc.reviews).find(\n (review) => review.seat === record.id && review.request === prepared.requestId\n );\n if (again) return { state: 'queued', submissionId: null, review: again.id, reason: null };\n if (liveReviews(doc, record.id).length > MAX_QUEUED) return unanswered('overflow');\n const id = `apr-${crypto.randomUUID()}`;\n const task = await tx.createTask(\n reviews.task,\n { review: id },\n { ownership: { kind: 'conversation' }, conversationId: conversation.id, background: true }\n );\n doc.reviews[id] = {\n id,\n seat: record.id,\n agentId: record.agentId,\n label: record.label,\n text: prepared.text,\n request: prepared.requestId,\n tier: gate,\n tools: record.gates.tools,\n n: doc.next,\n task: Number(task),\n state: 'queued',\n reason: null,\n createdAt: at,\n decidedAt: null,\n submissionId: null,\n ended: false,\n conversation: Number(conversation.id),\n };\n doc.next += 1;\n pruneReviews(doc);\n const asked: GuestAsked = {\n seat: record.id,\n name: record.label,\n review: id,\n text: prepared.text,\n at,\n };\n await tx.appendEntry(conversation.id, { kind: ASKED_KIND, data: { ...asked } });\n return { state: 'queued', submissionId: null, review: id, reason: null };\n }, using);\n}\n\nfunction forgotten(\n harness: GuestsOptions['harness'],\n id: string,\n approval: string,\n using: Context\n): Promise<{ removed: boolean }> {\n return harness.commit(async (tx) => {\n const record = (await tx.doc(GuestsDoc)).seats[id];\n const kept = record?.alwaysTools?.filter((rule) => rule.approval !== approval);\n if (!record || !kept || kept.length === record.alwaysTools?.length) return { removed: false };\n record.alwaysTools = kept;\n return { removed: true };\n }, using);\n}\n\nfunction keyedLine() {\n const locks = new Map<string, Promise<unknown>>();\n return <T>(id: string, run: () => Promise<T>): Promise<T> => {\n const next = (locks.get(id) ?? Promise.resolve()).then(run, run);\n const settled = next.catch(() => undefined);\n locks.set(id, settled);\n void settled.then(() => {\n if (locks.get(id) === settled) locks.delete(id);\n });\n return next;\n };\n}\n\nconst pendingIn =\n (approvals: GuestsOptions['approvals'], reviews: DocumentState<ReviewsState>) =>\n (id: string): number =>\n (approvals?.state.value ?? []).filter((item) => item.seat === id && item.state === 'pending')\n .length + liveReviews(reviews.value, id).filter((review) => review.state === 'queued').length;\n\nfunction shownSeats(\n records: Record<string, SeatRecord>,\n links: ReadonlyMap<string, number>,\n at: number,\n pendingOf: (id: string) => number\n): GuestSeat[] {\n return Object.values(records)\n .filter((record) => liveSeat(record, at))\n .sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id))\n .map((record) => ({\n id: record.id,\n label: record.label,\n agentId: record.agentId,\n createdAt: record.createdAt,\n expiresAt: record.expiresAt,\n gates: { ...record.gates },\n connected: (links.get(record.id) ?? 0) > 0,\n pending: pendingOf(record.id),\n alwaysTools: copy(record.alwaysTools ?? []),\n }));\n}\n\nexport async function createGuests(options: GuestsOptions): Promise<Guests> {\n const { harness, agents } = options;\n const now = options.now ?? Date.now;\n const context = options.context ?? BACKGROUND_CONTEXT;\n const guestPolicy = () => options.policy?.current().guests ?? DEFAULT_GUEST_POLICY;\n const records: Record<string, SeatRecord> = await harness.commit(async (tx) => {\n await tx.doc(GuestReviewsDoc);\n return copy((await tx.doc(GuestsDoc)).seats);\n }, context);\n const seatsDoc = (await harness.documentState(GuestsDoc, context)) as DocumentState<GuestsState>;\n const reviews = (await harness.documentState(\n GuestReviewsDoc,\n context\n )) as DocumentState<ReviewsState>;\n const links = new Map<string, number>();\n const ended = new Set<(id: string) => void>();\n const gone = new Map<string, Promise<void>>();\n const shown = () => shownSeats(records, links, now(), pendingIn(options.approvals, reviews));\n const seats = replicatedState<GuestSeat[]>(shown());\n let timer: ReturnType<typeof setTimeout> | undefined;\n const locked = keyedLine();\n const cleanup = (record: SeatRecord, using: Context) =>\n locked(record.id, async () => {\n await withdrawQueued(harness, agents, record, using);\n await harness.commit(async (tx) => {\n const doc = await tx.doc(GuestReviewsDoc);\n for (const review of liveReviews(doc, record.id))\n if (review.state === 'queued') {\n endReview(review, 'withdrawn', 'revoked', now());\n await told(tx, review);\n }\n }, using);\n await options.approvals?.withdrawSeat(record.id, using);\n });\n const end = (id: string, using: Context = context): Promise<void> => {\n const going = gone.get(id);\n if (going !== undefined) return going;\n const cleaning = cleanup(records[id] as SeatRecord, using);\n gone.set(id, cleaning);\n for (const listener of [...ended]) listener(id);\n return cleaning;\n };\n const publish = () => {\n const at = now();\n for (const record of Object.values(records))\n if (!liveSeat(record, at)) void end(record.id).catch(() => undefined);\n seats.replace(context, shown());\n if (timer) clearTimeout(timer);\n timer = undefined;\n const next = Math.min(\n ...Object.values(records)\n .filter((record) => liveSeat(record, at) && record.expiresAt !== null)\n .map((record) => record.expiresAt as number)\n );\n if (Number.isFinite(next))\n timer = setTimeout(publish, Math.min(Math.max(0, next - at), LONGEST_TIMER));\n };\n publish();\n const offSeats = seatsDoc.subscribe((value) => {\n for (const id of Object.keys(records)) delete records[id];\n Object.assign(records, copy((value as GuestsState).seats));\n publish();\n });\n const write = (change: (seats: Record<string, SeatRecord>) => SeatRecord | undefined) =>\n harness.commit(async (tx) => {\n const doc = await tx.doc(GuestsDoc);\n const changed = change(doc.seats);\n return changed ? copy(changed) : undefined;\n }, context);\n const recount = () => seats.replace(context, shown());\n const offPending = options.approvals?.state.subscribe(recount);\n const offReviews = reviews.subscribe(recount);\n options.reviews?.bind({\n approvals: options.approvals as NonNullable<GuestsOptions['approvals']>,\n agents,\n line: locked,\n alwaysAllowed: () => guestPolicy().always.messages,\n });\n const current = (guest: GuestPrincipal) => {\n const record = records[guest.id];\n return record && liveSeat(record, now()) && record.epoch === guest.epoch ? record : undefined;\n };\n return {\n seats,\n reviews,\n async open(draft, using) {\n const at = now();\n const problem = checkDraft(draft, at, guestPolicy());\n if (problem) return { seat: null, error: problem };\n if (!agents.state().cones[draft.agentId])\n return { seat: null, error: `There is no cone ${draft.agentId}.` };\n const record: SeatRecord = {\n id: draft.id,\n label: cleanLabel(draft.label),\n agentId: draft.agentId,\n expiresAt: draft.expiresAt,\n gates: { messages: draft.gates.messages, tools: draft.gates.tools },\n createdAt: at,\n epoch: 1,\n revoked: null,\n };\n const kept = await harness.commit(async (tx) => {\n const doc = await tx.doc(GuestsDoc);\n if (doc.seats[record.id]) return null;\n prune(doc.seats, at);\n doc.seats[record.id] = copy(record);\n return copy(doc.seats);\n }, using);\n if (!kept) return { seat: null, error: 'That guest seat already exists.' };\n for (const id of Object.keys(records)) delete records[id];\n Object.assign(records, kept);\n publish();\n return { seat: shown().find((seat) => seat.id === record.id) as GuestSeat, error: null };\n },\n async revoke(id, using) {\n const at = now();\n const changed = await write((all) => {\n const record = all[id];\n if (!record || record.revoked !== null) return undefined;\n return Object.assign(record, { revoked: at, epoch: record.epoch + 1, alwaysTools: [] });\n });\n if (!changed) return { revoked: false };\n records[id] = changed;\n const ending = end(id, using);\n publish();\n await ending;\n return { revoked: true };\n },\n forgetAlways: (id, approval, using) => forgotten(harness, id, approval, using),\n admit(id, label) {\n const record = records[id];\n if (!record || !liveSeat(record, now()) || record.label !== cleanLabel(label)) return null;\n return { id: record.id, name: record.label, epoch: record.epoch };\n },\n record: (id) => records[id],\n current,\n joined(id) {\n links.set(id, (links.get(id) ?? 0) + 1);\n seats.replace(context, shown());\n let left = false;\n return () => {\n if (left) return;\n left = true;\n links.set(id, (links.get(id) ?? 1) - 1);\n seats.replace(context, shown());\n };\n },\n onEnd(listener) {\n ended.add(listener);\n return () => ended.delete(listener);\n },\n async propose(guest, request, using) {\n const record = current(guest);\n if (!record) return unanswered('revoked');\n const prepared = await prepare(record, request);\n if ('state' in prepared) return prepared;\n return locked(record.id, async () => {\n const still = current(guest);\n return still ? place(still, prepared, options, using, now()) : unanswered('revoked');\n });\n },\n policy: guestPolicy,\n dispose() {\n if (timer) clearTimeout(timer);\n offPending?.();\n offReviews();\n offSeats();\n seatsDoc.dispose();\n reviews.dispose();\n ended.clear();\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { type Context, type ReplicatedState, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { type DocumentState, type Harness, InboxDoc } from '@earendil-works/pi-durable';\nimport type { Agents } from '../agents.ts';\nimport type { Approvals } from '../approvals/index.ts';\nimport type { ApprovalPolicies } from '../approvals/policy.ts';\nimport { scrubText } from '../secrets/scrub.ts';\nimport type { GuestPrincipal } from '../serving.ts';\nimport {\n ASKED_KIND,\n GUEST_INPUT_KIND,\n type GuestAsked,\n type GuestInput,\n guestContent,\n} from './attribution.ts';\nimport { GUEST_REQUEST, sealedRules } from './guard.ts';\nimport { DEFAULT_GUEST_POLICY, type GuestPolicy } from './policy.ts';\nimport {\n endReview,\n type GuestReviews,\n GuestReviewsDoc,\n KEPT_SUBMISSIONS,\n liveReviews,\n MAX_QUEUED,\n pruneReviews,\n type ReviewsState,\n told,\n} from './review.ts';\nimport {\n checkDraft,\n cleanLabel,\n type GuestDraft,\n type GuestSeat,\n GuestsDoc,\n type GuestsState,\n liveSeat,\n MAX_SEAT_MS,\n type SeatRecord,\n} from './seats.ts';\n\nexport type GuestOpened = { seat: GuestSeat; error: null } | { seat: null; error: string };\n\nexport type Proposed =\n | { state: 'submitted'; submissionId: string; reason: null }\n | { state: 'queued'; submissionId: null; review: string; reason: null }\n | { state: 'unanswered'; submissionId: null; reason: string };\n\nexport interface ProposeRequest {\n text: string;\n requestId: string | null;\n}\n\nexport interface Guests {\n readonly seats: ReplicatedState<GuestSeat[]>;\n readonly reviews: ReplicatedState<ReviewsState | null>;\n open(draft: GuestDraft, context: Context): Promise<GuestOpened>;\n revoke(id: string, context: Context): Promise<{ revoked: boolean }>;\n forgetAlways(id: string, approval: string, context: Context): Promise<{ removed: boolean }>;\n admit(id: string, label: string): GuestPrincipal | null;\n record(id: string): SeatRecord | undefined;\n current(guest: GuestPrincipal): SeatRecord | undefined;\n joined(id: string): () => void;\n onEnd(listener: (id: string) => void): () => void;\n propose(guest: GuestPrincipal, request: ProposeRequest, context: Context): Promise<Proposed>;\n policy(): GuestPolicy;\n dispose(): void;\n}\n\nexport interface GuestsOptions {\n harness: Pick<Harness, 'commit' | 'abortSubmission' | 'documentState'>;\n agents: Pick<Agents, 'state' | 'conversation'>;\n approvals?: Pick<Approvals, 'state' | 'request' | 'withdrawSeat' | 'expire' | 'withdraw'>;\n policy?: Pick<ApprovalPolicies, 'current'>;\n reviews?: Pick<GuestReviews, 'task' | 'bind'>;\n now?: () => number;\n context?: Context;\n}\n\nexport const MAX_PROPOSAL = 32_000;\nconst MAX_REQUEST = 128;\nconst LONGEST_TIMER = 2 ** 31 - 1;\n\nconst copy = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;\n\nconst unanswered = (reason: string): Proposed => ({\n state: 'unanswered',\n submissionId: null,\n reason,\n});\n\nfunction prune(all: Record<string, SeatRecord>, at: number) {\n for (const [id, record] of Object.entries(all)) {\n const over = record.revoked ?? record.expiresAt;\n if (over !== null && at - over > MAX_SEAT_MS) delete all[id];\n }\n}\n\nasync function withdrawQueued(\n harness: GuestsOptions['harness'],\n agents: GuestsOptions['agents'],\n record: SeatRecord,\n using: Context\n) {\n const conversation = await agents.conversation(record.agentId, using);\n if (!conversation) return;\n const queued = await harness.commit(\n async (tx) =>\n (await tx.doc(InboxDoc, conversation.id)).items\n .filter((item) => {\n const entry = item.entry as { kind?: string; data?: { seat?: unknown } } | undefined;\n return entry?.kind === GUEST_INPUT_KIND && entry.data?.seat === record.id;\n })\n .map((item) => item.id),\n using\n );\n for (const id of queued) await harness.abortSubmission(id, using, conversation.id);\n}\n\nasync function deliver(\n record: SeatRecord,\n text: string,\n requestId: string,\n options: GuestsOptions,\n using: Context\n): Promise<Proposed> {\n const conversation = await options.agents.conversation(record.agentId, using);\n if (!conversation) return unanswered('gone');\n const data: GuestInput = { seat: record.id, name: record.label, approval: null };\n const submitted = await conversation.submit(\n {\n type: 'input',\n content: guestContent(record.label, text),\n whenBusy: 'followUp',\n entry: { kind: GUEST_INPUT_KIND, data: { ...data } },\n requestId,\n },\n using\n );\n const id = String(submitted.id);\n await options.harness.commit(async (tx) => {\n const kept = (await tx.doc(GuestsDoc)).seats[record.id];\n if (!kept || kept.submissions?.some((entry) => entry.id === id)) return;\n kept.submissions = [\n ...(kept.submissions ?? []),\n { id, approval: null, tools: record.gates.tools },\n ].slice(-KEPT_SUBMISSIONS);\n }, using);\n return { state: 'submitted', submissionId: id, reason: null };\n}\n\ntype Prepared = { text: string; requestId: string };\n\nasync function prepare(record: SeatRecord, request: ProposeRequest): Promise<Prepared | Proposed> {\n const text = typeof request?.text === 'string' ? request.text.trim() : '';\n if (!text) return unanswered('empty');\n if (text.length > MAX_PROPOSAL) return unanswered('too-long');\n const asked =\n typeof request.requestId === 'string' &&\n request.requestId &&\n request.requestId.length <= MAX_REQUEST\n ? request.requestId\n : crypto.randomUUID();\n return { text: await scrubText(text), requestId: `${GUEST_REQUEST}${record.id}:${asked}` };\n}\n\nasync function place(\n record: SeatRecord,\n prepared: Prepared,\n options: GuestsOptions,\n using: Context,\n at: number\n): Promise<Proposed> {\n const gate = record.gates.messages;\n if (gate === 'off') return deliver(record, prepared.text, prepared.requestId, options, using);\n const { reviews } = options;\n if (!reviews || !options.approvals) return unanswered('unavailable');\n const conversation = await options.agents.conversation(record.agentId, using);\n if (!conversation) return unanswered('gone');\n return options.harness.commit(async (tx): Promise<Proposed> => {\n const doc = await tx.doc(GuestReviewsDoc);\n const again = Object.values(doc.reviews).find(\n (review) => review.seat === record.id && review.request === prepared.requestId\n );\n if (again) return { state: 'queued', submissionId: null, review: again.id, reason: null };\n if (liveReviews(doc, record.id).length > MAX_QUEUED) return unanswered('overflow');\n const id = `apr-${crypto.randomUUID()}`;\n const task = await tx.createTask(\n reviews.task,\n { review: id },\n { ownership: { kind: 'conversation' }, conversationId: conversation.id, background: true }\n );\n doc.reviews[id] = {\n id,\n seat: record.id,\n agentId: record.agentId,\n label: record.label,\n text: prepared.text,\n request: prepared.requestId,\n tier: gate,\n tools: record.gates.tools,\n n: doc.next,\n task: Number(task),\n state: 'queued',\n reason: null,\n createdAt: at,\n decidedAt: null,\n submissionId: null,\n ended: false,\n conversation: Number(conversation.id),\n };\n doc.next += 1;\n pruneReviews(doc);\n const asked: GuestAsked = {\n seat: record.id,\n name: record.label,\n review: id,\n text: prepared.text,\n at,\n };\n await tx.appendEntry(conversation.id, { kind: ASKED_KIND, data: { ...asked } });\n return { state: 'queued', submissionId: null, review: id, reason: null };\n }, using);\n}\n\nfunction forgotten(\n harness: GuestsOptions['harness'],\n id: string,\n approval: string,\n using: Context\n): Promise<{ removed: boolean }> {\n return harness.commit(async (tx) => {\n const record = (await tx.doc(GuestsDoc)).seats[id];\n const kept = record?.alwaysTools?.filter((rule) => rule.approval !== approval);\n if (!record || !kept || kept.length === record.alwaysTools?.length) return { removed: false };\n record.alwaysTools = kept;\n return { removed: true };\n }, using);\n}\n\nfunction keyedLine() {\n const locks = new Map<string, Promise<unknown>>();\n return <T>(id: string, run: () => Promise<T>): Promise<T> => {\n const next = (locks.get(id) ?? Promise.resolve()).then(run, run);\n const settled = next.catch(() => undefined);\n locks.set(id, settled);\n void settled.then(() => {\n if (locks.get(id) === settled) locks.delete(id);\n });\n return next;\n };\n}\n\nconst pendingIn =\n (approvals: GuestsOptions['approvals'], reviews: DocumentState<ReviewsState>) =>\n (id: string): number =>\n (approvals?.state.value ?? []).filter((item) => item.seat === id && item.state === 'pending')\n .length + liveReviews(reviews.value, id).filter((review) => review.state === 'queued').length;\n\nfunction shownSeats(\n records: Record<string, SeatRecord>,\n links: ReadonlyMap<string, number>,\n at: number,\n pendingOf: (id: string) => number\n): GuestSeat[] {\n return Object.values(records)\n .filter((record) => liveSeat(record, at))\n .sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id))\n .map((record) => ({\n id: record.id,\n label: record.label,\n agentId: record.agentId,\n createdAt: record.createdAt,\n expiresAt: record.expiresAt,\n gates: { ...record.gates },\n connected: (links.get(record.id) ?? 0) > 0,\n pending: pendingOf(record.id),\n alwaysTools: (record.alwaysTools ?? []).map(({ argumentsHash: _hash, ...rule }) => rule),\n }));\n}\n\nfunction openedSeats(\n harness: GuestsOptions['harness'],\n using: Context\n): Promise<Record<string, SeatRecord>> {\n return harness.commit(async (tx) => {\n await tx.doc(GuestReviewsDoc);\n const seats = (await tx.doc(GuestsDoc)).seats;\n for (const record of Object.values(seats)) {\n const rules = await sealedRules(record.alwaysTools ?? []);\n if (rules) record.alwaysTools = rules;\n }\n return copy(seats);\n }, using);\n}\n\nexport async function createGuests(options: GuestsOptions): Promise<Guests> {\n const { harness, agents } = options;\n const now = options.now ?? Date.now;\n const context = options.context ?? BACKGROUND_CONTEXT;\n const guestPolicy = () => options.policy?.current().guests ?? DEFAULT_GUEST_POLICY;\n const records = await openedSeats(harness, context);\n const seatsDoc = (await harness.documentState(GuestsDoc, context)) as DocumentState<GuestsState>;\n const reviews = (await harness.documentState(\n GuestReviewsDoc,\n context\n )) as DocumentState<ReviewsState>;\n const links = new Map<string, number>();\n const ended = new Set<(id: string) => void>();\n const gone = new Map<string, Promise<void>>();\n const shown = () => shownSeats(records, links, now(), pendingIn(options.approvals, reviews));\n const seats = replicatedState<GuestSeat[]>(shown());\n let timer: ReturnType<typeof setTimeout> | undefined;\n const locked = keyedLine();\n const cleanup = (record: SeatRecord, using: Context) =>\n locked(record.id, async () => {\n await withdrawQueued(harness, agents, record, using);\n await harness.commit(async (tx) => {\n const doc = await tx.doc(GuestReviewsDoc);\n for (const review of liveReviews(doc, record.id))\n if (review.state === 'queued') {\n endReview(review, 'withdrawn', 'revoked', now());\n await told(tx, review);\n }\n }, using);\n await options.approvals?.withdrawSeat(record.id, using);\n });\n const end = (id: string, using: Context = context): Promise<void> => {\n const going = gone.get(id);\n if (going !== undefined) return going;\n const cleaning = cleanup(records[id] as SeatRecord, using);\n gone.set(id, cleaning);\n for (const listener of [...ended]) listener(id);\n return cleaning;\n };\n const publish = () => {\n const at = now();\n for (const record of Object.values(records))\n if (!liveSeat(record, at)) void end(record.id).catch(() => undefined);\n seats.replace(context, shown());\n if (timer) clearTimeout(timer);\n timer = undefined;\n const next = Math.min(\n ...Object.values(records)\n .filter((record) => liveSeat(record, at) && record.expiresAt !== null)\n .map((record) => record.expiresAt as number)\n );\n if (Number.isFinite(next))\n timer = setTimeout(publish, Math.min(Math.max(0, next - at), LONGEST_TIMER));\n };\n publish();\n const offSeats = seatsDoc.subscribe((value) => {\n for (const id of Object.keys(records)) delete records[id];\n Object.assign(records, copy((value as GuestsState).seats));\n publish();\n });\n const write = (change: (seats: Record<string, SeatRecord>) => SeatRecord | undefined) =>\n harness.commit(async (tx) => {\n const doc = await tx.doc(GuestsDoc);\n const changed = change(doc.seats);\n return changed ? copy(changed) : undefined;\n }, context);\n const recount = () => seats.replace(context, shown());\n const offPending = options.approvals?.state.subscribe(recount);\n const offReviews = reviews.subscribe(recount);\n options.reviews?.bind({\n approvals: options.approvals as NonNullable<GuestsOptions['approvals']>,\n agents,\n line: locked,\n alwaysAllowed: () => guestPolicy().always.messages,\n });\n const current = (guest: GuestPrincipal) => {\n const record = records[guest.id];\n return record && liveSeat(record, now()) && record.epoch === guest.epoch ? record : undefined;\n };\n return {\n seats,\n reviews,\n async open(draft, using) {\n const at = now();\n const problem = checkDraft(draft, at, guestPolicy());\n if (problem) return { seat: null, error: problem };\n if (!agents.state().cones[draft.agentId])\n return { seat: null, error: `There is no cone ${draft.agentId}.` };\n const record: SeatRecord = {\n id: draft.id,\n label: cleanLabel(draft.label),\n agentId: draft.agentId,\n expiresAt: draft.expiresAt,\n gates: { messages: draft.gates.messages, tools: draft.gates.tools },\n createdAt: at,\n epoch: 1,\n revoked: null,\n };\n const kept = await harness.commit(async (tx) => {\n const doc = await tx.doc(GuestsDoc);\n if (doc.seats[record.id]) return null;\n prune(doc.seats, at);\n doc.seats[record.id] = copy(record);\n return copy(doc.seats);\n }, using);\n if (!kept) return { seat: null, error: 'That guest seat already exists.' };\n for (const id of Object.keys(records)) delete records[id];\n Object.assign(records, kept);\n publish();\n return { seat: shown().find((seat) => seat.id === record.id) as GuestSeat, error: null };\n },\n async revoke(id, using) {\n const at = now();\n const changed = await write((all) => {\n const record = all[id];\n if (!record || record.revoked !== null) return undefined;\n return Object.assign(record, { revoked: at, epoch: record.epoch + 1, alwaysTools: [] });\n });\n if (!changed) return { revoked: false };\n records[id] = changed;\n const ending = end(id, using);\n publish();\n await ending;\n return { revoked: true };\n },\n forgetAlways: (id, approval, using) => forgotten(harness, id, approval, using),\n admit(id, label) {\n const record = records[id];\n if (!record || !liveSeat(record, now()) || record.label !== cleanLabel(label)) return null;\n return { id: record.id, name: record.label, epoch: record.epoch };\n },\n record: (id) => records[id],\n current,\n joined(id) {\n links.set(id, (links.get(id) ?? 0) + 1);\n seats.replace(context, shown());\n let left = false;\n return () => {\n if (left) return;\n left = true;\n links.set(id, (links.get(id) ?? 1) - 1);\n seats.replace(context, shown());\n };\n },\n onEnd(listener) {\n ended.add(listener);\n return () => ended.delete(listener);\n },\n async propose(guest, request, using) {\n const record = current(guest);\n if (!record) return unanswered('revoked');\n const prepared = await prepare(record, request);\n if ('state' in prepared) return prepared;\n return locked(record.id, async () => {\n const still = current(guest);\n return still ? place(still, prepared, options, using, now()) : unanswered('revoked');\n });\n },\n policy: guestPolicy,\n dispose() {\n if (timer) clearTimeout(timer);\n offPending?.();\n offReviews();\n offSeats();\n seatsDoc.dispose();\n reviews.dispose();\n ended.clear();\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/guests/seats.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/guests/seats.ts"],
|
|
4
|
-
"sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\nimport type { ApprovalTier, DecidedBy } from '../approvals/record.ts';\nimport {\n DAY_MS,\n DEFAULT_GUEST_POLICY,\n type GuestPolicy,\n gateKind,\n gateRefusal,\n MAX_SEAT_DAYS,\n} from './policy.ts';\n\nexport type GuestGate = ApprovalTier | 'off';\n\nexport type GuestGates = {\n messages: GuestGate;\n tools: GuestGate;\n};\n\nexport type GuestDraft = {\n id: string;\n label: string;\n agentId: string;\n expiresAt: number | null;\n gates: GuestGates;\n};\n\nexport type SeatSubmission = { id: string; approval: string | null; tools: GuestGate };\n\nexport type AlwaysTool = {\n approval: string;\n tool: string;\n arguments: string;\n by: DecidedBy | null;\n at: number;\n};\n\nexport type SeatRecord = GuestDraft & {\n createdAt: number;\n epoch: number;\n revoked: number | null;\n delivered?: string[];\n submissions?: SeatSubmission[];\n always?: { by: DecidedBy | null; approval: string; at: number };\n alwaysTools?: AlwaysTool[];\n};\n\nexport interface GuestSeat {\n id: string;\n label: string;\n agentId: string;\n createdAt: number;\n expiresAt: number | null;\n gates: GuestGates;\n connected: boolean;\n pending: number;\n alwaysTools:
|
|
5
|
-
"mappings": "AAAA,SAAS,iBAAiB;AAE1B;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\nimport type { ApprovalTier, DecidedBy } from '../approvals/record.ts';\nimport {\n DAY_MS,\n DEFAULT_GUEST_POLICY,\n type GuestPolicy,\n gateKind,\n gateRefusal,\n MAX_SEAT_DAYS,\n} from './policy.ts';\n\nexport type GuestGate = ApprovalTier | 'off';\n\nexport type GuestGates = {\n messages: GuestGate;\n tools: GuestGate;\n};\n\nexport type GuestDraft = {\n id: string;\n label: string;\n agentId: string;\n expiresAt: number | null;\n gates: GuestGates;\n};\n\nexport type SeatSubmission = { id: string; approval: string | null; tools: GuestGate };\n\nexport type AlwaysTool = {\n approval: string;\n tool: string;\n argumentsHash: string;\n arguments: string;\n by: DecidedBy | null;\n at: number;\n};\n\nexport type ShownAlwaysTool = Omit<AlwaysTool, 'argumentsHash'>;\n\nexport type SeatRecord = GuestDraft & {\n createdAt: number;\n epoch: number;\n revoked: number | null;\n delivered?: string[];\n submissions?: SeatSubmission[];\n always?: { by: DecidedBy | null; approval: string; at: number };\n alwaysTools?: AlwaysTool[];\n};\n\nexport interface GuestSeat {\n id: string;\n label: string;\n agentId: string;\n createdAt: number;\n expiresAt: number | null;\n gates: GuestGates;\n connected: boolean;\n pending: number;\n alwaysTools: ShownAlwaysTool[];\n}\n\nexport type GuestsState = { seats: Record<string, SeatRecord> };\n\nexport const GuestsDoc = defineDoc<GuestsState>({\n kind: 'slicc.guests',\n version: 1,\n scope: 'session',\n initial: () => ({ seats: {} }),\n});\n\nexport const MAX_SEAT_MS = MAX_SEAT_DAYS * DAY_MS;\nexport const MAX_LABEL = 64;\nconst MAX_ID = 256;\n\nexport function cleanLabel(raw: unknown): string {\n return String(raw ?? '')\n .replace(/[\\p{Cc}\\p{Cf}]/gu, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, MAX_LABEL);\n}\n\nexport function isGate(value: unknown): value is GuestGate {\n if (typeof value !== 'string') return false;\n if (value === 'user' || value === 'agent' || value === 'cone' || value === 'off') return true;\n return /^scoop:\\S+$/.test(value);\n}\n\nexport function checkDraft(\n draft: GuestDraft,\n now: number,\n policy: GuestPolicy = DEFAULT_GUEST_POLICY\n): string | null {\n if (typeof draft?.id !== 'string' || !draft.id || draft.id.length > MAX_ID)\n return 'A guest seat needs an id from the tray hub.';\n if (!cleanLabel(draft.label)) return 'A guest seat needs a label.';\n if (!isGate(draft.gates?.messages) || !isGate(draft.gates?.tools))\n return 'A guest seat needs a known gate for messages and for tools.';\n if (!policy.allowedMessageGates.includes(gateKind(draft.gates.messages)))\n return gateRefusal(\"a guest's messages\", policy.allowedMessageGates);\n if (!policy.allowedToolGates.includes(gateKind(draft.gates.tools)))\n return gateRefusal(\"a guest's tool calls\", policy.allowedToolGates);\n const { expiresAt } = draft;\n if (expiresAt !== null && (typeof expiresAt !== 'number' || !Number.isFinite(expiresAt)))\n return 'A guest seat expires at a time, or never.';\n if (expiresAt !== null && expiresAt <= now) return 'That expiry has already passed.';\n const days = policy.maxSeatDays;\n if (expiresAt !== null && expiresAt - now > days * DAY_MS)\n return `A guest seat lasts at most ${days} ${days === 1 ? 'day' : 'days'}.`;\n return null;\n}\n\nexport function liveSeat(seat: SeatRecord | undefined, now: number): boolean {\n return Boolean(\n seat && seat.revoked === null && (seat.expiresAt === null || seat.expiresAt > now)\n );\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAE1B;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAsDA,MAAM,YAAY,UAAuB;AAAA,EAC9C,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,OAAO,CAAC,EAAE;AAC9B,CAAC;AAEM,MAAM,cAAc,gBAAgB;AACpC,MAAM,YAAY;AACzB,MAAM,SAAS;AAER,SAAS,WAAW,KAAsB;AAC/C,SAAO,OAAO,OAAO,EAAE,EACpB,QAAQ,oBAAoB,GAAG,EAC/B,QAAQ,QAAQ,GAAG,EACnB,KAAK,EACL,MAAM,GAAG,SAAS;AACvB;AAEO,SAAS,OAAO,OAAoC;AACzD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,UAAU,UAAU,UAAU,WAAW,UAAU,UAAU,UAAU,MAAO,QAAO;AACzF,SAAO,cAAc,KAAK,KAAK;AACjC;AAEO,SAAS,WACd,OACA,KACA,SAAsB,sBACP;AACf,MAAI,OAAO,OAAO,OAAO,YAAY,CAAC,MAAM,MAAM,MAAM,GAAG,SAAS;AAClE,WAAO;AACT,MAAI,CAAC,WAAW,MAAM,KAAK,EAAG,QAAO;AACrC,MAAI,CAAC,OAAO,MAAM,OAAO,QAAQ,KAAK,CAAC,OAAO,MAAM,OAAO,KAAK;AAC9D,WAAO;AACT,MAAI,CAAC,OAAO,oBAAoB,SAAS,SAAS,MAAM,MAAM,QAAQ,CAAC;AACrE,WAAO,YAAY,sBAAsB,OAAO,mBAAmB;AACrE,MAAI,CAAC,OAAO,iBAAiB,SAAS,SAAS,MAAM,MAAM,KAAK,CAAC;AAC/D,WAAO,YAAY,wBAAwB,OAAO,gBAAgB;AACpE,QAAM,EAAE,UAAU,IAAI;AACtB,MAAI,cAAc,SAAS,OAAO,cAAc,YAAY,CAAC,OAAO,SAAS,SAAS;AACpF,WAAO;AACT,MAAI,cAAc,QAAQ,aAAa,IAAK,QAAO;AACnD,QAAM,OAAO,OAAO;AACpB,MAAI,cAAc,QAAQ,YAAY,MAAM,OAAO;AACjD,WAAO,8BAA8B,IAAI,IAAI,SAAS,IAAI,QAAQ,MAAM;AAC1E,SAAO;AACT;AAEO,SAAS,SAAS,MAA8B,KAAsB;AAC3E,SAAO;AAAA,IACL,QAAQ,KAAK,YAAY,SAAS,KAAK,cAAc,QAAQ,KAAK,YAAY;AAAA,EAChF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,6 +6,21 @@ const ENDS = {
|
|
|
6
6
|
unanswered: "expired",
|
|
7
7
|
withdrawn: "withdrawn"
|
|
8
8
|
};
|
|
9
|
+
const ARGS_LENGTH = 65536;
|
|
10
|
+
function pretty(text) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.stringify(JSON.parse(text), null, 2);
|
|
13
|
+
} catch {
|
|
14
|
+
return text;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function argsOf(text) {
|
|
18
|
+
if (text === void 0) return {};
|
|
19
|
+
const args = pretty(text);
|
|
20
|
+
if (args.length <= ARGS_LENGTH) return { args };
|
|
21
|
+
const end = /[\uD800-\uDBFF]/.test(args[ARGS_LENGTH - 1]) ? ARGS_LENGTH - 1 : ARGS_LENGTH;
|
|
22
|
+
return { args: args.slice(0, end), argsTruncated: true };
|
|
23
|
+
}
|
|
9
24
|
function requesterOf(item) {
|
|
10
25
|
const { kind, name, agentId, where } = item.requester;
|
|
11
26
|
return {
|
|
@@ -41,8 +56,16 @@ function shown(item, agentName) {
|
|
|
41
56
|
always: item.always,
|
|
42
57
|
...item.verdict ? { verdict: { decision: item.verdict.decision, reason: item.verdict.reason } } : {}
|
|
43
58
|
};
|
|
44
|
-
if (item.kind === "guest-tool")
|
|
45
|
-
|
|
59
|
+
if (item.kind === "guest-tool") {
|
|
60
|
+
const tool = {
|
|
61
|
+
...guest,
|
|
62
|
+
kind: "guest-tool",
|
|
63
|
+
tool: item.detail.tool,
|
|
64
|
+
summary: item.detail.summary,
|
|
65
|
+
...argsOf(item.detail.arguments)
|
|
66
|
+
};
|
|
67
|
+
return tool;
|
|
68
|
+
}
|
|
46
69
|
return {
|
|
47
70
|
...guest,
|
|
48
71
|
kind: "guest-message",
|
|
@@ -128,6 +151,7 @@ class ApprovalsAdapter extends Emitter {
|
|
|
128
151
|
}
|
|
129
152
|
}
|
|
130
153
|
export {
|
|
154
|
+
ARGS_LENGTH,
|
|
131
155
|
ApprovalsAdapter,
|
|
132
156
|
NOT_HERE,
|
|
133
157
|
shown
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/spectrum/approvals-port.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n Approval,\n ApprovalDecision,\n ApprovalEnd,\n ApprovalRequester,\n ApprovalsEvents,\n ApprovalsPort,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Approval as Item } from '../approvals/record.ts';\nimport type { AgentConnection } from '../client.ts';\nimport { Emitter } from './emitter.ts';\n\nconst ENDS: Record<string, ApprovalEnd> = {\n approved: 'answered',\n rejected: 'answered',\n unanswered: 'expired',\n withdrawn: 'withdrawn',\n};\n\nfunction requesterOf(item: Item): ApprovalRequester {\n const { kind, name, agentId, where } = item.requester;\n return {\n kind,\n name,\n ...(agentId ? { agentId } : {}),\n ...(where ? { where } : {}),\n };\n}\n\nexport function shown(item: Item, agentName: string | undefined): Approval {\n const base = {\n id: item.id,\n requester: requesterOf(item),\n createdAt: item.createdAt,\n expiresAt: item.expiresAt,\n };\n if (item.kind === 'sudo') {\n const { command, target, cwd, alwaysScope } = item.detail;\n return {\n ...base,\n kind: 'sudo',\n command,\n ...(target ? { target } : {}),\n ...(cwd ? { cwd } : {}),\n always: item.always,\n ...(alwaysScope ? { alwaysScope } : {}),\n };\n }\n const guest = {\n ...base,\n agentId: item.agentId,\n ...(agentName ? { agentName } : {}),\n always: item.always,\n ...(item.verdict\n ? { verdict: { decision: item.verdict.decision, reason: item.verdict.reason } }\n : {}),\n };\n if (item.kind === 'guest-tool')\n
|
|
5
|
-
"mappings": "AAQA,SAAS,0BAA0B;AAGnC,SAAS,eAAe;AAExB,MAAM,OAAoC;AAAA,EACxC,UAAU;AAAA,EACV,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,WAAW;AACb;AAEA,SAAS,YAAY,MAA+B;AAClD,QAAM,EAAE,MAAM,MAAM,SAAS,MAAM,IAAI,KAAK;AAC5C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC7B,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,EAC3B;AACF;AAEO,SAAS,MAAM,MAAY,WAAyC;AACzE,QAAM,OAAO;AAAA,IACX,IAAI,KAAK;AAAA,IACT,WAAW,YAAY,IAAI;AAAA,IAC3B,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,EAClB;AACA,MAAI,KAAK,SAAS,QAAQ;AACxB,UAAM,EAAE,SAAS,QAAQ,KAAK,YAAY,IAAI,KAAK;AACnD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM;AAAA,MACN;AAAA,MACA,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,MAC3B,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;AAAA,MACrB,QAAQ,KAAK;AAAA,MACb,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,IACvC;AAAA,EACF;AACA,QAAM,QAAQ;AAAA,IACZ,GAAG;AAAA,IACH,SAAS,KAAK;AAAA,IACd,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,IACjC,QAAQ,KAAK;AAAA,IACb,GAAI,KAAK,UACL,EAAE,SAAS,EAAE,UAAU,KAAK,QAAQ,UAAU,QAAQ,KAAK,QAAQ,OAAO,EAAE,IAC5E,CAAC;AAAA,EACP;AACA,MAAI,KAAK,SAAS;
|
|
4
|
+
"sourcesContent": ["import type {\n Approval,\n ApprovalDecision,\n ApprovalEnd,\n ApprovalRequester,\n ApprovalsEvents,\n ApprovalsPort,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Approval as Item } from '../approvals/record.ts';\nimport type { AgentConnection } from '../client.ts';\nimport { Emitter } from './emitter.ts';\n\nconst ENDS: Record<string, ApprovalEnd> = {\n approved: 'answered',\n rejected: 'answered',\n unanswered: 'expired',\n withdrawn: 'withdrawn',\n};\n\nexport const ARGS_LENGTH = 65_536;\n\nfunction pretty(text: string): string {\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch {\n return text;\n }\n}\n\nfunction argsOf(text: string | undefined): { args?: string; argsTruncated?: true } {\n if (text === undefined) return {};\n const args = pretty(text);\n if (args.length <= ARGS_LENGTH) return { args };\n const end = /[\\uD800-\\uDBFF]/.test(args[ARGS_LENGTH - 1] as string)\n ? ARGS_LENGTH - 1\n : ARGS_LENGTH;\n return { args: args.slice(0, end), argsTruncated: true };\n}\n\nfunction requesterOf(item: Item): ApprovalRequester {\n const { kind, name, agentId, where } = item.requester;\n return {\n kind,\n name,\n ...(agentId ? { agentId } : {}),\n ...(where ? { where } : {}),\n };\n}\n\nexport function shown(item: Item, agentName: string | undefined): Approval {\n const base = {\n id: item.id,\n requester: requesterOf(item),\n createdAt: item.createdAt,\n expiresAt: item.expiresAt,\n };\n if (item.kind === 'sudo') {\n const { command, target, cwd, alwaysScope } = item.detail;\n return {\n ...base,\n kind: 'sudo',\n command,\n ...(target ? { target } : {}),\n ...(cwd ? { cwd } : {}),\n always: item.always,\n ...(alwaysScope ? { alwaysScope } : {}),\n };\n }\n const guest = {\n ...base,\n agentId: item.agentId,\n ...(agentName ? { agentName } : {}),\n always: item.always,\n ...(item.verdict\n ? { verdict: { decision: item.verdict.decision, reason: item.verdict.reason } }\n : {}),\n };\n if (item.kind === 'guest-tool') {\n const tool = {\n ...guest,\n kind: 'guest-tool' as const,\n tool: item.detail.tool,\n summary: item.detail.summary,\n ...argsOf(item.detail.arguments),\n };\n return tool;\n }\n return {\n ...guest,\n kind: 'guest-message',\n text: item.detail.text,\n ...(item.detail.attachments.length\n ? { attachments: item.detail.attachments.map((file) => ({ ...file })) }\n : {}),\n };\n}\n\nexport type HostApprovals = ApprovalsPort;\n\nexport const NOT_HERE = \"This request can't be answered in this tab.\";\n\nconst asked = (item: Item) => item.state === 'pending' && item.tier === 'user';\n\nexport class ApprovalsAdapter extends Emitter<ApprovalsEvents> implements ApprovalsPort {\n readonly #connection: AgentConnection;\n readonly #host: HostApprovals | undefined;\n readonly #ours: boolean;\n readonly #mine = new Set<string>();\n readonly #deciding = new Set<string>();\n #listed = new Set<string>();\n readonly decide?: (id: string, decision: ApprovalDecision, reason?: string) => Promise<void>;\n\n constructor(connection: AgentConnection, host?: HostApprovals) {\n super();\n this.#connection = connection;\n this.#host = host;\n this.#ours = !connection.guest;\n if (this.#ours || host?.decide) this.decide = (...args) => this.#route(...args);\n this.#listed = new Set(this.#agent().map((approval) => approval.id));\n connection.approvals?.subscribe(() => this.#changed());\n host?.on('approvals', () => this.emit('approvals', this.list()));\n host?.on('ended', (detail) => this.emit('ended', detail));\n }\n\n #items(): readonly Item[] {\n return this.#connection.approvals?.value ?? [];\n }\n\n list(): readonly Approval[] {\n return [...(this.#host?.list() ?? []), ...this.#agent()].sort(\n (a, b) => a.createdAt - b.createdAt\n );\n }\n\n #agent(): readonly Approval[] {\n const names = new Map(\n (this.#connection.agents?.value?.agents ?? []).map((agent) => [agent.id, agent.name])\n );\n return this.#items()\n .filter((item) => asked(item) && !this.#mine.has(item.id))\n .sort((a, b) => a.createdAt - b.createdAt)\n .map((item) => shown(item, names.get(item.agentId)));\n }\n\n #changed(): void {\n const items = new Map(this.#items().map((item) => [item.id, item]));\n const listed = this.#agent();\n const now = new Set(listed.map((approval) => approval.id));\n for (const id of this.#listed) {\n if (now.has(id) || this.#mine.has(id) || this.#deciding.has(id)) continue;\n const state = items.get(id)?.state;\n this.emit('ended', { id, reason: (state && ENDS[state]) || 'answered' });\n }\n for (const id of this.#mine) {\n const item = items.get(id);\n if (!item || !asked(item)) this.#mine.delete(id);\n }\n this.#listed = now;\n this.emit('approvals', this.list());\n }\n\n #route(id: string, decision: ApprovalDecision, reason?: string): Promise<void> {\n const host = this.#host;\n if (host?.list().some((approval) => approval.id === id)) {\n if (!host.decide) return Promise.reject(new Error(NOT_HERE));\n return host.decide(id, decision, reason);\n }\n if (!this.#ours) return Promise.reject(new Error(NOT_HERE));\n return this.#decide(id, decision, reason);\n }\n\n async #decide(id: string, decision: ApprovalDecision, reason?: string): Promise<void> {\n this.#deciding.add(id);\n try {\n const answer = await this.#connection.control.decide(\n id,\n decision,\n reason ?? null,\n BACKGROUND_CONTEXT\n );\n if (!answer.done) throw new Error(answer.error);\n this.#mine.add(id);\n } finally {\n this.#deciding.delete(id);\n }\n this.#changed();\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,7 +2,7 @@ import type { Context } from '@earendil-works/chord';
|
|
|
2
2
|
import { type Extension, type Harness, type SubmissionId } from '@earendil-works/pi-durable';
|
|
3
3
|
import type { Approval, Approvals } from '../approvals/index.ts';
|
|
4
4
|
import { type GuestPolicy } from './policy.ts';
|
|
5
|
-
import { type SeatRecord } from './seats.ts';
|
|
5
|
+
import { type AlwaysTool, type SeatRecord } from './seats.ts';
|
|
6
6
|
export declare const GUEST_GUARD = "slicc-guest-guard";
|
|
7
7
|
export declare const GUEST_REQUEST = "guest:";
|
|
8
8
|
export declare const GUEST_TOOL_MEMO = "slicc.guest-tool";
|
|
@@ -31,6 +31,9 @@ type Taint = {
|
|
|
31
31
|
tier: string;
|
|
32
32
|
};
|
|
33
33
|
export declare function canonical(value: unknown): string;
|
|
34
|
+
export declare function scrubbedArguments(args: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
35
|
+
export declare function argumentsHash(args: string): Promise<string>;
|
|
36
|
+
export declare function sealedRules(rules: readonly AlwaysTool[]): Promise<AlwaysTool[] | null>;
|
|
34
37
|
export declare function summaryOf(args: Record<string, unknown>): string;
|
|
35
38
|
export declare function reasonOf(item: Approval): string;
|
|
36
39
|
export declare function taintsOf(inputs: readonly SubmissionId[], seats: Readonly<Record<string, SeatRecord>>, harness: Pick<Harness, 'submission'>, context: Context): Promise<Taint[]>;
|
|
@@ -20,10 +20,12 @@ export type SeatSubmission = {
|
|
|
20
20
|
export type AlwaysTool = {
|
|
21
21
|
approval: string;
|
|
22
22
|
tool: string;
|
|
23
|
+
argumentsHash: string;
|
|
23
24
|
arguments: string;
|
|
24
25
|
by: DecidedBy | null;
|
|
25
26
|
at: number;
|
|
26
27
|
};
|
|
28
|
+
export type ShownAlwaysTool = Omit<AlwaysTool, 'argumentsHash'>;
|
|
27
29
|
export type SeatRecord = GuestDraft & {
|
|
28
30
|
createdAt: number;
|
|
29
31
|
epoch: number;
|
|
@@ -46,7 +48,7 @@ export interface GuestSeat {
|
|
|
46
48
|
gates: GuestGates;
|
|
47
49
|
connected: boolean;
|
|
48
50
|
pending: number;
|
|
49
|
-
alwaysTools:
|
|
51
|
+
alwaysTools: ShownAlwaysTool[];
|
|
50
52
|
}
|
|
51
53
|
export type GuestsState = {
|
|
52
54
|
seats: Record<string, SeatRecord>;
|
|
@@ -2,6 +2,7 @@ import type { Approval, ApprovalDecision, ApprovalsEvents, ApprovalsPort } from
|
|
|
2
2
|
import type { Approval as Item } from '../approvals/record.ts';
|
|
3
3
|
import type { AgentConnection } from '../client.ts';
|
|
4
4
|
import { Emitter } from './emitter.ts';
|
|
5
|
+
export declare const ARGS_LENGTH = 65536;
|
|
5
6
|
export declare function shown(item: Item, agentName: string | undefined): Approval;
|
|
6
7
|
export type HostApprovals = ApprovalsPort;
|
|
7
8
|
export declare const NOT_HERE = "This request can't be answered in this tab.";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-agent",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.17.1",
|
|
4
4
|
"description": "SLICC's agent core on pi durable: an agent worker, session storage, remoting and pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"release": "semantic-release"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@ai-ecoverse/slicc-kernel": "1.
|
|
68
|
+
"@ai-ecoverse/slicc-kernel": "1.135.3",
|
|
69
69
|
"@ai-ecoverse/slicc-scrub": "1.0.0",
|
|
70
|
-
"@ai-ecoverse/slicc-shared-web": "1.
|
|
70
|
+
"@ai-ecoverse/slicc-shared-web": "1.27.0",
|
|
71
71
|
"@ai-ecoverse/slicc-spectrum": "1.101.2",
|
|
72
72
|
"@ai-ecoverse/wasi-fnox": "1.39.0-5",
|
|
73
73
|
"@ai-ecoverse/wasm-bash": "5.3.0-21",
|