@ai-ecoverse/slicc-agent 5.0.2 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/approvals/index.js +172 -0
- package/dist/approvals/index.js.map +7 -0
- package/dist/approvals/record.js +89 -0
- package/dist/approvals/record.js.map +7 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +2 -2
- package/dist/control.js +14 -3
- package/dist/control.js.map +2 -2
- package/dist/host.js +18 -3
- package/dist/host.js.map +2 -2
- package/dist/index.js +10 -0
- package/dist/index.js.map +2 -2
- package/dist/services.js +4 -2
- package/dist/services.js.map +2 -2
- package/dist/spectrum/approvals-port.js +135 -0
- package/dist/spectrum/approvals-port.js.map +7 -0
- package/dist/spectrum/index.js +10 -0
- package/dist/spectrum/index.js.map +3 -3
- package/dist/types/approvals/index.d.ts +28 -0
- package/dist/types/approvals/record.d.ts +108 -0
- package/dist/types/client.d.ts +3 -0
- package/dist/types/host.d.ts +4 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/services.d.ts +5 -2
- package/dist/types/spectrum/approvals-port.d.ts +13 -0
- package/dist/types/spectrum/index.d.ts +4 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -600,6 +600,20 @@ It prints the tab's stdout and stderr on their own streams and exits with its co
|
|
|
600
600
|
|
|
601
601
|
**Followers never execute.** Read-only attachments (`owner.port({ readOnly: true })`) and attachments made with `owner.port({ pages: false })`, which bios uses for link followers, get a `slicc.agent.pages` whose `open` answers `{ error }`, so they are never chosen.
|
|
602
602
|
|
|
603
|
+
### Approvals
|
|
604
|
+
|
|
605
|
+
Every approval, of a guest's prompt, of a tool call in a run a guest started, or of a sudo request, is an item in one session document, `slicc.approvals` ([`src/approvals/`](src/approvals/)). That document is the authority: not a lick, which the agent can confirm itself, and not a UI projection, so a settled item stays settled after a reload.
|
|
606
|
+
|
|
607
|
+
- **An item** is `{ id, kind, seat?, agentId, requester, detail, tier, state, decidedBy, answer?, reason?, verdict?, always, createdAt, expiresAt, decidedAt? }`. `kind` is `guest-message` (`detail: { text, attachments }`), `guest-tool` (`{ tool, summary }`) or `sudo` (`{ command, target?, cwd?, alwaysScope? }`). `requester` is `{ kind: 'guest' | 'agent' | 'user', name, seat?, agentId?, where? }`.
|
|
608
|
+
- **`state`** is `pending`, `approved` (with `answer` `once` or `always`), `rejected` (someone said no), `unanswered` (nobody decided: it timed out or couldn't be routed) or `withdrawn` (the requester went away). `reason` is a field, never a state: a person's own words, or `timeout`, `unknown-tier`, `unroutable`, `unknown-answer`, `restart` or `revoked`. `decidedBy` is `owner`, `follower`, `judge` or `host`.
|
|
609
|
+
- **`tier`** says who answers: `user` (a person, through `decide`), `agent` (the automatic judge), `cone` or `scoop:<name>`. A tier the agent doesn't know settles at once as `unanswered` with `unknown-tier`, and so does `cone` for a guest's tool call (`unroutable`), because a run can't approve its own gate. A person answers only `user` items, the judge only `agent` ones.
|
|
610
|
+
- **The rules:** the first decision wins, and a later one, or one past `expiresAt`, is refused ("Someone else already answered this.", "This request timed out.", "This request was withdrawn."). An answer other than `once`, `always` or `deny` is a deny (`unknown-answer`). `always` is `false` for anything a guest triggered, and `decide(id, 'always')` on such an item is refused and leaves it pending.
|
|
611
|
+
- **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.
|
|
612
|
+
- **In the worker,** `AgentHost.approvals` has `request(draft, ctx)`, `decide(id, answer, reason, by, ctx)`, `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.
|
|
613
|
+
- **Over the link,** `AgentTranscript.approvals` replicates the items, and `AgentControl.decide(id, decision, reason, ctx)` answers one, for the owner's pages and followers with Control, the same path as `send`. `decidedBy` comes from the link's seat. A follower without Control sees the items, and its `decide` is refused with `EACCES`.
|
|
614
|
+
- **In spectrum** (1.78 on, which shows guest approvals in `<slicc-approvals>` without Always, and as notices without `decide`), `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`. `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. A connection without Control (`connection.readOnly`) gets no `decide`.
|
|
615
|
+
- **With the host's own approvals:** bios has its own `approvals` port for sudo, and a page that spreads `{ ...bios, ...createAgentModel(connection) }` would lose it. Pass that port in instead, as `createAgentModel(connection, { approvals: biosPort })`. The returned port then lists both sets, oldest first, sends `decide` to whichever port listed the id, and passes on both ports' `approvals` and `ended` events. It has `decide` when either side can answer; asked for an item its owner can't answer, it rejects with "This request can't be answered in this tab." Without the option, it lists only the agent's items.
|
|
616
|
+
|
|
603
617
|
### Remote control
|
|
604
618
|
|
|
605
619
|
`remoteControl(connection, { agent? })` from `@ai-ecoverse/slicc-agent/page` gives a linked CLI's `slicc prompt`, `slicc watch` and `slicc abort` (the `agent` stream of the slicc link protocol, §5.11) as plain objects the page writes to the link as NDJSON. `agent()` names the agent, by default the cone the worker treats as current.
|
|
@@ -631,7 +645,7 @@ The agent never holds a secret's value. Values live in the vault (`vault.sliccy.
|
|
|
631
645
|
|
|
632
646
|
## The kernel
|
|
633
647
|
|
|
634
|
-
`@ai-ecoverse/slicc-kernel` is a peer dependency (`>=1.
|
|
648
|
+
`@ai-ecoverse/slicc-kernel` is a peer dependency (`>=1.90.0 <2`; until 3.31.0 the floor was 1.26.3, then 1.47.1, then 1.81.0): the agent only calls `attachKernel`, the client end of the page kernel's port, so it must match the kernel the page runs. Seven chooses that version: slicc-bios pins the certified kernel next to the agent in `src/packages/agent/package.json`, and pnpm fills the peer from that pin. The floor is the kernel slicc-bios ships (1.90.0 today), and slicc-bios's own CI tests the agent on it; when slicc-bios moves its pin, the floor follows. The kernel and the `wasm-*` commands are devDependencies pinned to certified versions (kernel 1.94.0 today), and CI runs the unit and integration tests on them in one leg. If the port protocol ever breaks within 1.x, a patch release narrows the upper bound. Renovate opens PRs for the kernel, `wasi-pnpm`, the `wasm-*` packages and `slicc-cdp` but never merges them, because they move only after the homescoop coordinator certifies them; such a PR is merged once the cert lane has certified its version.
|
|
635
649
|
|
|
636
650
|
## Patched dependencies
|
|
637
651
|
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { replicatedState } from "@earendil-works/chord";
|
|
2
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
3
|
+
import {
|
|
4
|
+
APPROVAL_MS,
|
|
5
|
+
ApprovalsDoc,
|
|
6
|
+
cleanReason,
|
|
7
|
+
guestKind,
|
|
8
|
+
knownTier,
|
|
9
|
+
MAX_DELAY,
|
|
10
|
+
prune,
|
|
11
|
+
REASONS,
|
|
12
|
+
routable,
|
|
13
|
+
ruled
|
|
14
|
+
} from "./record.js";
|
|
15
|
+
export * from "./record.js";
|
|
16
|
+
const copy = (items) => JSON.parse(JSON.stringify(items));
|
|
17
|
+
async function attachApprovals(options, context = BACKGROUND_CONTEXT) {
|
|
18
|
+
const { harness } = options;
|
|
19
|
+
const now = options.now ?? Date.now;
|
|
20
|
+
const ttl = options.ttlMs ?? APPROVAL_MS;
|
|
21
|
+
const timers = /* @__PURE__ */ new Map();
|
|
22
|
+
let line = Promise.resolve();
|
|
23
|
+
let closed = false;
|
|
24
|
+
const settle = (item, how) => {
|
|
25
|
+
item.state = how.state;
|
|
26
|
+
item.decidedBy = how.by;
|
|
27
|
+
item.decidedAt = now();
|
|
28
|
+
if (how.reason) item.reason = how.reason;
|
|
29
|
+
};
|
|
30
|
+
const write = (change, using) => harness.commit(async (tx) => {
|
|
31
|
+
const doc = await tx.doc(ApprovalsDoc);
|
|
32
|
+
const result = change(doc);
|
|
33
|
+
doc.items = prune(doc.items);
|
|
34
|
+
return { result, items: copy(doc.items) };
|
|
35
|
+
}, using);
|
|
36
|
+
const startup = await write((doc) => {
|
|
37
|
+
for (const item of doc.items) {
|
|
38
|
+
if (item.state !== "pending") continue;
|
|
39
|
+
if (item.kind === "sudo")
|
|
40
|
+
settle(item, { state: "withdrawn", by: "host", reason: REASONS.restart });
|
|
41
|
+
else if (item.expiresAt <= now())
|
|
42
|
+
settle(item, { state: "unanswered", by: "host", reason: REASONS.timeout });
|
|
43
|
+
}
|
|
44
|
+
}, context);
|
|
45
|
+
const state = replicatedState(startup.items);
|
|
46
|
+
const serial = (change, using) => {
|
|
47
|
+
const run = line.then(async () => {
|
|
48
|
+
const { result, items } = await write(change, using);
|
|
49
|
+
state.replace(using, items);
|
|
50
|
+
arm(items);
|
|
51
|
+
return result;
|
|
52
|
+
});
|
|
53
|
+
line = run.catch(() => void 0);
|
|
54
|
+
return run;
|
|
55
|
+
};
|
|
56
|
+
const expire = (id) => {
|
|
57
|
+
timers.delete(id);
|
|
58
|
+
const due = state.value.find((entry) => entry.id === id);
|
|
59
|
+
if (due?.state === "pending" && due.expiresAt > now()) return arm(state.value);
|
|
60
|
+
void serial((doc) => {
|
|
61
|
+
const item = doc.items.find((entry) => entry.id === id);
|
|
62
|
+
if (item?.state === "pending" && item.expiresAt <= now())
|
|
63
|
+
settle(item, { state: "unanswered", by: "host", reason: REASONS.timeout });
|
|
64
|
+
}, BACKGROUND_CONTEXT).catch(() => void 0);
|
|
65
|
+
};
|
|
66
|
+
function arm(items) {
|
|
67
|
+
const pending = /* @__PURE__ */ new Set();
|
|
68
|
+
for (const item of items) {
|
|
69
|
+
if (item.state !== "pending") continue;
|
|
70
|
+
pending.add(item.id);
|
|
71
|
+
if (timers.has(item.id) || closed) continue;
|
|
72
|
+
timers.set(
|
|
73
|
+
item.id,
|
|
74
|
+
setTimeout(() => expire(item.id), Math.min(MAX_DELAY, Math.max(0, item.expiresAt - now())))
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
for (const [id, timer] of timers)
|
|
78
|
+
if (!pending.has(id)) {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
timers.delete(id);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
arm(startup.items);
|
|
84
|
+
const withdrawWhere = (match, reason) => (doc) => {
|
|
85
|
+
const hit = doc.items.filter((item) => item.state === "pending" && match(item));
|
|
86
|
+
for (const item of hit)
|
|
87
|
+
settle(item, { state: "withdrawn", by: "host", ...reason ? { reason } : {} });
|
|
88
|
+
return hit.map((item) => copy([item])[0]);
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
state,
|
|
92
|
+
request(draft, using) {
|
|
93
|
+
return serial((doc) => {
|
|
94
|
+
const at = now();
|
|
95
|
+
const item = {
|
|
96
|
+
...draft,
|
|
97
|
+
id: `apr-${crypto.randomUUID()}`,
|
|
98
|
+
state: "pending",
|
|
99
|
+
decidedBy: null,
|
|
100
|
+
always: guestKind(draft.kind) ? false : draft.always === true,
|
|
101
|
+
createdAt: at,
|
|
102
|
+
expiresAt: draft.expiresAt ?? at + ttl
|
|
103
|
+
};
|
|
104
|
+
if (!knownTier(draft.tier))
|
|
105
|
+
settle(item, { state: "unanswered", by: "host", reason: REASONS.unknownTier });
|
|
106
|
+
else if (!routable(draft.kind, draft.tier))
|
|
107
|
+
settle(item, { state: "unanswered", by: "host", reason: REASONS.unroutable });
|
|
108
|
+
doc.items.push(item);
|
|
109
|
+
return copy([item])[0];
|
|
110
|
+
}, using);
|
|
111
|
+
},
|
|
112
|
+
decide(id, answer, reason, by, using) {
|
|
113
|
+
return serial((doc) => {
|
|
114
|
+
const item = doc.items.find((entry) => entry.id === id);
|
|
115
|
+
const ruling = item ? ruled(item, answer, reason, by, now()) : { error: `There is no approval ${id}.` };
|
|
116
|
+
if (item && ruling.settle) {
|
|
117
|
+
if (ruling.answer) item.answer = ruling.answer;
|
|
118
|
+
settle(item, ruling.settle);
|
|
119
|
+
}
|
|
120
|
+
if ("error" in ruling && ruling.error)
|
|
121
|
+
return { done: false, state: null, error: ruling.error };
|
|
122
|
+
return { done: true, state: item.state, error: null };
|
|
123
|
+
}, using);
|
|
124
|
+
},
|
|
125
|
+
settled(id, using) {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const signal = using.abortSignal;
|
|
128
|
+
let done = false;
|
|
129
|
+
let off;
|
|
130
|
+
const finish = (end) => {
|
|
131
|
+
done = true;
|
|
132
|
+
off?.();
|
|
133
|
+
signal?.removeEventListener("abort", abort);
|
|
134
|
+
end();
|
|
135
|
+
};
|
|
136
|
+
const abort = () => finish(() => reject(signal?.reason));
|
|
137
|
+
const check = (items) => {
|
|
138
|
+
const item = items?.find((entry) => entry.id === id);
|
|
139
|
+
if (!item) finish(() => reject(new Error(`There is no approval ${id}.`)));
|
|
140
|
+
else if (item.state !== "pending") finish(() => resolve(item));
|
|
141
|
+
};
|
|
142
|
+
if (signal?.aborted) return abort();
|
|
143
|
+
check(state.value);
|
|
144
|
+
if (done) return;
|
|
145
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
146
|
+
off = state.subscribe((items) => check(items));
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
async withdraw(id, reason, using) {
|
|
150
|
+
const [hit] = await serial(
|
|
151
|
+
withdrawWhere((item) => item.id === id, cleanReason(reason)),
|
|
152
|
+
using
|
|
153
|
+
);
|
|
154
|
+
return hit;
|
|
155
|
+
},
|
|
156
|
+
async withdrawSeat(seat, using) {
|
|
157
|
+
return (await serial(
|
|
158
|
+
withdrawWhere((item) => item.seat === seat, REASONS.revoked),
|
|
159
|
+
using
|
|
160
|
+
)).length;
|
|
161
|
+
},
|
|
162
|
+
close() {
|
|
163
|
+
closed = true;
|
|
164
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
165
|
+
timers.clear();
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export {
|
|
170
|
+
attachApprovals
|
|
171
|
+
};
|
|
172
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/approvals/index.ts"],
|
|
4
|
+
"sourcesContent": ["import { type Context, type ReplicatedState, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Harness } from '@earendil-works/pi-durable';\nimport {\n APPROVAL_MS,\n type Approval,\n type ApprovalDraft,\n type ApprovalState,\n ApprovalsDoc,\n type ApprovalsState,\n cleanReason,\n type DecidedBy,\n guestKind,\n knownTier,\n MAX_DELAY,\n prune,\n REASONS,\n routable,\n ruled,\n type Settle,\n} from './record.ts';\n\nexport * from './record.ts';\n\nexport type ApprovalDecided =\n | { done: true; state: ApprovalState; error: null }\n | { done: false; state: null; error: string };\n\nexport interface Approvals {\n readonly state: ReplicatedState<Approval[]>;\n request(draft: ApprovalDraft, context: Context): Promise<Approval>;\n decide(\n id: string,\n answer: unknown,\n reason: string | null,\n by: DecidedBy,\n context: Context\n ): Promise<ApprovalDecided>;\n settled(id: string, context: Context): Promise<Approval>;\n withdraw(id: string, reason: string | null, context: Context): Promise<Approval | undefined>;\n withdrawSeat(seat: string, context: Context): Promise<number>;\n close(): void;\n}\n\nexport interface ApprovalsAttach {\n harness: Pick<Harness, 'commit'>;\n now?: () => number;\n ttlMs?: number;\n}\n\nconst copy = (items: readonly Approval[]): Approval[] => JSON.parse(JSON.stringify(items));\n\nexport async function attachApprovals(\n options: ApprovalsAttach,\n context: Context = BACKGROUND_CONTEXT\n): Promise<Approvals> {\n const { harness } = options;\n const now = options.now ?? Date.now;\n const ttl = options.ttlMs ?? APPROVAL_MS;\n const timers = new Map<string, ReturnType<typeof setTimeout>>();\n let line: Promise<unknown> = Promise.resolve();\n let closed = false;\n const settle = (item: Approval, how: Settle) => {\n item.state = how.state;\n item.decidedBy = how.by;\n item.decidedAt = now();\n if (how.reason) item.reason = how.reason;\n };\n const write = <R>(\n change: (doc: ApprovalsState) => R,\n using: Context\n ): Promise<{ result: R; items: Approval[] }> =>\n harness.commit(async (tx) => {\n const doc = await tx.doc(ApprovalsDoc);\n const result = change(doc);\n doc.items = prune(doc.items);\n return { result, items: copy(doc.items) };\n }, using);\n const startup = await write((doc) => {\n for (const item of doc.items) {\n if (item.state !== 'pending') continue;\n if (item.kind === 'sudo')\n settle(item, { state: 'withdrawn', by: 'host', reason: REASONS.restart });\n else if (item.expiresAt <= now())\n settle(item, { state: 'unanswered', by: 'host', reason: REASONS.timeout });\n }\n }, context);\n const state = replicatedState<Approval[]>(startup.items);\n const serial = <R>(change: (doc: ApprovalsState) => R, using: Context): Promise<R> => {\n const run = line.then(async () => {\n const { result, items } = await write(change, using);\n state.replace(using, items);\n arm(items);\n return result;\n });\n line = run.catch(() => undefined);\n return run;\n };\n const expire = (id: string) => {\n timers.delete(id);\n const due = (state.value as Approval[]).find((entry) => entry.id === id);\n if (due?.state === 'pending' && due.expiresAt > now()) return arm(state.value as Approval[]);\n void serial((doc) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.state === 'pending' && item.expiresAt <= now())\n settle(item, { state: 'unanswered', by: 'host', reason: REASONS.timeout });\n }, BACKGROUND_CONTEXT).catch(() => undefined);\n };\n function arm(items: readonly Approval[]) {\n const pending = new Set<string>();\n for (const item of items) {\n if (item.state !== 'pending') continue;\n pending.add(item.id);\n if (timers.has(item.id) || closed) continue;\n timers.set(\n item.id,\n setTimeout(() => expire(item.id), Math.min(MAX_DELAY, Math.max(0, item.expiresAt - now())))\n );\n }\n for (const [id, timer] of timers)\n if (!pending.has(id)) {\n clearTimeout(timer);\n timers.delete(id);\n }\n }\n arm(startup.items);\n const withdrawWhere =\n (match: (item: Approval) => boolean, reason: string | undefined) => (doc: ApprovalsState) => {\n const hit = doc.items.filter((item) => item.state === 'pending' && match(item));\n for (const item of hit)\n settle(item, { state: 'withdrawn', by: 'host', ...(reason ? { reason } : {}) });\n return hit.map((item) => copy([item])[0] as Approval);\n };\n return {\n state,\n request(draft, using) {\n return serial((doc) => {\n const at = now();\n const item = {\n ...draft,\n id: `apr-${crypto.randomUUID()}`,\n state: 'pending',\n decidedBy: null,\n always: guestKind(draft.kind) ? false : draft.always === true,\n createdAt: at,\n expiresAt: draft.expiresAt ?? at + ttl,\n } as Approval;\n if (!knownTier(draft.tier))\n settle(item, { state: 'unanswered', by: 'host', reason: REASONS.unknownTier });\n else if (!routable(draft.kind, draft.tier))\n settle(item, { state: 'unanswered', by: 'host', reason: REASONS.unroutable });\n doc.items.push(item);\n return copy([item])[0] as Approval;\n }, using);\n },\n decide(id, answer, reason, by, using) {\n return serial((doc): ApprovalDecided => {\n const item = doc.items.find((entry) => entry.id === id);\n const ruling = item\n ? ruled(item, answer, reason, by, now())\n : { error: `There is no approval ${id}.` };\n if (item && ruling.settle) {\n if (ruling.answer) item.answer = ruling.answer;\n settle(item, ruling.settle);\n }\n if ('error' in ruling && ruling.error)\n return { done: false, state: null, error: ruling.error };\n return { done: true, state: (item as Approval).state, error: null };\n }, using);\n },\n settled(id, using) {\n return new Promise((resolve, reject) => {\n const signal = using.abortSignal;\n let done = false;\n let off: (() => void) | undefined;\n const finish = (end: () => void) => {\n done = true;\n off?.();\n signal?.removeEventListener('abort', abort);\n end();\n };\n const abort = () => finish(() => reject(signal?.reason));\n const check = (items: readonly Approval[] | undefined) => {\n const item = items?.find((entry) => entry.id === id);\n if (!item) finish(() => reject(new Error(`There is no approval ${id}.`)));\n else if (item.state !== 'pending') finish(() => resolve(item));\n };\n if (signal?.aborted) return abort();\n check(state.value);\n if (done) return;\n signal?.addEventListener('abort', abort, { once: true });\n off = state.subscribe((items) => check(items));\n });\n },\n async withdraw(id, reason, using) {\n const [hit] = await serial(\n withdrawWhere((item) => item.id === id, cleanReason(reason)),\n using\n );\n return hit;\n },\n async withdrawSeat(seat, using) {\n return (\n await serial(\n withdrawWhere((item) => item.seat === seat, REASONS.revoked),\n using\n )\n ).length;\n },\n close() {\n closed = true;\n for (const timer of timers.values()) clearTimeout(timer);\n timers.clear();\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { defineDoc } from "@earendil-works/pi-durable";
|
|
2
|
+
const ApprovalsDoc = defineDoc({
|
|
3
|
+
kind: "slicc.approvals",
|
|
4
|
+
version: 1,
|
|
5
|
+
scope: "session",
|
|
6
|
+
initial: () => ({ items: [] })
|
|
7
|
+
});
|
|
8
|
+
const APPROVAL_MS = 3e5;
|
|
9
|
+
const APPROVALS_KEPT = 100;
|
|
10
|
+
const MAX_DELAY = 2147483647;
|
|
11
|
+
const REASON_LENGTH = 500;
|
|
12
|
+
const REASONS = {
|
|
13
|
+
timeout: "timeout",
|
|
14
|
+
unknownTier: "unknown-tier",
|
|
15
|
+
unroutable: "unroutable",
|
|
16
|
+
unknownAnswer: "unknown-answer",
|
|
17
|
+
restart: "restart",
|
|
18
|
+
revoked: "revoked"
|
|
19
|
+
};
|
|
20
|
+
const guestKind = (kind) => kind !== "sudo";
|
|
21
|
+
function knownTier(tier) {
|
|
22
|
+
if (tier === "user" || tier === "agent" || tier === "cone") return true;
|
|
23
|
+
return typeof tier === "string" && /^scoop:\S+$/.test(tier);
|
|
24
|
+
}
|
|
25
|
+
function routable(kind, tier) {
|
|
26
|
+
return !(kind === "guest-tool" && tier === "cone");
|
|
27
|
+
}
|
|
28
|
+
function answerOf(value) {
|
|
29
|
+
return value === "once" || value === "always" || value === "deny" ? value : null;
|
|
30
|
+
}
|
|
31
|
+
const personal = (by) => by === "owner" || by === "follower";
|
|
32
|
+
function answerer(by, tier) {
|
|
33
|
+
if (personal(by)) return tier === "user";
|
|
34
|
+
if (by === "judge") return tier === "agent";
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
function gone(state) {
|
|
38
|
+
if (state === "unanswered") return "This request timed out.";
|
|
39
|
+
if (state === "withdrawn") return "This request was withdrawn.";
|
|
40
|
+
return "Someone else already answered this.";
|
|
41
|
+
}
|
|
42
|
+
function cleanReason(reason) {
|
|
43
|
+
if (typeof reason !== "string") return void 0;
|
|
44
|
+
const text = reason.trim().slice(0, REASON_LENGTH);
|
|
45
|
+
return text || void 0;
|
|
46
|
+
}
|
|
47
|
+
const decided = (item) => item.decidedAt ?? item.createdAt;
|
|
48
|
+
function prune(items, kept = APPROVALS_KEPT) {
|
|
49
|
+
const settled = items.filter((item) => item.state !== "pending");
|
|
50
|
+
settled.sort((a, b) => decided(a) - decided(b));
|
|
51
|
+
const drop = new Set(settled.slice(0, Math.max(0, settled.length - kept)));
|
|
52
|
+
return items.filter((item) => !drop.has(item));
|
|
53
|
+
}
|
|
54
|
+
const why = (reason) => reason ? { reason } : {};
|
|
55
|
+
function ruled(item, answer, reason, by, at) {
|
|
56
|
+
if (item.state !== "pending") return { error: gone(item.state) };
|
|
57
|
+
if (item.expiresAt <= at)
|
|
58
|
+
return {
|
|
59
|
+
error: gone("unanswered"),
|
|
60
|
+
settle: { state: "unanswered", by: "host", reason: REASONS.timeout }
|
|
61
|
+
};
|
|
62
|
+
if (!answerer(by, item.tier)) return { error: "This request isn't yours to answer." };
|
|
63
|
+
const known = answerOf(answer);
|
|
64
|
+
if (known === "always" && !item.always)
|
|
65
|
+
return { error: "Always isn't offered for this request." };
|
|
66
|
+
if (!known) return { settle: { state: "rejected", by, reason: REASONS.unknownAnswer } };
|
|
67
|
+
const given = why(cleanReason(reason));
|
|
68
|
+
if (known === "deny") return { settle: { state: "rejected", by, ...given } };
|
|
69
|
+
return { settle: { state: "approved", by, ...given }, answer: known };
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
APPROVALS_KEPT,
|
|
73
|
+
APPROVAL_MS,
|
|
74
|
+
ApprovalsDoc,
|
|
75
|
+
MAX_DELAY,
|
|
76
|
+
REASONS,
|
|
77
|
+
REASON_LENGTH,
|
|
78
|
+
answerOf,
|
|
79
|
+
answerer,
|
|
80
|
+
cleanReason,
|
|
81
|
+
gone,
|
|
82
|
+
guestKind,
|
|
83
|
+
knownTier,
|
|
84
|
+
personal,
|
|
85
|
+
prune,
|
|
86
|
+
routable,
|
|
87
|
+
ruled
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/approvals/record.ts"],
|
|
4
|
+
"sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\n\nexport type ApprovalKind = 'guest-message' | 'guest-tool' | 'sudo';\n\nexport type ApprovalState = 'pending' | 'approved' | 'rejected' | 'unanswered' | 'withdrawn';\n\nexport type ApprovalTier = 'user' | 'agent' | 'cone' | `scoop:${string}`;\n\nexport type ApprovalAnswer = 'once' | 'always' | 'deny';\n\nexport type DecidedBy = 'owner' | 'follower' | 'judge' | 'host';\n\nexport type ApprovalRequester = {\n kind: 'guest' | 'agent' | 'user';\n name: string;\n seat?: string;\n agentId?: string;\n where?: string;\n};\n\nexport type ApprovalVerdict = { decision: 'allow' | 'deny'; reason: string };\n\nexport type ApprovalAttachment = { name: string; mimeType: string; size: number };\n\nexport type GuestMessageDetail = { text: string; attachments: ApprovalAttachment[] };\n\nexport type GuestToolDetail = { tool: string; summary: string };\n\nexport type SudoDetail = {\n command: string;\n target?: string;\n cwd?: string;\n alwaysScope?: 'cwd';\n};\n\ntype Base = {\n id: string;\n seat?: string;\n agentId: string;\n requester: ApprovalRequester;\n tier: string;\n state: ApprovalState;\n decidedBy: DecidedBy | null;\n answer?: 'once' | 'always';\n reason?: string;\n verdict?: ApprovalVerdict;\n always: boolean;\n createdAt: number;\n expiresAt: number;\n decidedAt?: number;\n};\n\nexport type GuestMessageApproval = Base & { kind: 'guest-message'; detail: GuestMessageDetail };\nexport type GuestToolApproval = Base & { kind: 'guest-tool'; detail: GuestToolDetail };\nexport type SudoApproval = Base & { kind: 'sudo'; detail: SudoDetail };\n\nexport type Approval = GuestMessageApproval | GuestToolApproval | SudoApproval;\n\ntype DraftOf<T extends Approval> = Pick<T, 'kind' | 'detail' | 'agentId' | 'requester'> & {\n seat?: string;\n tier: string;\n always?: boolean;\n expiresAt?: number;\n};\n\nexport type ApprovalDraft =\n | DraftOf<GuestMessageApproval>\n | DraftOf<GuestToolApproval>\n | DraftOf<SudoApproval>;\n\nexport type ApprovalsState = { items: Approval[] };\n\nexport const ApprovalsDoc = defineDoc<ApprovalsState>({\n kind: 'slicc.approvals',\n version: 1,\n scope: 'session',\n initial: () => ({ items: [] }),\n});\n\nexport const APPROVAL_MS = 300_000;\nexport const APPROVALS_KEPT = 100;\nexport const MAX_DELAY = 2_147_483_647;\nexport const REASON_LENGTH = 500;\n\nexport const REASONS = {\n timeout: 'timeout',\n unknownTier: 'unknown-tier',\n unroutable: 'unroutable',\n unknownAnswer: 'unknown-answer',\n restart: 'restart',\n revoked: 'revoked',\n} as const;\n\nexport const guestKind = (kind: ApprovalKind): boolean => kind !== 'sudo';\n\nexport function knownTier(tier: unknown): tier is ApprovalTier {\n if (tier === 'user' || tier === 'agent' || tier === 'cone') return true;\n return typeof tier === 'string' && /^scoop:\\S+$/.test(tier);\n}\n\nexport function routable(kind: ApprovalKind, tier: ApprovalTier): boolean {\n return !(kind === 'guest-tool' && tier === 'cone');\n}\n\nexport function answerOf(value: unknown): ApprovalAnswer | null {\n return value === 'once' || value === 'always' || value === 'deny' ? value : null;\n}\n\nexport const personal = (by: DecidedBy): boolean => by === 'owner' || by === 'follower';\n\nexport function answerer(by: DecidedBy, tier: string): boolean {\n if (personal(by)) return tier === 'user';\n if (by === 'judge') return tier === 'agent';\n return true;\n}\n\nexport function gone(state: ApprovalState): string {\n if (state === 'unanswered') return 'This request timed out.';\n if (state === 'withdrawn') return 'This request was withdrawn.';\n return 'Someone else already answered this.';\n}\n\nexport function cleanReason(reason: unknown): string | undefined {\n if (typeof reason !== 'string') return undefined;\n const text = reason.trim().slice(0, REASON_LENGTH);\n return text || undefined;\n}\n\nconst decided = (item: Approval) => item.decidedAt ?? item.createdAt;\n\nexport function prune(items: Approval[], kept = APPROVALS_KEPT): Approval[] {\n const settled = items.filter((item) => item.state !== 'pending');\n settled.sort((a, b) => decided(a) - decided(b));\n const drop = new Set(settled.slice(0, Math.max(0, settled.length - kept)));\n return items.filter((item) => !drop.has(item));\n}\n\nexport type Settle = { state: Exclude<ApprovalState, 'pending'>; by: DecidedBy; reason?: string };\n\nexport type Ruling = { error?: string; settle?: Settle; answer?: 'once' | 'always' };\n\nconst why = (reason: string | undefined) => (reason ? { reason } : {});\n\nexport function ruled(\n item: Approval,\n answer: unknown,\n reason: unknown,\n by: DecidedBy,\n at: number\n): Ruling {\n if (item.state !== 'pending') return { error: gone(item.state) };\n if (item.expiresAt <= at)\n return {\n error: gone('unanswered'),\n settle: { state: 'unanswered', by: 'host', reason: REASONS.timeout },\n };\n if (!answerer(by, item.tier)) return { error: \"This request isn't yours to answer.\" };\n const known = answerOf(answer);\n if (known === 'always' && !item.always)\n return { error: \"Always isn't offered for this request.\" };\n if (!known) return { settle: { state: 'rejected', by, reason: REASONS.unknownAnswer } };\n const given = why(cleanReason(reason));\n if (known === 'deny') return { settle: { state: 'rejected', by, ...given } };\n return { settle: { state: 'approved', by, ...given }, answer: known };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAwEnB,MAAM,eAAe,UAA0B;AAAA,EACpD,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,OAAO,CAAC,EAAE;AAC9B,CAAC;AAEM,MAAM,cAAc;AACpB,MAAM,iBAAiB;AACvB,MAAM,YAAY;AAClB,MAAM,gBAAgB;AAEtB,MAAM,UAAU;AAAA,EACrB,SAAS;AAAA,EACT,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,SAAS;AAAA,EACT,SAAS;AACX;AAEO,MAAM,YAAY,CAAC,SAAgC,SAAS;AAE5D,SAAS,UAAU,MAAqC;AAC7D,MAAI,SAAS,UAAU,SAAS,WAAW,SAAS,OAAQ,QAAO;AACnE,SAAO,OAAO,SAAS,YAAY,cAAc,KAAK,IAAI;AAC5D;AAEO,SAAS,SAAS,MAAoB,MAA6B;AACxE,SAAO,EAAE,SAAS,gBAAgB,SAAS;AAC7C;AAEO,SAAS,SAAS,OAAuC;AAC9D,SAAO,UAAU,UAAU,UAAU,YAAY,UAAU,SAAS,QAAQ;AAC9E;AAEO,MAAM,WAAW,CAAC,OAA2B,OAAO,WAAW,OAAO;AAEtE,SAAS,SAAS,IAAe,MAAuB;AAC7D,MAAI,SAAS,EAAE,EAAG,QAAO,SAAS;AAClC,MAAI,OAAO,QAAS,QAAO,SAAS;AACpC,SAAO;AACT;AAEO,SAAS,KAAK,OAA8B;AACjD,MAAI,UAAU,aAAc,QAAO;AACnC,MAAI,UAAU,YAAa,QAAO;AAClC,SAAO;AACT;AAEO,SAAS,YAAY,QAAqC;AAC/D,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,QAAM,OAAO,OAAO,KAAK,EAAE,MAAM,GAAG,aAAa;AACjD,SAAO,QAAQ;AACjB;AAEA,MAAM,UAAU,CAAC,SAAmB,KAAK,aAAa,KAAK;AAEpD,SAAS,MAAM,OAAmB,OAAO,gBAA4B;AAC1E,QAAM,UAAU,MAAM,OAAO,CAAC,SAAS,KAAK,UAAU,SAAS;AAC/D,UAAQ,KAAK,CAAC,GAAG,MAAM,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC;AAC9C,QAAM,OAAO,IAAI,IAAI,QAAQ,MAAM,GAAG,KAAK,IAAI,GAAG,QAAQ,SAAS,IAAI,CAAC,CAAC;AACzE,SAAO,MAAM,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC;AAC/C;AAMA,MAAM,MAAM,CAAC,WAAgC,SAAS,EAAE,OAAO,IAAI,CAAC;AAE7D,SAAS,MACd,MACA,QACA,QACA,IACA,IACQ;AACR,MAAI,KAAK,UAAU,UAAW,QAAO,EAAE,OAAO,KAAK,KAAK,KAAK,EAAE;AAC/D,MAAI,KAAK,aAAa;AACpB,WAAO;AAAA,MACL,OAAO,KAAK,YAAY;AAAA,MACxB,QAAQ,EAAE,OAAO,cAAc,IAAI,QAAQ,QAAQ,QAAQ,QAAQ;AAAA,IACrE;AACF,MAAI,CAAC,SAAS,IAAI,KAAK,IAAI,EAAG,QAAO,EAAE,OAAO,sCAAsC;AACpF,QAAM,QAAQ,SAAS,MAAM;AAC7B,MAAI,UAAU,YAAY,CAAC,KAAK;AAC9B,WAAO,EAAE,OAAO,yCAAyC;AAC3D,MAAI,CAAC,MAAO,QAAO,EAAE,QAAQ,EAAE,OAAO,YAAY,IAAI,QAAQ,QAAQ,cAAc,EAAE;AACtF,QAAM,QAAQ,IAAI,YAAY,MAAM,CAAC;AACrC,MAAI,UAAU,OAAQ,QAAO,EAAE,QAAQ,EAAE,OAAO,YAAY,IAAI,GAAG,MAAM,EAAE;AAC3E,SAAO,EAAE,QAAQ,EAAE,OAAO,YAAY,IAAI,GAAG,MAAM,GAAG,QAAQ,MAAM;AACtE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/client.js
CHANGED
|
@@ -122,6 +122,7 @@ async function connectAgent(endpoint, options = {}) {
|
|
|
122
122
|
});
|
|
123
123
|
return {
|
|
124
124
|
serverId: peer.serverId,
|
|
125
|
+
readOnly: controlHandle.kind === "view",
|
|
125
126
|
control,
|
|
126
127
|
transcript: hold(state("state")),
|
|
127
128
|
deliveries: hold(state("deliveries")),
|
|
@@ -137,6 +138,7 @@ async function connectAgent(endpoint, options = {}) {
|
|
|
137
138
|
monitor: hold(state("monitor")),
|
|
138
139
|
vault: hold(state("vault")),
|
|
139
140
|
labels: hold(state("labels")),
|
|
141
|
+
approvals: hold(state("approvals")),
|
|
140
142
|
settings: lastingSettings(settings, hold),
|
|
141
143
|
remote,
|
|
142
144
|
pages,
|
package/dist/client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n type Context,\n createHost,\n isChordError,\n type Readable,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.ts';\nimport { mismatched } from './mismatch.ts';\nimport { controlAnswer, pageAnswer, remoteAnswer, settingsAnswer } from './readonly.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport type { VaultState } from './secrets/vault.ts';\nimport {\n AgentControl,\n AgentPagesService,\n AgentRemoteService,\n AgentSettings,\n type AgentsSummary,\n AgentTranscript,\n CONTROL_METHODS,\n type Command,\n type Delivered,\n type MonitorFeed,\n PAGE_METHODS,\n type PrivateRevision,\n REMOTE_METHODS,\n SETTINGS_METHODS,\n type SendMode,\n type SettingsState,\n TRANSCRIPT_STATES,\n} from './services.ts';\nimport { AGENT_HOST, bound, kept, type Remote, type Root, replicated } from './serving.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { greeted, LINK_PROTOCOL, type PortEndpoint, PortLink } from './wire.ts';\n\nexport interface AgentConnection {\n readonly serverId: string;\n readonly control: AgentControl;\n readonly transcript: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents?: ReplicatedState<AgentsSummary>;\n readonly views?: ReplicatedState<Record<string, ConversationView>>;\n readonly commands?: ReplicatedState<Command[]>;\n readonly privateRevision?: ReplicatedState<PrivateRevision>;\n readonly sprinkles?: ReplicatedState<Sprinkle[]>;\n readonly memories?: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes?: ReplicatedState<MemoryScope[]>;\n readonly frozen?: ReplicatedState<FrozenCone[]>;\n readonly changes?: ReplicatedState<ChangesView>;\n readonly monitor?: ReplicatedState<MonitorFeed>;\n readonly vault?: ReplicatedState<VaultState>;\n readonly labels?: ReplicatedState<Record<string, string>>;\n readonly settings: AgentSettings;\n readonly remote?: AgentRemote;\n readonly pages?: AgentPages;\n readonly closed: Promise<Error | undefined>;\n readonly mismatch?: string;\n prompt(text: string, whenBusy?: SendMode): Promise<string>;\n webhook(\n name: string,\n delivery: { id?: string | null; headers?: Record<string, string>; body: string }\n ): Promise<{ delivered: boolean }>;\n close(): Promise<void>;\n}\n\nexport interface Lasting<T> {\n readonly state: ReplicatedState<T>;\n freeze(): void;\n}\n\nexport function lasting<T>(state: ReplicatedState<T>): Lasting<T> {\n let last: T | undefined;\n let frozen = false;\n const read = () => {\n if (frozen) return last;\n try {\n last = state.value;\n } catch {}\n return last;\n };\n return {\n state: {\n get value() {\n return read();\n },\n subscribe: (listener) => (frozen ? () => undefined : state.subscribe(listener)),\n },\n freeze() {\n read();\n frozen = true;\n },\n };\n}\n\nfunction lastingSettings(\n settings: AgentSettings,\n hold: <T>(state: ReplicatedState<T>) => ReplicatedState<T>\n): AgentSettings {\n return {\n state: hold(settings.state),\n connect: (providerId, secret, fields, context) =>\n settings.connect(providerId, secret, fields, context),\n disconnect: (providerId, context) => settings.disconnect(providerId, context),\n signIn: (providerId, context) => settings.signIn(providerId, context),\n dismissMoved: (context) => settings.dismissMoved(context),\n };\n}\n\nconst MISMATCHES = new Set(['contract_mismatch', 'upgrade_required']);\n\nconst ended = (link: PortLink): Promise<never> =>\n link.ended.then((reason) => {\n throw reason ?? new Error('the agent closed the link');\n });\n\nexport async function connectAgent(\n endpoint: PortEndpoint,\n options: { context?: Context } = {}\n): Promise<AgentConnection> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const link = new PortLink(endpoint);\n const peer = greeted(await link.hello);\n if (peer.protocol !== LINK_PROTOCOL) {\n link.close('link protocol mismatch');\n return mismatched(peer.serverId, peer.protocol);\n }\n const host = createHost({ name: `follower:${crypto.randomUUID()}`, kind: 'client' });\n host.connect(link);\n const get = (contract: Parameters<typeof host.get>[0]) =>\n host.get(contract, {}, { at: AGENT_HOST }) as unknown as Remote;\n const handles = [\n AgentControl,\n AgentTranscript,\n AgentSettings,\n AgentRemoteService,\n AgentPagesService,\n ].map(get);\n const [controlHandle, transcriptHandle, settingsHandle, remoteHandle, pagesHandle] = handles as [\n Remote,\n Remote,\n Remote,\n Remote,\n Remote,\n ];\n const release = () => {\n for (const handle of handles) handle.release();\n };\n try {\n await Promise.race([Promise.all(handles.map((handle) => handle.ready)), ended(link)]);\n } catch (error) {\n release();\n link.close('handshake failed');\n if (isChordError(error) && MISMATCHES.has(error.code))\n return mismatched(peer.serverId, peer.protocol);\n throw error;\n }\n const root: Root = (run, rootOptions) => host.root(run, rootOptions);\n const control = bound<AgentControl>(controlHandle, root, CONTROL_METHODS, controlAnswer);\n const members = Object.fromEntries(\n TRANSCRIPT_STATES.map((name) => [name, transcriptHandle[name] as Readable<unknown>])\n ) as Record<(typeof TRANSCRIPT_STATES)[number], Readable<unknown>>;\n const settingsState = settingsHandle.state as Readable<unknown>;\n const keeping = [...Object.values(members), settingsState].map(kept);\n await Promise.race([Promise.all(keeping.map((keep) => keep.ready)), ended(link)]);\n const settings: AgentSettings = {\n ...bound<Omit<AgentSettings, 'state'>>(settingsHandle, root, SETTINGS_METHODS, settingsAnswer),\n state: replicated(settingsState as Readable<SettingsState | undefined>),\n };\n const remote = bound<AgentRemote>(remoteHandle, root, REMOTE_METHODS, remoteAnswer);\n const pages = bound<AgentPages>(pagesHandle, root, PAGE_METHODS, pageAnswer);\n const state = <K extends keyof typeof members>(name: K) =>\n replicated(members[name] as Readable<never>);\n const held: Lasting<unknown>[] = [];\n const hold = <T>(state: ReplicatedState<T>): ReplicatedState<T> => {\n const kept = lasting(state);\n held.push(kept as Lasting<unknown>);\n return kept.state;\n };\n let open = true;\n const finish = () => {\n if (!open) return;\n open = false;\n for (const kept of held) kept.freeze();\n for (const keep of keeping) keep.stop();\n release();\n };\n const closed = link.ended.then((reason) => {\n finish();\n return reason;\n });\n return {\n serverId: peer.serverId,\n control,\n transcript: hold<ConversationView>(state('state')),\n deliveries: hold<Record<string, Delivered>>(state('deliveries')),\n agents: hold<AgentsSummary>(state('agents')),\n views: hold<Record<string, ConversationView>>(state('views')),\n commands: hold<Command[]>(state('commands')),\n privateRevision: hold<PrivateRevision>(state('privateRevision')),\n sprinkles: hold<Sprinkle[]>(state('sprinkles')),\n memories: hold<MemoryEntry[]>(state('memories')),\n memoryScopes: hold<MemoryScope[]>(state('memoryScopes')),\n frozen: hold<FrozenCone[]>(state('frozen')),\n changes: hold<ChangesView>(state('changes')),\n monitor: hold<MonitorFeed>(state('monitor')),\n vault: hold<VaultState>(state('vault')),\n labels: hold<Record<string, string>>(state('labels')),\n settings: lastingSettings(settings, hold),\n remote,\n pages,\n closed,\n webhook(name, delivery) {\n return control.webhook(\n name,\n { id: delivery.id ?? null, headers: delivery.headers ?? {}, body: delivery.body },\n context\n );\n },\n async prompt(text, whenBusy = 'followUp') {\n const sent = await control.send({ text, whenBusy, requestId: null }, context);\n if (!sent.accepted) throw new Error(`${sent.error.code}: ${sent.error.message}`);\n const settled = await control.wait(sent.submissionId, context);\n if (settled.status !== 'done') throw new Error(`unanswered: ${settled.reason}`);\n return settled.text;\n },\n async close() {\n finish();\n link.close('closed');\n await closed;\n await host.close();\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "AAAA;AAAA,EAEE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,0BAA0B;
|
|
4
|
+
"sourcesContent": ["import {\n type Context,\n createHost,\n isChordError,\n type Readable,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { Approval } from './approvals/record.ts';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.ts';\nimport { mismatched } from './mismatch.ts';\nimport { controlAnswer, pageAnswer, remoteAnswer, settingsAnswer } from './readonly.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport type { VaultState } from './secrets/vault.ts';\nimport {\n AgentControl,\n AgentPagesService,\n AgentRemoteService,\n AgentSettings,\n type AgentsSummary,\n AgentTranscript,\n CONTROL_METHODS,\n type Command,\n type Delivered,\n type MonitorFeed,\n PAGE_METHODS,\n type PrivateRevision,\n REMOTE_METHODS,\n SETTINGS_METHODS,\n type SendMode,\n type SettingsState,\n TRANSCRIPT_STATES,\n} from './services.ts';\nimport { AGENT_HOST, bound, kept, type Remote, type Root, replicated } from './serving.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\nimport { greeted, LINK_PROTOCOL, type PortEndpoint, PortLink } from './wire.ts';\n\nexport interface AgentConnection {\n readonly serverId: string;\n readonly control: AgentControl;\n readonly transcript: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents?: ReplicatedState<AgentsSummary>;\n readonly views?: ReplicatedState<Record<string, ConversationView>>;\n readonly commands?: ReplicatedState<Command[]>;\n readonly privateRevision?: ReplicatedState<PrivateRevision>;\n readonly sprinkles?: ReplicatedState<Sprinkle[]>;\n readonly memories?: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes?: ReplicatedState<MemoryScope[]>;\n readonly frozen?: ReplicatedState<FrozenCone[]>;\n readonly changes?: ReplicatedState<ChangesView>;\n readonly monitor?: ReplicatedState<MonitorFeed>;\n readonly vault?: ReplicatedState<VaultState>;\n readonly labels?: ReplicatedState<Record<string, string>>;\n readonly approvals?: ReplicatedState<Approval[]>;\n readonly settings: AgentSettings;\n readonly remote?: AgentRemote;\n readonly pages?: AgentPages;\n readonly closed: Promise<Error | undefined>;\n readonly mismatch?: string;\n readonly readOnly?: boolean;\n prompt(text: string, whenBusy?: SendMode): Promise<string>;\n webhook(\n name: string,\n delivery: { id?: string | null; headers?: Record<string, string>; body: string }\n ): Promise<{ delivered: boolean }>;\n close(): Promise<void>;\n}\n\nexport interface Lasting<T> {\n readonly state: ReplicatedState<T>;\n freeze(): void;\n}\n\nexport function lasting<T>(state: ReplicatedState<T>): Lasting<T> {\n let last: T | undefined;\n let frozen = false;\n const read = () => {\n if (frozen) return last;\n try {\n last = state.value;\n } catch {}\n return last;\n };\n return {\n state: {\n get value() {\n return read();\n },\n subscribe: (listener) => (frozen ? () => undefined : state.subscribe(listener)),\n },\n freeze() {\n read();\n frozen = true;\n },\n };\n}\n\nfunction lastingSettings(\n settings: AgentSettings,\n hold: <T>(state: ReplicatedState<T>) => ReplicatedState<T>\n): AgentSettings {\n return {\n state: hold(settings.state),\n connect: (providerId, secret, fields, context) =>\n settings.connect(providerId, secret, fields, context),\n disconnect: (providerId, context) => settings.disconnect(providerId, context),\n signIn: (providerId, context) => settings.signIn(providerId, context),\n dismissMoved: (context) => settings.dismissMoved(context),\n };\n}\n\nconst MISMATCHES = new Set(['contract_mismatch', 'upgrade_required']);\n\nconst ended = (link: PortLink): Promise<never> =>\n link.ended.then((reason) => {\n throw reason ?? new Error('the agent closed the link');\n });\n\nexport async function connectAgent(\n endpoint: PortEndpoint,\n options: { context?: Context } = {}\n): Promise<AgentConnection> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const link = new PortLink(endpoint);\n const peer = greeted(await link.hello);\n if (peer.protocol !== LINK_PROTOCOL) {\n link.close('link protocol mismatch');\n return mismatched(peer.serverId, peer.protocol);\n }\n const host = createHost({ name: `follower:${crypto.randomUUID()}`, kind: 'client' });\n host.connect(link);\n const get = (contract: Parameters<typeof host.get>[0]) =>\n host.get(contract, {}, { at: AGENT_HOST }) as unknown as Remote;\n const handles = [\n AgentControl,\n AgentTranscript,\n AgentSettings,\n AgentRemoteService,\n AgentPagesService,\n ].map(get);\n const [controlHandle, transcriptHandle, settingsHandle, remoteHandle, pagesHandle] = handles as [\n Remote,\n Remote,\n Remote,\n Remote,\n Remote,\n ];\n const release = () => {\n for (const handle of handles) handle.release();\n };\n try {\n await Promise.race([Promise.all(handles.map((handle) => handle.ready)), ended(link)]);\n } catch (error) {\n release();\n link.close('handshake failed');\n if (isChordError(error) && MISMATCHES.has(error.code))\n return mismatched(peer.serverId, peer.protocol);\n throw error;\n }\n const root: Root = (run, rootOptions) => host.root(run, rootOptions);\n const control = bound<AgentControl>(controlHandle, root, CONTROL_METHODS, controlAnswer);\n const members = Object.fromEntries(\n TRANSCRIPT_STATES.map((name) => [name, transcriptHandle[name] as Readable<unknown>])\n ) as Record<(typeof TRANSCRIPT_STATES)[number], Readable<unknown>>;\n const settingsState = settingsHandle.state as Readable<unknown>;\n const keeping = [...Object.values(members), settingsState].map(kept);\n await Promise.race([Promise.all(keeping.map((keep) => keep.ready)), ended(link)]);\n const settings: AgentSettings = {\n ...bound<Omit<AgentSettings, 'state'>>(settingsHandle, root, SETTINGS_METHODS, settingsAnswer),\n state: replicated(settingsState as Readable<SettingsState | undefined>),\n };\n const remote = bound<AgentRemote>(remoteHandle, root, REMOTE_METHODS, remoteAnswer);\n const pages = bound<AgentPages>(pagesHandle, root, PAGE_METHODS, pageAnswer);\n const state = <K extends keyof typeof members>(name: K) =>\n replicated(members[name] as Readable<never>);\n const held: Lasting<unknown>[] = [];\n const hold = <T>(state: ReplicatedState<T>): ReplicatedState<T> => {\n const kept = lasting(state);\n held.push(kept as Lasting<unknown>);\n return kept.state;\n };\n let open = true;\n const finish = () => {\n if (!open) return;\n open = false;\n for (const kept of held) kept.freeze();\n for (const keep of keeping) keep.stop();\n release();\n };\n const closed = link.ended.then((reason) => {\n finish();\n return reason;\n });\n return {\n serverId: peer.serverId,\n readOnly: controlHandle.kind === 'view',\n control,\n transcript: hold<ConversationView>(state('state')),\n deliveries: hold<Record<string, Delivered>>(state('deliveries')),\n agents: hold<AgentsSummary>(state('agents')),\n views: hold<Record<string, ConversationView>>(state('views')),\n commands: hold<Command[]>(state('commands')),\n privateRevision: hold<PrivateRevision>(state('privateRevision')),\n sprinkles: hold<Sprinkle[]>(state('sprinkles')),\n memories: hold<MemoryEntry[]>(state('memories')),\n memoryScopes: hold<MemoryScope[]>(state('memoryScopes')),\n frozen: hold<FrozenCone[]>(state('frozen')),\n changes: hold<ChangesView>(state('changes')),\n monitor: hold<MonitorFeed>(state('monitor')),\n vault: hold<VaultState>(state('vault')),\n labels: hold<Record<string, string>>(state('labels')),\n approvals: hold<Approval[]>(state('approvals')),\n settings: lastingSettings(settings, hold),\n remote,\n pages,\n closed,\n webhook(name, delivery) {\n return control.webhook(\n name,\n { id: delivery.id ?? null, headers: delivery.headers ?? {}, body: delivery.body },\n context\n );\n },\n async prompt(text, whenBusy = 'followUp') {\n const sent = await control.send({ text, whenBusy, requestId: null }, context);\n if (!sent.accepted) throw new Error(`${sent.error.code}: ${sent.error.message}`);\n const settled = await control.wait(sent.submissionId, context);\n if (settled.status !== 'done') throw new Error(`unanswered: ${settled.reason}`);\n return settled.text;\n },\n async close() {\n finish();\n link.close('closed');\n await closed;\n await host.close();\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["state", "kept"]
|
|
7
7
|
}
|
package/dist/control.js
CHANGED
|
@@ -19,6 +19,17 @@ function created(run) {
|
|
|
19
19
|
(error) => ({ id: null, error: error.message })
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
|
+
function monitorControl(monitor) {
|
|
23
|
+
return {
|
|
24
|
+
monitorOpen: async (lease, resync, context) => monitor ? monitor.open(lease, resync, context) : { lease: "", ttlMs: 0 },
|
|
25
|
+
monitorClose: async (lease, context) => monitor?.close(lease, context)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const noApprovals = async () => ({
|
|
29
|
+
done: false,
|
|
30
|
+
state: null,
|
|
31
|
+
error: "This agent has no approvals."
|
|
32
|
+
});
|
|
22
33
|
function freezerControl(freezer) {
|
|
23
34
|
const needed = () => {
|
|
24
35
|
if (!freezer) throw new Error("This agent has no freezer.");
|
|
@@ -290,8 +301,7 @@ function createAgentControl(harness, target, deliveries, licks, scoops, skills,
|
|
|
290
301
|
...memoryControl(memory),
|
|
291
302
|
...freezerControl(freezer),
|
|
292
303
|
...changesControl(changes),
|
|
293
|
-
|
|
294
|
-
monitorClose: async (lease, context) => monitor?.close(lease, context),
|
|
304
|
+
...monitorControl(monitor),
|
|
295
305
|
image: (ref, context) => storedImage(harness, ref, context),
|
|
296
306
|
async unqueue(agentId, id, context) {
|
|
297
307
|
return withdraw(await conversationFor(agentId, context), id, context);
|
|
@@ -358,7 +368,8 @@ function createAgentControl(harness, target, deliveries, licks, scoops, skills,
|
|
|
358
368
|
async agentCommands(agentId, _context) {
|
|
359
369
|
return skills?.commandsFor(agentId) ?? [];
|
|
360
370
|
},
|
|
361
|
-
toolLabel: async (agentId, callIds, context) => labels ? labels.label(agentId, callIds, context) : null
|
|
371
|
+
toolLabel: async (agentId, callIds, context) => labels ? labels.label(agentId, callIds, context) : null,
|
|
372
|
+
decide: noApprovals
|
|
362
373
|
};
|
|
363
374
|
}
|
|
364
375
|
export {
|