@ai-ecoverse/slicc-agent 5.4.1 → 5.5.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.
Files changed (43) hide show
  1. package/README.md +26 -8
  2. package/dist/approvals/index.js +2 -1
  3. package/dist/approvals/index.js.map +2 -2
  4. package/dist/approvals/policy.js +110 -0
  5. package/dist/approvals/policy.js.map +7 -0
  6. package/dist/client.js +1 -0
  7. package/dist/client.js.map +2 -2
  8. package/dist/control.js.map +2 -2
  9. package/dist/guests/client.js +6 -3
  10. package/dist/guests/client.js.map +2 -2
  11. package/dist/guests/index.js +4 -1
  12. package/dist/guests/index.js.map +2 -2
  13. package/dist/guests/policy.js +75 -0
  14. package/dist/guests/policy.js.map +7 -0
  15. package/dist/guests/seats.js +16 -6
  16. package/dist/guests/seats.js.map +2 -2
  17. package/dist/guests/serve.js +8 -2
  18. package/dist/guests/serve.js.map +2 -2
  19. package/dist/host.js +35 -28
  20. package/dist/host.js.map +2 -2
  21. package/dist/index.js +4 -0
  22. package/dist/index.js.map +2 -2
  23. package/dist/services.js +12 -3
  24. package/dist/services.js.map +2 -2
  25. package/dist/spectrum/approvals-port.js +2 -2
  26. package/dist/spectrum/approvals-port.js.map +2 -2
  27. package/dist/spectrum/guest-port.js +14 -4
  28. package/dist/spectrum/guest-port.js.map +2 -2
  29. package/dist/spectrum/index.js +3 -1
  30. package/dist/spectrum/index.js.map +2 -2
  31. package/dist/types/approvals/index.d.ts +2 -1
  32. package/dist/types/approvals/policy.d.ts +38 -0
  33. package/dist/types/client.d.ts +2 -0
  34. package/dist/types/control.d.ts +1 -1
  35. package/dist/types/guests/index.d.ts +4 -0
  36. package/dist/types/guests/policy.d.ts +23 -0
  37. package/dist/types/guests/seats.d.ts +2 -1
  38. package/dist/types/host.d.ts +1 -3
  39. package/dist/types/index.d.ts +2 -0
  40. package/dist/types/services.d.ts +11 -3
  41. package/dist/types/spectrum/guest-port.d.ts +8 -3
  42. package/dist/types/spectrum/index.d.ts +1 -1
  43. package/package.json +2 -2
package/README.md CHANGED
@@ -32,7 +32,7 @@ await agent.prompt('What is in /home?');
32
32
  - `@ai-ecoverse/slicc-agent/page` has what the page needs (`startAgent`, `connectAgent`) without the agent's runtime: no pi-durable, pi-ai, providers or SQLite, which load only in the worker. `@ai-ecoverse/slicc-agent/spectrum` is just as light, and a test keeps both that way.
33
33
  - `startAgent({ worker })` takes the Web Lock `slicc-agent`, so one tab at a time owns the agent worker, and waits while another tab holds it. `connect()` returns an `AgentConnection` (`control`, `transcript`, `prompt()`, and `webhook(name, { id?, headers?, body })`, which hands a webhook delivery, such as one from the tray hub, to the licks without a chord `Context`), `restart()` replaces the worker (durable resumes from SQLite), and `release()` stops it and lets the next tab take over. `port()` returns a raw `MessagePort` into the worker instead, for another tab: hand it over (through a SharedWorker, say), and that tab calls `connectAgent(port)`. A caller that already holds the lock passes `held: true`. A connection's `closed` resolves when its port ends (with the error, if any), and from then on its states keep their last values.
34
34
  - **Followers over a link** (PR 18): `port({ readOnly: true })` and `connect({ readOnly: true })` give a view-only connection, and `port({ pages: false })` a full one that never takes `panel` requests (see Panels).
35
- - **What a view-only connection can do:** it sees the transcript, the agents, settings state and remote-control `watch`, and may call `wait` and `changesOpen`.
35
+ - **What a view-only connection can do:** it sees the transcript, the agents, settings state and remote-control `watch`, and may call `wait` and `changesOpen`. It's still an owner device, so it has the owner-device methods (`OWNER_DEVICE_METHODS` in [`src/services.ts`](src/services.ts)) with the owner's powers: `decide`, `guestOpen`, `guestRevoke` and `approvalPolicySet`. Control gates driving, not approvals.
36
36
  - **What it can't:** every other `AgentControl` method, settings action and remote-control `prompt` or `abort` is refused with `EACCES` ("this follower can watch, but not send, steer or abort"). Send and compact answer `accepted: false` with that code. The `view` seat's variants don't have those methods at all (`view` for control and settings, `watch` for remote control, `none` for Panels); the follower's client answers the missing ones with the refusals ([`src/readonly.ts`](src/readonly.ts)), so the worker never runs them. A `watch` stream is readable only over the link that opened it.
37
37
  - **Carrying the connection over a byte stream:** `linkEndpoint({ write, close })` is a `PortEndpoint` that `connectAgent` accepts. Feed it the stream's bytes with `receive(bytes)` and its end with `end(error?)`. On the owner's side, `bridgePort(port, { write, close })` does the same for the `MessagePort` from `port()`.
38
38
  - **The bytes:** the owner first posts a hello, its server id, then each Chord frame travels as its JSON text in UTF-8 (`PortLink` in [`src/wire.ts`](src/wire.ts)), and `{ close: true }` ends the port. On a byte stream each message becomes a frame: a `u32` big-endian length, then a tag (0 data, 1 hello, 2 close), then the payload. A frame is at most 16 MiB, and a malformed one ends the link.
@@ -606,11 +606,11 @@ Every approval, of a guest's prompt, of a tool call in a run a guest started, or
606
606
  - **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? }`.
607
607
  - **`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`, `revoked` or `aborted`. `decidedBy` is `owner`, `follower`, `judge` or `host`.
608
608
  - **`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.
609
- - **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.
609
+ - **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 unless the approval policy's `guests.always` offers it for that kind (by default it doesn't), and `decide(id, 'always')` on an item without it is refused and leaves it pending. What Always then does for a guest is for #253 and #254; for now it approves once.
610
610
  - **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.
611
611
  - **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.
612
- - **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`.
613
- - **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`.
612
+ - **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`.
613
+ - **In spectrum** (1.80 on, which shows guest approvals in `<slicc-approvals>` without Always, 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`. `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.
614
614
  - **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.
615
615
 
616
616
  #### The automatic judge
@@ -622,10 +622,27 @@ An `agent` item is answered by the judge ([`src/approvals/judge.ts`](src/approva
622
622
  - **The owner's intent:** the policy file `/etc/slicc/APPROVALS.md`, the owner's last 10 messages in the item's cone, and the global and cone memory. The worker seeds the policy when it's missing, owned by root with mode 644, so it's written without a prompt and a kernel user can change it only with sudo. It's read again for every request. The owner's messages are the cone's `pi.user` entries without a `slicc.from` note and that aren't licks, so a guest's `slicc.guest-input` never counts.
623
623
  - **The guest's text** is only in `request.untrusted`, inside a fence with a new nonce each call. Any copy of the fence in the text is removed.
624
624
  - **The model** is `approvals.judgeModel` in the agent's `settings.json` (`/etc/slicc/agent/settings.json` with kernel users), `adobe/clef` by default, never the cone's model.
625
- - **Recording:** `decidedBy` is `judge`, and `verdict` on the item is `{ decision, reason, risk, aligned, model }`, where `risk` is P(high). `decision` is `allow` or `deny` for a confident answer, and `unsure` for one in between, which goes to a person with its reason. A confident verdict that lands after the item timed out stays on it. A model-less `slicc.approval-verdict` entry in the item's conversation records each outcome (`approved`, `rejected`, `escalated` or `unanswered`), with the error when the judge failed.
625
+ - **Recording:** `decidedBy` is `judge`, and `verdict` on the item is `{ decision, reason, risk, aligned, model }`, where `risk` is P(high). `decision` is `allow` or `deny` for a confident answer, and `unsure` for one in between, which goes to a person with its reason; spectrum 1.80 shows it as "The automatic check wasn't sure: …". A confident verdict that lands after the item timed out stays on it. A model-less `slicc.approval-verdict` entry in the item's conversation records each outcome (`approved`, `rejected`, `escalated` or `unanswered`), with the error when the judge failed.
626
626
  - **Abort:** the judge runs with the request context's signal. When the request is aborted, the item is withdrawn (`aborted`) and a late verdict is dropped. After a reload, a pending `agent` item goes to a person.
627
627
  - **Evals:** [`evals/judge.json`](evals/judge.json) holds guest prompts and tool calls with their expected verdicts. `SLICC_ADOBE_IMS_TOKEN=… npm run eval:judge -- --model adobe/clef --model adobe/clef-flash` runs them against the real classifiers; run it before changing the thresholds, questions or default policy. `--record` saves the probabilities it saw, and CI replays those through the approvals surface.
628
628
 
629
+ ### Approval policy
630
+
631
+ All the limits on approvals and guests live in one owner-changeable policy, the session document `slicc.approval-policy` ([`src/approvals/policy.ts`](src/approvals/policy.ts)). It's structured JSON, apart from the judge's free-text `/etc/slicc/APPROVALS.md`.
632
+
633
+ - **Defaults:**
634
+ - `guests.manageGuests: 'deny'`, typed `'deny' | <tier>`. Only `deny` is accepted for now, because a guest has no way to manage guests yet.
635
+ - `guests.allowedToolGates: ['user', 'agent', 'scoop']`.
636
+ - `guests.allowedMessageGates: ['user', 'agent', 'cone', 'scoop', 'off']`.
637
+ - `guests.maxSeatDays: 30` (1 to 365).
638
+ - `guests.always: { messages: false, tools: false }`.
639
+ - `sudo.approver: 'user'` (`'agent'` lets the judge decide sudo).
640
+ - **Reading it:** `AgentTranscript.approvalPolicy` replicates it to every owner device.
641
+ - **Changing it:** `AgentControl.approvalPolicySet({ guests?, sudo? })` changes the given entries. It's for owner devices only (the owner's pages, and followers with or without Control), never guests. It validates the change and answers `{ policy, error }`. Only the entries an owner device set are stored, as overrides read over the defaults, so a later default still reaches every entry nobody set.
642
+ - **Where it applies:** gates and expiry are checked when a seat opens, so a seat keeps its gates when the policy changes later.
643
+ - **What it doesn't open:** the guest contract stays closed to everything but its own seat (`closedToGuests`). That's the default-deny base a later policy entry may open.
644
+ - **Not policy:** the guest guard (`slicc-guest-guard`, until #254) and the technical bounds (label 64, proposal 32,000 characters).
645
+
629
646
  ### Remote control
630
647
 
631
648
  `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.
@@ -642,15 +659,16 @@ An `agent` item is answered by the judge ([`src/approvals/judge.ts`](src/approva
642
659
 
643
660
  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.
644
661
 
645
- - **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 }`), and the URL is returned once and never kept. `unavailable()` says "Inviting guests needs the tray hub." without a hub, and "Only a tab with Control can invite guests." on a view-only follower or a guest.
646
- - **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 30 days), the two gates (`user`, `agent`, `cone`, `scoop:<name>` or `off`, fixed when the seat is opened; the tools gate can't be `off` or `cone`, from any caller, so a guest's run never uses tools unreviewed: "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 30 days after they end.
662
+ - **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 }`), 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()` lists the gates the approval policy allows for messages and tools, in display order, with `scoop:*` for "any scoop", for the invite dialog.
663
+ - **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.
647
664
  - **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.
648
665
  - **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.
649
666
  - **The guest's page** calls `connectGuest(endpoint)` from `@ai-ecoverse/slicc-agent/page`, which resolves an `AgentConnection` for that one cone with `guest` (the seat as the guest sees it); `connectAgent` on a guest port is forbidden. `createAgentModel(guest).agent` shows the transcript and sends through `propose`; every other control method, settings action, `prompt` and `webhook` rejects with `EACCES` ("a guest can only watch and send messages"). A port that isn't a valid seat rejects with `EACCES` "This guest link isn't valid any more."
650
667
  - **Messages.** With the messages gate `off`, a guest's message is submitted to the cone as a follow-up placed as a `slicc.guest-input` entry (`{ seat, name, approval: null }`), and the model reads it after the line "[A guest message from "<label>", shared through a guest seat, NOT from the cone's owner. …]". The spectrum adapter shows it as a user message with `origin: 'guest'` and `from` the label, without that line, queued or placed. Under any other gate it waits as a pending `guest-message` approval (tier = the gate, requester from the seat), which times out like other approvals. The guest contract's `proposals` state lists the seat's own, and the guest's adapter shows a pending one as its queued message and an ended one as a notice: "Nobody approved this in time.", "It was declined." or "It was withdrawn." Once approved it is delivered exactly once (request id `guest:<seat>:<approval>`, and the seat record keeps the ids it delivered) as the same entry with `approval` set; a seat revoked or expired by then gets nothing. #253 makes the delivery durable across a crash between approval and submit, and adds the review UX.
651
668
  - **Tool calls in guest runs are refused.** Until #254 gates them by the seat's tools tier, `slicc-guest-guard`, an extension `openAgent` installs in every registry (and that conversations with a pinned extension list get back, like `slicc-secrets`), blocks every tool call in a run whose inputs include one a guest placed (its request id starts `guest:`), nested and codemode calls included: "Tool calls in a run a guest started need a review, which this agent can't do yet." The owner's own runs in the same cone use tools as before.
652
669
  - **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.
653
- - **Owner-only.** Only the owner's page may open or revoke seats (`GUEST_MANAGERS` in `src/host.ts`); a follower with Control is refused (`forbidden`, "only the owner can invite or revoke guests"), and its `guests.unavailable()` says "Only the owner's tab can invite guests."
670
+ - **Every owner device manages guests.** The owner's pages and followers, with or without Control, open, list and revoke seats alike.
671
+ - **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.
654
672
 
655
673
  ### Credentials
656
674
 
@@ -50,6 +50,7 @@ const judgedIn = (id, judgement, now) => (doc) => {
50
50
  }
51
51
  return copy([item])[0];
52
52
  };
53
+ const offered = (options, draft) => guestKind(draft.kind) ? options.guestAlways?.(draft.kind) === true : draft.always === true;
53
54
  async function attachApprovals(options, context = BACKGROUND_CONTEXT) {
54
55
  const { harness } = options;
55
56
  const now = options.now ?? Date.now;
@@ -144,7 +145,7 @@ async function attachApprovals(options, context = BACKGROUND_CONTEXT) {
144
145
  id: `apr-${crypto.randomUUID()}`,
145
146
  state: "pending",
146
147
  decidedBy: null,
147
- always: guestKind(draft.kind) ? false : draft.always === true,
148
+ always: offered(options, draft),
148
149
  createdAt: at,
149
150
  expiresAt: draft.expiresAt ?? at + ttl
150
151
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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 type { ApprovalJudge, Judgement } from './judge.ts';\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 './intent.ts';\nexport * from './judge.ts';\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 judge?: ApprovalJudge;\n}\n\nconst copy = (items: readonly Approval[]): Approval[] => JSON.parse(JSON.stringify(items));\n\nfunction mark(item: Approval, how: Settle, at: number) {\n item.state = how.state;\n item.decidedBy = how.by;\n item.decidedAt = at;\n if (how.reason) item.reason = how.reason;\n}\n\nconst withdrawIn =\n (match: (item: Approval) => boolean, reason: string | undefined, now: () => number) =>\n (doc: ApprovalsState) => {\n const hit = doc.items.filter((item) => item.state === 'pending' && match(item));\n for (const item of hit)\n mark(item, { state: 'withdrawn', by: 'host', ...(reason ? { reason } : {}) }, now());\n return hit.map((item) => copy([item])[0] as Approval);\n };\n\nconst judgedIn = (id: string, judgement: Judgement, now: () => number) => (doc: ApprovalsState) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.tier !== 'agent') return undefined;\n const late = item.state === 'unanswered' && item.reason === REASONS.timeout && !item.verdict;\n if (item.state !== 'pending' && !(late && judgement.verdict)) return undefined;\n if (judgement.verdict) item.verdict = judgement.verdict;\n if (late) return copy([item])[0] as Approval;\n if (judgement.outcome === 'ask') item.tier = 'user';\n else {\n const at = now();\n const ruling = ruled(\n item,\n judgement.outcome === 'approve' ? 'once' : 'deny',\n null,\n 'judge',\n at\n );\n if (ruling.answer) item.answer = ruling.answer;\n if (ruling.settle) mark(item, ruling.settle, at);\n }\n return copy([item])[0] as Approval;\n};\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) => mark(item, how, now());\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 else if (options.judge && item.tier === 'agent') item.tier = 'user';\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 = (match: (item: Approval) => boolean, reason: string | undefined) =>\n withdrawIn(match, reason, now);\n const judging = async (judge: ApprovalJudge, item: Approval, using: Context) => {\n const signal = using.abortSignal;\n let gone = false;\n const abort = () => {\n gone = true;\n void serial(\n withdrawWhere((entry) => entry.id === item.id && entry.tier === 'agent', REASONS.aborted),\n BACKGROUND_CONTEXT\n ).catch(() => undefined);\n };\n if (signal?.aborted) return abort();\n signal?.addEventListener('abort', abort, { once: true });\n const judgement = await judge\n .judge(item, using)\n .catch((error: unknown): Judgement => ({ outcome: 'ask', error: String(error) }));\n signal?.removeEventListener('abort', abort);\n if (gone || closed) return;\n const after = await serial(judgedIn(item.id, judgement, now), BACKGROUND_CONTEXT).catch(\n () => undefined\n );\n if (after) await judge.record?.(after, judgement, BACKGROUND_CONTEXT).catch(() => undefined);\n };\n return {\n state,\n async request(draft, using) {\n const made = await 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 if (options.judge && made.state === 'pending' && made.tier === 'agent')\n void judging(options.judge, made, using);\n return made;\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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",
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 type { ApprovalJudge, Judgement } from './judge.ts';\nimport {\n APPROVAL_MS,\n type Approval,\n type ApprovalDraft,\n type ApprovalKind,\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 './intent.ts';\nexport * from './judge.ts';\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 judge?: ApprovalJudge;\n guestAlways?: (kind: ApprovalKind) => boolean;\n}\n\nconst copy = (items: readonly Approval[]): Approval[] => JSON.parse(JSON.stringify(items));\n\nfunction mark(item: Approval, how: Settle, at: number) {\n item.state = how.state;\n item.decidedBy = how.by;\n item.decidedAt = at;\n if (how.reason) item.reason = how.reason;\n}\n\nconst withdrawIn =\n (match: (item: Approval) => boolean, reason: string | undefined, now: () => number) =>\n (doc: ApprovalsState) => {\n const hit = doc.items.filter((item) => item.state === 'pending' && match(item));\n for (const item of hit)\n mark(item, { state: 'withdrawn', by: 'host', ...(reason ? { reason } : {}) }, now());\n return hit.map((item) => copy([item])[0] as Approval);\n };\n\nconst judgedIn = (id: string, judgement: Judgement, now: () => number) => (doc: ApprovalsState) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.tier !== 'agent') return undefined;\n const late = item.state === 'unanswered' && item.reason === REASONS.timeout && !item.verdict;\n if (item.state !== 'pending' && !(late && judgement.verdict)) return undefined;\n if (judgement.verdict) item.verdict = judgement.verdict;\n if (late) return copy([item])[0] as Approval;\n if (judgement.outcome === 'ask') item.tier = 'user';\n else {\n const at = now();\n const ruling = ruled(\n item,\n judgement.outcome === 'approve' ? 'once' : 'deny',\n null,\n 'judge',\n at\n );\n if (ruling.answer) item.answer = ruling.answer;\n if (ruling.settle) mark(item, ruling.settle, at);\n }\n return copy([item])[0] as Approval;\n};\n\nconst offered = (options: ApprovalsAttach, draft: ApprovalDraft): boolean =>\n guestKind(draft.kind) ? options.guestAlways?.(draft.kind) === true : draft.always === true;\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) => mark(item, how, now());\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 else if (options.judge && item.tier === 'agent') item.tier = 'user';\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 = (match: (item: Approval) => boolean, reason: string | undefined) =>\n withdrawIn(match, reason, now);\n const judging = async (judge: ApprovalJudge, item: Approval, using: Context) => {\n const signal = using.abortSignal;\n let gone = false;\n const abort = () => {\n gone = true;\n void serial(\n withdrawWhere((entry) => entry.id === item.id && entry.tier === 'agent', REASONS.aborted),\n BACKGROUND_CONTEXT\n ).catch(() => undefined);\n };\n if (signal?.aborted) return abort();\n signal?.addEventListener('abort', abort, { once: true });\n const judgement = await judge\n .judge(item, using)\n .catch((error: unknown): Judgement => ({ outcome: 'ask', error: String(error) }));\n signal?.removeEventListener('abort', abort);\n if (gone || closed) return;\n const after = await serial(judgedIn(item.id, judgement, now), BACKGROUND_CONTEXT).catch(\n () => undefined\n );\n if (after) await judge.record?.(after, judgement, BACKGROUND_CONTEXT).catch(() => undefined);\n };\n return {\n state,\n async request(draft, using) {\n const made = await serial((doc) => {\n const at = now();\n const item = {\n ...draft,\n id: `apr-${crypto.randomUUID()}`,\n state: 'pending',\n decidedBy: null,\n always: offered(options, draft),\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 if (options.judge && made.state === 'pending' && made.tier === 'agent')\n void judging(options.judge, made, using);\n return made;\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": "AAAA,SAA6C,uBAAuB;AACpE,SAAS,0BAA0B;AAGnC;AAAA,EACE;AAAA,EAKA;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP,cAAc;AACd,cAAc;AACd,cAAc;AA8Bd,MAAM,OAAO,CAAC,UAA2C,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AAEzF,SAAS,KAAK,MAAgB,KAAa,IAAY;AACrD,OAAK,QAAQ,IAAI;AACjB,OAAK,YAAY,IAAI;AACrB,OAAK,YAAY;AACjB,MAAI,IAAI,OAAQ,MAAK,SAAS,IAAI;AACpC;AAEA,MAAM,aACJ,CAAC,OAAoC,QAA4B,QACjE,CAAC,QAAwB;AACvB,QAAM,MAAM,IAAI,MAAM,OAAO,CAAC,SAAS,KAAK,UAAU,aAAa,MAAM,IAAI,CAAC;AAC9E,aAAW,QAAQ;AACjB,SAAK,MAAM,EAAE,OAAO,aAAa,IAAI,QAAQ,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,EAAG,GAAG,IAAI,CAAC;AACrF,SAAO,IAAI,IAAI,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAa;AACtD;AAEF,MAAM,WAAW,CAAC,IAAY,WAAsB,QAAsB,CAAC,QAAwB;AACjG,QAAM,OAAO,IAAI,MAAM,KAAK,CAAC,UAAU,MAAM,OAAO,EAAE;AACtD,MAAI,MAAM,SAAS,QAAS,QAAO;AACnC,QAAM,OAAO,KAAK,UAAU,gBAAgB,KAAK,WAAW,QAAQ,WAAW,CAAC,KAAK;AACrF,MAAI,KAAK,UAAU,aAAa,EAAE,QAAQ,UAAU,SAAU,QAAO;AACrE,MAAI,UAAU,QAAS,MAAK,UAAU,UAAU;AAChD,MAAI,KAAM,QAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;AAC/B,MAAI,UAAU,YAAY,MAAO,MAAK,OAAO;AAAA,OACxC;AACH,UAAM,KAAK,IAAI;AACf,UAAM,SAAS;AAAA,MACb;AAAA,MACA,UAAU,YAAY,YAAY,SAAS;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,OAAO,OAAQ,MAAK,SAAS,OAAO;AACxC,QAAI,OAAO,OAAQ,MAAK,MAAM,OAAO,QAAQ,EAAE;AAAA,EACjD;AACA,SAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;AACvB;AAEA,MAAM,UAAU,CAAC,SAA0B,UACzC,UAAU,MAAM,IAAI,IAAI,QAAQ,cAAc,MAAM,IAAI,MAAM,OAAO,MAAM,WAAW;AAExF,eAAsB,gBACpB,SACA,UAAmB,oBACC;AACpB,QAAM,EAAE,QAAQ,IAAI;AACpB,QAAM,MAAM,QAAQ,OAAO,KAAK;AAChC,QAAM,MAAM,QAAQ,SAAS;AAC7B,QAAM,SAAS,oBAAI,IAA2C;AAC9D,MAAI,OAAyB,QAAQ,QAAQ;AAC7C,MAAI,SAAS;AACb,QAAM,SAAS,CAAC,MAAgB,QAAgB,KAAK,MAAM,KAAK,IAAI,CAAC;AACrE,QAAM,QAAQ,CACZ,QACA,UAEA,QAAQ,OAAO,OAAO,OAAO;AAC3B,UAAM,MAAM,MAAM,GAAG,IAAI,YAAY;AACrC,UAAM,SAAS,OAAO,GAAG;AACzB,QAAI,QAAQ,MAAM,IAAI,KAAK;AAC3B,WAAO,EAAE,QAAQ,OAAO,KAAK,IAAI,KAAK,EAAE;AAAA,EAC1C,GAAG,KAAK;AACV,QAAM,UAAU,MAAM,MAAM,CAAC,QAAQ;AACnC,eAAW,QAAQ,IAAI,OAAO;AAC5B,UAAI,KAAK,UAAU,UAAW;AAC9B,UAAI,KAAK,SAAS;AAChB,eAAO,MAAM,EAAE,OAAO,aAAa,IAAI,QAAQ,QAAQ,QAAQ,QAAQ,CAAC;AAAA,eACjE,KAAK,aAAa,IAAI;AAC7B,eAAO,MAAM,EAAE,OAAO,cAAc,IAAI,QAAQ,QAAQ,QAAQ,QAAQ,CAAC;AAAA,eAClE,QAAQ,SAAS,KAAK,SAAS,QAAS,MAAK,OAAO;AAAA,IAC/D;AAAA,EACF,GAAG,OAAO;AACV,QAAM,QAAQ,gBAA4B,QAAQ,KAAK;AACvD,QAAM,SAAS,CAAI,QAAoC,UAA+B;AACpF,UAAM,MAAM,KAAK,KAAK,YAAY;AAChC,YAAM,EAAE,QAAQ,MAAM,IAAI,MAAM,MAAM,QAAQ,KAAK;AACnD,YAAM,QAAQ,OAAO,KAAK;AAC1B,UAAI,KAAK;AACT,aAAO;AAAA,IACT,CAAC;AACD,WAAO,IAAI,MAAM,MAAM,MAAS;AAChC,WAAO;AAAA,EACT;AACA,QAAM,SAAS,CAAC,OAAe;AAC7B,WAAO,OAAO,EAAE;AAChB,UAAM,MAAO,MAAM,MAAqB,KAAK,CAAC,UAAU,MAAM,OAAO,EAAE;AACvE,QAAI,KAAK,UAAU,aAAa,IAAI,YAAY,IAAI,EAAG,QAAO,IAAI,MAAM,KAAmB;AAC3F,SAAK,OAAO,CAAC,QAAQ;AACnB,YAAM,OAAO,IAAI,MAAM,KAAK,CAAC,UAAU,MAAM,OAAO,EAAE;AACtD,UAAI,MAAM,UAAU,aAAa,KAAK,aAAa,IAAI;AACrD,eAAO,MAAM,EAAE,OAAO,cAAc,IAAI,QAAQ,QAAQ,QAAQ,QAAQ,CAAC;AAAA,IAC7E,GAAG,kBAAkB,EAAE,MAAM,MAAM,MAAS;AAAA,EAC9C;AACA,WAAS,IAAI,OAA4B;AACvC,UAAM,UAAU,oBAAI,IAAY;AAChC,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,UAAU,UAAW;AAC9B,cAAQ,IAAI,KAAK,EAAE;AACnB,UAAI,OAAO,IAAI,KAAK,EAAE,KAAK,OAAQ;AACnC,aAAO;AAAA,QACL,KAAK;AAAA,QACL,WAAW,MAAM,OAAO,KAAK,EAAE,GAAG,KAAK,IAAI,WAAW,KAAK,IAAI,GAAG,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC;AAAA,MAC5F;AAAA,IACF;AACA,eAAW,CAAC,IAAI,KAAK,KAAK;AACxB,UAAI,CAAC,QAAQ,IAAI,EAAE,GAAG;AACpB,qBAAa,KAAK;AAClB,eAAO,OAAO,EAAE;AAAA,MAClB;AAAA,EACJ;AACA,MAAI,QAAQ,KAAK;AACjB,QAAM,gBAAgB,CAAC,OAAoC,WACzD,WAAW,OAAO,QAAQ,GAAG;AAC/B,QAAM,UAAU,OAAO,OAAsB,MAAgB,UAAmB;AAC9E,UAAM,SAAS,MAAM;AACrB,QAAI,OAAO;AACX,UAAM,QAAQ,MAAM;AAClB,aAAO;AACP,WAAK;AAAA,QACH,cAAc,CAAC,UAAU,MAAM,OAAO,KAAK,MAAM,MAAM,SAAS,SAAS,QAAQ,OAAO;AAAA,QACxF;AAAA,MACF,EAAE,MAAM,MAAM,MAAS;AAAA,IACzB;AACA,QAAI,QAAQ,QAAS,QAAO,MAAM;AAClC,YAAQ,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;AACvD,UAAM,YAAY,MAAM,MACrB,MAAM,MAAM,KAAK,EACjB,MAAM,CAAC,WAA+B,EAAE,SAAS,OAAO,OAAO,OAAO,KAAK,EAAE,EAAE;AAClF,YAAQ,oBAAoB,SAAS,KAAK;AAC1C,QAAI,QAAQ,OAAQ;AACpB,UAAM,QAAQ,MAAM,OAAO,SAAS,KAAK,IAAI,WAAW,GAAG,GAAG,kBAAkB,EAAE;AAAA,MAChF,MAAM;AAAA,IACR;AACA,QAAI,MAAO,OAAM,MAAM,SAAS,OAAO,WAAW,kBAAkB,EAAE,MAAM,MAAM,MAAS;AAAA,EAC7F;AACA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,QAAQ,OAAO,OAAO;AAC1B,YAAM,OAAO,MAAM,OAAO,CAAC,QAAQ;AACjC,cAAM,KAAK,IAAI;AACf,cAAM,OAAO;AAAA,UACX,GAAG;AAAA,UACH,IAAI,OAAO,OAAO,WAAW,CAAC;AAAA,UAC9B,OAAO;AAAA,UACP,WAAW;AAAA,UACX,QAAQ,QAAQ,SAAS,KAAK;AAAA,UAC9B,WAAW;AAAA,UACX,WAAW,MAAM,aAAa,KAAK;AAAA,QACrC;AACA,YAAI,CAAC,UAAU,MAAM,IAAI;AACvB,iBAAO,MAAM,EAAE,OAAO,cAAc,IAAI,QAAQ,QAAQ,QAAQ,YAAY,CAAC;AAAA,iBACtE,CAAC,SAAS,MAAM,MAAM,MAAM,IAAI;AACvC,iBAAO,MAAM,EAAE,OAAO,cAAc,IAAI,QAAQ,QAAQ,QAAQ,WAAW,CAAC;AAC9E,YAAI,MAAM,KAAK,IAAI;AACnB,eAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;AAAA,MACvB,GAAG,KAAK;AACR,UAAI,QAAQ,SAAS,KAAK,UAAU,aAAa,KAAK,SAAS;AAC7D,aAAK,QAAQ,QAAQ,OAAO,MAAM,KAAK;AACzC,aAAO;AAAA,IACT;AAAA,IACA,OAAO,IAAI,QAAQ,QAAQ,IAAI,OAAO;AACpC,aAAO,OAAO,CAAC,QAAyB;AACtC,cAAM,OAAO,IAAI,MAAM,KAAK,CAAC,UAAU,MAAM,OAAO,EAAE;AACtD,cAAM,SAAS,OACX,MAAM,MAAM,QAAQ,QAAQ,IAAI,IAAI,CAAC,IACrC,EAAE,OAAO,wBAAwB,EAAE,IAAI;AAC3C,YAAI,QAAQ,OAAO,QAAQ;AACzB,cAAI,OAAO,OAAQ,MAAK,SAAS,OAAO;AACxC,iBAAO,MAAM,OAAO,MAAM;AAAA,QAC5B;AACA,YAAI,WAAW,UAAU,OAAO;AAC9B,iBAAO,EAAE,MAAM,OAAO,OAAO,MAAM,OAAO,OAAO,MAAM;AACzD,eAAO,EAAE,MAAM,MAAM,OAAQ,KAAkB,OAAO,OAAO,KAAK;AAAA,MACpE,GAAG,KAAK;AAAA,IACV;AAAA,IACA,QAAQ,IAAI,OAAO;AACjB,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAM,SAAS,MAAM;AACrB,YAAI,OAAO;AACX,YAAI;AACJ,cAAM,SAAS,CAAC,QAAoB;AAClC,iBAAO;AACP,gBAAM;AACN,kBAAQ,oBAAoB,SAAS,KAAK;AAC1C,cAAI;AAAA,QACN;AACA,cAAM,QAAQ,MAAM,OAAO,MAAM,OAAO,QAAQ,MAAM,CAAC;AACvD,cAAM,QAAQ,CAAC,UAA2C;AACxD,gBAAM,OAAO,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,EAAE;AACnD,cAAI,CAAC,KAAM,QAAO,MAAM,OAAO,IAAI,MAAM,wBAAwB,EAAE,GAAG,CAAC,CAAC;AAAA,mBAC/D,KAAK,UAAU,UAAW,QAAO,MAAM,QAAQ,IAAI,CAAC;AAAA,QAC/D;AACA,YAAI,QAAQ,QAAS,QAAO,MAAM;AAClC,cAAM,MAAM,KAAK;AACjB,YAAI,KAAM;AACV,gBAAQ,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;AACvD,cAAM,MAAM,UAAU,CAAC,UAAU,MAAM,KAAK,CAAC;AAAA,MAC/C,CAAC;AAAA,IACH;AAAA,IACA,MAAM,SAAS,IAAI,QAAQ,OAAO;AAChC,YAAM,CAAC,GAAG,IAAI,MAAM;AAAA,QAClB,cAAc,CAAC,SAAS,KAAK,OAAO,IAAI,YAAY,MAAM,CAAC;AAAA,QAC3D;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,aAAa,MAAM,OAAO;AAC9B,cACE,MAAM;AAAA,QACJ,cAAc,CAAC,SAAS,KAAK,SAAS,MAAM,QAAQ,OAAO;AAAA,QAC3D;AAAA,MACF,GACA;AAAA,IACJ;AAAA,IACA,QAAQ;AACN,eAAS;AACT,iBAAW,SAAS,OAAO,OAAO,EAAG,cAAa,KAAK;AACvD,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,110 @@
1
+ import {
2
+ replicatedState
3
+ } from "@earendil-works/chord";
4
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
5
+ import { defineDoc } from "@earendil-works/pi-durable";
6
+ import {
7
+ changedGuests,
8
+ DEFAULT_GUEST_POLICY
9
+ } from "../guests/policy.js";
10
+ const DEFAULT_APPROVAL_POLICY = {
11
+ guests: DEFAULT_GUEST_POLICY,
12
+ sudo: { approver: "user" }
13
+ };
14
+ const ApprovalPolicyDoc = defineDoc({
15
+ kind: "slicc.approval-policy",
16
+ version: 1,
17
+ scope: "session",
18
+ initial: () => ({ overrides: {} })
19
+ });
20
+ const copy = (value) => JSON.parse(JSON.stringify(value));
21
+ function changedSudo(next, change) {
22
+ if (!change || typeof change !== "object" || Array.isArray(change))
23
+ return "The sudo policy is an object of policy entries.";
24
+ for (const [name, value] of Object.entries(change)) {
25
+ if (name !== "approver") return `There is no sudo policy entry ${name}.`;
26
+ if (value !== "user" && value !== "agent")
27
+ return "A sudo request is approved by user or agent.";
28
+ next.sudo.approver = value;
29
+ }
30
+ return null;
31
+ }
32
+ function changedPolicy(base, change) {
33
+ if (!change || typeof change !== "object" || Array.isArray(change))
34
+ return { policy: null, error: "An approval policy change is an object of sections." };
35
+ const next = copy(base);
36
+ for (const [name, value] of Object.entries(change)) {
37
+ const error = name === "guests" ? changedGuests(next.guests, value) : name === "sudo" ? changedSudo(next, value) : `There is no approval policy section ${name}.`;
38
+ if (error) return { policy: null, error };
39
+ }
40
+ return { policy: next, error: null };
41
+ }
42
+ function resolvedPolicy(overrides) {
43
+ const guests = overrides.guests ?? {};
44
+ return copy({
45
+ guests: {
46
+ ...DEFAULT_GUEST_POLICY,
47
+ ...guests,
48
+ always: { ...DEFAULT_GUEST_POLICY.always, ...guests.always }
49
+ },
50
+ sudo: { ...DEFAULT_APPROVAL_POLICY.sudo, ...overrides.sudo }
51
+ });
52
+ }
53
+ function overridden(overrides, change, policy) {
54
+ const next = copy(overrides);
55
+ for (const [section, entries] of Object.entries(change)) {
56
+ const key = section;
57
+ const into = next[key] ?? {};
58
+ const resolved = policy[key];
59
+ for (const name of Object.keys(entries))
60
+ into[name] = name === "always" ? { ...into.always, ...entries.always } : resolved[name];
61
+ next[key] = into;
62
+ }
63
+ return next;
64
+ }
65
+ const alwaysFor = (policy) => (kind) => {
66
+ const { always } = policy.current().guests;
67
+ return kind === "guest-tool" ? always.tools : always.messages;
68
+ };
69
+ async function attachApprovalPolicy(harness, context = BACKGROUND_CONTEXT) {
70
+ let overrides = await harness.commit(
71
+ async (tx) => copy((await tx.doc(ApprovalPolicyDoc)).overrides ?? {}),
72
+ context
73
+ );
74
+ const state = replicatedState(resolvedPolicy(overrides));
75
+ let line = Promise.resolve();
76
+ return {
77
+ state,
78
+ current: () => copy(state.value),
79
+ set(change, using) {
80
+ const run = line.then(async () => {
81
+ const made = changedPolicy(state.value, change);
82
+ if (!made.policy) return made;
83
+ const next = overridden(
84
+ overrides,
85
+ change,
86
+ made.policy
87
+ );
88
+ await harness.commit(async (tx) => {
89
+ (await tx.doc(ApprovalPolicyDoc)).overrides = copy(next);
90
+ }, using);
91
+ overrides = next;
92
+ const policy = resolvedPolicy(next);
93
+ state.replace(using, copy(policy));
94
+ return { policy: copy(policy), error: null };
95
+ });
96
+ line = run.catch(() => void 0);
97
+ return run;
98
+ }
99
+ };
100
+ }
101
+ export {
102
+ ApprovalPolicyDoc,
103
+ DEFAULT_APPROVAL_POLICY,
104
+ alwaysFor,
105
+ attachApprovalPolicy,
106
+ changedPolicy,
107
+ overridden,
108
+ resolvedPolicy
109
+ };
110
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/approvals/policy.ts"],
4
+ "sourcesContent": ["import {\n type Context,\n type JsonValue,\n type ReplicatedState,\n replicatedState,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { defineDoc, type Harness, type JsonObject } from '@earendil-works/pi-durable';\nimport {\n changedGuests,\n DEFAULT_GUEST_POLICY,\n type GuestPolicy,\n type GuestPolicyChange,\n} from '../guests/policy.ts';\nimport type { ApprovalKind } from './record.ts';\n\nexport type SudoPolicy = { approver: 'user' | 'agent' };\n\nexport type ApprovalPolicy = { guests: GuestPolicy; sudo: SudoPolicy };\n\nexport type ApprovalPolicyChange = { guests?: GuestPolicyChange; sudo?: Partial<SudoPolicy> };\n\nexport type ApprovalPolicySet =\n | { policy: ApprovalPolicy; error: null }\n | { policy: null; error: string };\n\nexport const DEFAULT_APPROVAL_POLICY: ApprovalPolicy = {\n guests: DEFAULT_GUEST_POLICY,\n sudo: { approver: 'user' },\n};\n\ntype Overrides = Partial<Record<keyof ApprovalPolicy, JsonObject>>;\n\nexport const ApprovalPolicyDoc = defineDoc<{ overrides: Overrides }>({\n kind: 'slicc.approval-policy',\n version: 1,\n scope: 'session',\n initial: () => ({ overrides: {} }),\n});\n\nconst copy = <T>(value: T): T => JSON.parse(JSON.stringify(value));\n\nfunction changedSudo(next: ApprovalPolicy, change: unknown): string | null {\n if (!change || typeof change !== 'object' || Array.isArray(change))\n return 'The sudo policy is an object of policy entries.';\n for (const [name, value] of Object.entries(change)) {\n if (name !== 'approver') return `There is no sudo policy entry ${name}.`;\n if (value !== 'user' && value !== 'agent')\n return 'A sudo request is approved by user or agent.';\n next.sudo.approver = value;\n }\n return null;\n}\n\nexport function changedPolicy(base: ApprovalPolicy, change: unknown): ApprovalPolicySet {\n if (!change || typeof change !== 'object' || Array.isArray(change))\n return { policy: null, error: 'An approval policy change is an object of sections.' };\n const next = copy(base);\n for (const [name, value] of Object.entries(change)) {\n const error =\n name === 'guests'\n ? changedGuests(next.guests, value)\n : name === 'sudo'\n ? changedSudo(next, value)\n : `There is no approval policy section ${name}.`;\n if (error) return { policy: null, error };\n }\n return { policy: next, error: null };\n}\n\nexport function resolvedPolicy(overrides: Overrides): ApprovalPolicy {\n const guests = (overrides.guests ?? {}) as Partial<GuestPolicy>;\n return copy({\n guests: {\n ...DEFAULT_GUEST_POLICY,\n ...guests,\n always: { ...DEFAULT_GUEST_POLICY.always, ...guests.always },\n },\n sudo: { ...DEFAULT_APPROVAL_POLICY.sudo, ...overrides.sudo },\n });\n}\n\nexport function overridden(\n overrides: Overrides,\n change: Record<string, Record<string, unknown>>,\n policy: ApprovalPolicy\n): Overrides {\n const next = copy(overrides);\n for (const [section, entries] of Object.entries(change)) {\n const key = section as keyof ApprovalPolicy;\n const into = next[key] ?? {};\n const resolved = policy[key] as unknown as JsonObject;\n for (const name of Object.keys(entries))\n into[name] = (\n name === 'always'\n ? { ...(into.always as object), ...(entries.always as object) }\n : resolved[name]\n ) as JsonValue;\n next[key] = into;\n }\n return next;\n}\n\nexport interface ApprovalPolicies {\n readonly state: ReplicatedState<ApprovalPolicy>;\n current(): ApprovalPolicy;\n set(change: unknown, context: Context): Promise<ApprovalPolicySet>;\n}\n\nexport const alwaysFor =\n (policy: Pick<ApprovalPolicies, 'current'>) =>\n (kind: ApprovalKind): boolean => {\n const { always } = policy.current().guests;\n return kind === 'guest-tool' ? always.tools : always.messages;\n };\n\nexport async function attachApprovalPolicy(\n harness: Pick<Harness, 'commit'>,\n context: Context = BACKGROUND_CONTEXT\n): Promise<ApprovalPolicies> {\n let overrides = await harness.commit(\n async (tx) => copy((await tx.doc(ApprovalPolicyDoc)).overrides ?? {}),\n context\n );\n const state = replicatedState<ApprovalPolicy>(resolvedPolicy(overrides));\n let line: Promise<unknown> = Promise.resolve();\n return {\n state,\n current: () => copy(state.value as ApprovalPolicy),\n set(change, using) {\n const run = line.then(async () => {\n const made = changedPolicy(state.value as ApprovalPolicy, change);\n if (!made.policy) return made;\n const next = overridden(\n overrides,\n change as Record<string, Record<string, unknown>>,\n made.policy\n );\n await harness.commit(async (tx) => {\n (await tx.doc(ApprovalPolicyDoc)).overrides = copy(next);\n }, using);\n overrides = next;\n const policy = resolvedPolicy(next);\n state.replace(using, copy(policy));\n return { policy: copy(policy), error: null };\n });\n line = run.catch(() => undefined);\n return run;\n },\n };\n}\n"],
5
+ "mappings": "AAAA;AAAA,EAIE;AAAA,OACK;AACP,SAAS,0BAA0B;AACnC,SAAS,iBAAgD;AACzD;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAaA,MAAM,0BAA0C;AAAA,EACrD,QAAQ;AAAA,EACR,MAAM,EAAE,UAAU,OAAO;AAC3B;AAIO,MAAM,oBAAoB,UAAoC;AAAA,EACnE,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,WAAW,CAAC,EAAE;AAClC,CAAC;AAED,MAAM,OAAO,CAAI,UAAgB,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AAEjE,SAAS,YAAY,MAAsB,QAAgC;AACzE,MAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM;AAC/D,WAAO;AACT,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAClD,QAAI,SAAS,WAAY,QAAO,iCAAiC,IAAI;AACrE,QAAI,UAAU,UAAU,UAAU;AAChC,aAAO;AACT,SAAK,KAAK,WAAW;AAAA,EACvB;AACA,SAAO;AACT;AAEO,SAAS,cAAc,MAAsB,QAAoC;AACtF,MAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM;AAC/D,WAAO,EAAE,QAAQ,MAAM,OAAO,sDAAsD;AACtF,QAAM,OAAO,KAAK,IAAI;AACtB,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAClD,UAAM,QACJ,SAAS,WACL,cAAc,KAAK,QAAQ,KAAK,IAChC,SAAS,SACP,YAAY,MAAM,KAAK,IACvB,uCAAuC,IAAI;AACnD,QAAI,MAAO,QAAO,EAAE,QAAQ,MAAM,MAAM;AAAA,EAC1C;AACA,SAAO,EAAE,QAAQ,MAAM,OAAO,KAAK;AACrC;AAEO,SAAS,eAAe,WAAsC;AACnE,QAAM,SAAU,UAAU,UAAU,CAAC;AACrC,SAAO,KAAK;AAAA,IACV,QAAQ;AAAA,MACN,GAAG;AAAA,MACH,GAAG;AAAA,MACH,QAAQ,EAAE,GAAG,qBAAqB,QAAQ,GAAG,OAAO,OAAO;AAAA,IAC7D;AAAA,IACA,MAAM,EAAE,GAAG,wBAAwB,MAAM,GAAG,UAAU,KAAK;AAAA,EAC7D,CAAC;AACH;AAEO,SAAS,WACd,WACA,QACA,QACW;AACX,QAAM,OAAO,KAAK,SAAS;AAC3B,aAAW,CAAC,SAAS,OAAO,KAAK,OAAO,QAAQ,MAAM,GAAG;AACvD,UAAM,MAAM;AACZ,UAAM,OAAO,KAAK,GAAG,KAAK,CAAC;AAC3B,UAAM,WAAW,OAAO,GAAG;AAC3B,eAAW,QAAQ,OAAO,KAAK,OAAO;AACpC,WAAK,IAAI,IACP,SAAS,WACL,EAAE,GAAI,KAAK,QAAmB,GAAI,QAAQ,OAAkB,IAC5D,SAAS,IAAI;AAErB,SAAK,GAAG,IAAI;AAAA,EACd;AACA,SAAO;AACT;AAQO,MAAM,YACX,CAAC,WACD,CAAC,SAAgC;AAC/B,QAAM,EAAE,OAAO,IAAI,OAAO,QAAQ,EAAE;AACpC,SAAO,SAAS,eAAe,OAAO,QAAQ,OAAO;AACvD;AAEF,eAAsB,qBACpB,SACA,UAAmB,oBACQ;AAC3B,MAAI,YAAY,MAAM,QAAQ;AAAA,IAC5B,OAAO,OAAO,MAAM,MAAM,GAAG,IAAI,iBAAiB,GAAG,aAAa,CAAC,CAAC;AAAA,IACpE;AAAA,EACF;AACA,QAAM,QAAQ,gBAAgC,eAAe,SAAS,CAAC;AACvE,MAAI,OAAyB,QAAQ,QAAQ;AAC7C,SAAO;AAAA,IACL;AAAA,IACA,SAAS,MAAM,KAAK,MAAM,KAAuB;AAAA,IACjD,IAAI,QAAQ,OAAO;AACjB,YAAM,MAAM,KAAK,KAAK,YAAY;AAChC,cAAM,OAAO,cAAc,MAAM,OAAyB,MAAM;AAChE,YAAI,CAAC,KAAK,OAAQ,QAAO;AACzB,cAAM,OAAO;AAAA,UACX;AAAA,UACA;AAAA,UACA,KAAK;AAAA,QACP;AACA,cAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,WAAC,MAAM,GAAG,IAAI,iBAAiB,GAAG,YAAY,KAAK,IAAI;AAAA,QACzD,GAAG,KAAK;AACR,oBAAY;AACZ,cAAM,SAAS,eAAe,IAAI;AAClC,cAAM,QAAQ,OAAO,KAAK,MAAM,CAAC;AACjC,eAAO,EAAE,QAAQ,KAAK,MAAM,GAAG,OAAO,KAAK;AAAA,MAC7C,CAAC;AACD,aAAO,IAAI,MAAM,MAAM,MAAS;AAChC,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
package/dist/client.js CHANGED
@@ -133,6 +133,7 @@ async function connectAgent(endpoint, options = {}) {
133
133
  viewOnly: controlHandle.kind === "view",
134
134
  owner: pagesHandle.kind === "pages",
135
135
  approvals: hold(state("approvals")),
136
+ approvalPolicy: hold(state("approvalPolicy")),
136
137
  settings: lastingSettings(settings, hold),
137
138
  remote,
138
139
  pages,
@@ -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 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 { GuestSeat } from './guests/seats.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.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 GuestProposal,\n type GuestShown,\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 { 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 guests?: ReplicatedState<GuestSeat[]>;\n readonly viewOnly?: boolean;\n readonly guest?: GuestShown;\n readonly proposals?: ReplicatedState<GuestProposal[]>;\n readonly owner?: boolean;\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 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 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 serverId = await link.hello;\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 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,\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 guests: hold<GuestSeat[]>(state('guests')),\n viewOnly: controlHandle.kind === 'view',\n owner: pagesHandle.kind === 'pages',\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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",
4
+ "sourcesContent": ["import {\n type Context,\n createHost,\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 { ApprovalPolicy } from './approvals/policy.ts';\nimport type { Approval } from './approvals/record.ts';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { GuestSeat } from './guests/seats.ts';\nimport type { MemoryEntry, MemoryScope } from './memory/format.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 GuestProposal,\n type GuestShown,\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 { 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 guests?: ReplicatedState<GuestSeat[]>;\n readonly viewOnly?: boolean;\n readonly guest?: GuestShown;\n readonly proposals?: ReplicatedState<GuestProposal[]>;\n readonly owner?: boolean;\n readonly approvals?: ReplicatedState<Approval[]>;\n readonly approvalPolicy?: ReplicatedState<ApprovalPolicy>;\n readonly settings: AgentSettings;\n readonly remote?: AgentRemote;\n readonly pages?: AgentPages;\n readonly closed: Promise<Error | undefined>;\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 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 serverId = await link.hello;\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 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,\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 guests: hold<GuestSeat[]>(state('guests')),\n viewOnly: controlHandle.kind === 'view',\n owner: pagesHandle.kind === 'pages',\n approvals: hold<Approval[]>(state('approvals')),\n approvalPolicy: hold<ApprovalPolicy>(state('approvalPolicy')),\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
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/control.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type AgentChange,\n AssistantEntry,\n type Conversation,\n ConversationBusy,\n type ConversationView,\n type EntryId,\n type EntryRecord,\n type Harness,\n type SubmissionId,\n type UserInput,\n} from '@earendil-works/pi-durable';\nimport { answerText } from './agent.ts';\nimport type { Agents, ScoopRecord } from './agents.ts';\nimport { type ShownAttachment, splitUser, userParts } from './attachments.ts';\nimport type { ChangesRuntime } from './changes/index.ts';\nimport type { Cones } from './cone.ts';\nimport type { Deliveries } from './deliveries.ts';\nimport type { FreezerRuntime } from './freezer/index.ts';\nimport type { Guests } from './guests/index.ts';\nimport type { LickSources, Licks } from './licks/index.ts';\nimport { LICK_STATE_KIND } from './licks/state.ts';\nimport type { MemoryRuntime } from './memory/index.ts';\nimport type { Monitor } from './monitor/index.ts';\nimport type { Scoops, ScoopsRuntime } from './scoops/index.ts';\nimport { pinned, SECRETS_COMMAND, secretsEntry } from './secrets/extension.ts';\nimport type { SecretName } from './secrets/names.ts';\nimport { scrubItems, scrubText } from './secrets/scrub.ts';\nimport type {\n AgentControl,\n Created,\n ImageRef,\n LickResolved,\n OperationError,\n Rewound,\n SendRequest,\n SendResponse,\n StoredImage,\n} from './services.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\nimport type { SprinklesRuntime } from './sprinkles/index.ts';\nimport type { LabelsRuntime } from './titles/labels.ts';\nimport { saverFor, type Uploads } from './uploads.ts';\n\nexport interface HostLicks {\n licks: Licks;\n sources: LickSources;\n}\n\nexport interface HostScoops {\n scoops: Scoops;\n runtime: ScoopsRuntime;\n}\n\ntype WithAgents = { agents?: Agents };\n\nexport function readable(out: string): string {\n const text = out.replace(/^(?:sub)?agent: /, '').trim();\n const sentence = text.charAt(0).toUpperCase() + text.slice(1);\n return /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;\n}\n\nfunction created(run: () => Promise<string>): Promise<Created> {\n return run().then(\n (id) => ({ id, error: null }),\n (error: Error) => ({ id: null, error: error.message })\n );\n}\n\nfunction monitorControl(\n monitor: Pick<Monitor, 'open' | 'close'> | undefined\n): Pick<AgentControl, 'monitorOpen' | 'monitorClose'> {\n return {\n monitorOpen: async (lease, resync, context) =>\n monitor ? monitor.open(lease, resync, context) : { lease: '', ttlMs: 0 },\n monitorClose: async (lease, context) => monitor?.close(lease, context),\n };\n}\n\nconst noApprovals: AgentControl['decide'] = async () => ({\n done: false,\n state: null,\n error: 'This agent has no approvals.',\n});\n\nfunction freezerControl(\n freezer: Pick<FreezerRuntime, 'freeze' | 'newChat' | 'thaw' | 'discard'> | undefined\n): Pick<AgentControl, 'freeze' | 'newChat' | 'thaw' | 'discard'> {\n const needed = () => {\n if (!freezer) throw new Error('This agent has no freezer.');\n return freezer;\n };\n return {\n freeze: (agentId, context) => created(async () => needed().freeze(agentId, context)),\n newChat: (agentId, context) =>\n created(async () => (await needed().newChat(agentId, context)) ?? ''),\n thaw: (id, context) => created(async () => needed().thaw(id, context)),\n discard: (id, context) =>\n created(async () => {\n await needed().discard(id, context);\n return id;\n }),\n };\n}\n\nfunction guestControl(\n guests: Pick<Guests, 'open' | 'revoke'> | undefined\n): Pick<AgentControl, 'guestOpen' | 'guestRevoke'> {\n return {\n guestOpen: async (draft, context) =>\n guests ? guests.open(draft, context) : { seat: null, error: NO_GUESTS },\n guestRevoke: async (id, context) => (guests ? guests.revoke(id, context) : { revoked: false }),\n };\n}\n\nconst NO_GUESTS = 'This agent has no guest seats.';\n\nfunction changesControl(\n changes: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'> | undefined\n): Pick<AgentControl, 'changesOpen' | 'changeAccept' | 'changeRevert'> {\n const needed = () => {\n if (!changes) throw new Error('This agent tracks no changes.');\n return changes;\n };\n const done = (path: string, run: (runtime: NonNullable<typeof changes>) => Promise<void>) =>\n created(async () => {\n await run(needed());\n return path;\n });\n return {\n changesOpen: async (context) => changes?.open(context),\n changeAccept: (path, context) => done(path, (runtime) => runtime.accept(path, context)),\n changeRevert: (path, context) => done(path, (runtime) => runtime.revert(path, context)),\n };\n}\n\nfunction memoryControl(\n memory: Pick<MemoryRuntime, 'save' | 'remove'> | undefined\n): Pick<AgentControl, 'memorySave' | 'memoryRemove'> {\n return {\n async memorySave(draft, context) {\n if (!memory) throw new Error('This agent has no memory.');\n const { id, ...rest } = draft;\n return memory.save({ ...rest, ...(id ? { id } : {}) }, context);\n },\n async memoryRemove(id, context) {\n return { removed: memory ? await memory.remove(id, context) : false };\n },\n };\n}\n\nfunction sprinkleControl(\n sprinkles: Pick<SprinklesRuntime, 'send' | 'call'> | undefined\n): Pick<AgentControl, 'sprinkleSend' | 'sprinkleCall'> {\n return {\n sprinkleSend: async (id, payload, context) =>\n sprinkles ? sprinkles.send(id, payload, context) : { delivered: false },\n async sprinkleCall(id, method, args, context) {\n if (!sprinkles) throw new Error('This agent has no sprinkles.');\n return sprinkles.call(id, method, args, context);\n },\n };\n}\n\nexport function submissionId(value: string): SubmissionId | undefined {\n const id = Number(value);\n return /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(id) ? (id as SubmissionId) : undefined;\n}\n\nfunction operationError(error: unknown): OperationError {\n if (error instanceof ConversationBusy) return { code: 'busy', message: error.message };\n return { code: 'failed', message: error instanceof Error ? error.message : String(error) };\n}\n\nasync function accepted(operation: () => Promise<{ id: number } | number>): Promise<SendResponse> {\n try {\n const result = await operation();\n const id = typeof result === 'number' ? result : result.id;\n return { accepted: true, submissionId: String(id), error: null };\n } catch (error) {\n return { accepted: false, submissionId: null, error: operationError(error) };\n }\n}\n\nfunction cones(target: Conversation | Cones): Cones {\n if ('cone' in target) return target;\n return {\n cone: () => target,\n switchCone: async () => {\n throw new Error('this conversation has no cone to rewind');\n },\n };\n}\n\ninterface Turn {\n text: string;\n attachments: ShownAttachment[];\n at: EntryId | null;\n files: boolean;\n}\n\ntype Block = { type?: string; name?: string };\n\nconst WRITERS = ['write', 'edit', 'bash', 'codemode'];\n\nfunction writesIn(entries: readonly EntryRecord[]): boolean {\n return entries.some((entry) =>\n (entry.model ?? []).some(\n (message) =>\n Array.isArray(message.content) &&\n (message.content as Block[]).some(\n (block) => block.type === 'toolCall' && WRITERS.includes(String(block.name))\n )\n )\n );\n}\n\nfunction lastTurn(view: ConversationView, messageId: string | null): Turn | null {\n const { entries } = view;\n const target = entries.findIndex((entry) => `e${entry.id}` === messageId);\n if (target < 0) return null;\n const turn = entries.slice(0, target).findLastIndex((entry) => entry.kind === 'pi.user');\n if (turn < 0) return null;\n const user = entries[turn] as EntryRecord;\n return {\n ...splitUser(user.model?.[0]?.content),\n at: entries[turn - 1]?.id ?? null,\n files: writesIn(entries.slice(turn)),\n };\n}\n\nfunction busy(view: ConversationView): boolean {\n const live = view.docs['pi.live'] as { run?: unknown } | undefined;\n const inbox = view.docs['pi.inbox'] as { items?: unknown[] } | undefined;\n return Boolean(live?.run) || Boolean(inbox?.items?.length);\n}\n\ntype RewindDeps = {\n harness: Harness;\n agents?: Agents;\n cone: Cones;\n scoops?: HostScoops;\n};\n\nasync function turnIn(\n harness: Harness,\n conversation: Conversation,\n messageId: string | null,\n context: Context\n): Promise<{ turn: Turn; view: ConversationView } | Rewound> {\n const attached = await conversation.viewState(context);\n const view = attached.value;\n attached.dispose();\n const pending = (await harness.inspect(context)).submissions.some(\n (submission) => submission.conversationId === conversation.id\n );\n if (pending || busy(view)) return { done: false, text: null, reason: 'busy' };\n const turn = lastTurn(view, messageId);\n return turn ? { turn, view } : { done: false, text: null, reason: 'no-turn' };\n}\n\nasync function forkAt(\n harness: Harness,\n conversation: Conversation,\n found: { turn: Turn },\n context: Context\n): Promise<Conversation> {\n const ownership = { kind: 'ownerless' } as const;\n if (found.turn.at !== null) return conversation.fork(found.turn.at, { ownership }, context);\n const agent = await conversation.agent(context);\n const change: AgentChange = {\n model: agent.model ?? null,\n thinkingLevel: agent.thinkingLevel,\n extensions: agent.extensions,\n tools: agent.tools,\n instructions: agent.instructions ?? null,\n cwd: agent.cwd ?? null,\n };\n return harness.createConversation({ ownership, agent: pinned(change) }, context);\n}\n\nasync function rewindAgent(\n deps: RewindDeps,\n agentId: string | null,\n messageId: string | null,\n context: Context\n): Promise<Rewound> {\n const { harness, agents, cone, scoops } = deps;\n const scoop = agentId && agents ? agents.state().scoops[agentId] : undefined;\n if (agentId && agents && !scoop && agentId !== agents.activeCone())\n await agents.selectCone(agentId, context);\n const conversation =\n scoop && agents\n ? ((await agents.conversation(agentId as string, context)) as Conversation)\n : cone.cone();\n const found = await turnIn(harness, conversation, messageId, context);\n if ('reason' in found) return found;\n const fork = await forkAt(harness, conversation, found, context);\n const changed =\n scoops && agents && !scoop\n ? await scoops.scoops.rewound(agents.activeCone(), fork, context)\n : { stopped: [], restored: [] };\n const files = found.turn.files;\n await fork.submit(\n { type: 'write', entry: { kind: 'slicc.rewound', data: { turns: 1, ...changed, files } } },\n context\n );\n if (scoop && agents)\n await agents.update((_tx, state) => {\n const record = state.scoops[agentId as string] as ScoopRecord;\n record.conversation = fork.id;\n delete record.title;\n delete record.titled;\n }, context);\n else await cone.switchCone(fork, context);\n const { attachments } = found.turn;\n return {\n done: true,\n text: found.turn.text,\n reason: null,\n ...(attachments.length ? { attachments } : {}),\n };\n}\n\ntype Picture = { type?: string; data?: string; mimeType?: string };\n\nasync function storedImage(\n harness: Harness,\n ref: ImageRef,\n context: Context\n): Promise<StoredImage> {\n const entry = await harness.commit((tx) => tx.entry(ref.entry as EntryId), context);\n const content = entry?.model?.[ref.message]?.content;\n const part = Array.isArray(content) ? (content[ref.part] as Picture | undefined) : undefined;\n return part?.type === 'image' && part.data && part.mimeType\n ? { data: part.data, mimeType: part.mimeType }\n : { data: null, mimeType: null };\n}\n\nasync function contentOf(\n text: string,\n request: SendRequest,\n conversation: Conversation,\n uploads: Uploads | undefined,\n context: Context\n): Promise<UserInput> {\n if (!request.attachments?.length) return scrubText(text);\n const saver = await saverFor(conversation, uploads, context);\n return scrubItems(await userParts(text, request.attachments, saver));\n}\n\nexport function createAgentControl(\n harness: Harness,\n target: Conversation | Cones,\n deliveries?: Deliveries,\n licks?: HostLicks,\n scoops?: HostScoops,\n skills?: Pick<SkillsRuntime, 'expand' | 'commandsFor'>,\n sprinkles?: Pick<SprinklesRuntime, 'send' | 'call'>,\n memory?: Pick<MemoryRuntime, 'save' | 'remove'>,\n freezer?: Pick<FreezerRuntime, 'freeze' | 'newChat' | 'thaw' | 'discard'>,\n changes?: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'>,\n monitor?: Pick<Monitor, 'open' | 'close'>,\n secrets?: () => readonly SecretName[],\n uploads?: Uploads,\n labels?: Pick<LabelsRuntime, 'label'>,\n guests?: Pick<Guests, 'open' | 'revoke'>\n): AgentControl {\n const cone = cones(target);\n const current = () => cone.cone();\n const agents = (target as WithAgents).agents;\n const conversationFor = async (agentId: string | null | undefined, context: Context) => {\n if (!agentId || !agents) return current();\n const found = await agents.conversation(agentId, context);\n if (!found) throw new Error(`There is no agent ${agentId}.`);\n return found;\n };\n const withdraw = async (conversation: Conversation, id: string, context: Context) => {\n const parsed = submissionId(id);\n if (parsed === undefined) return { outcome: 'not_found' as const };\n const result = await harness.abortSubmission(parsed, context, conversation.id);\n if (result === 'aborted') return { outcome: 'withdrawn' as const };\n return {\n outcome: result === 'not_found' ? ('not_found' as const) : ('already_placed' as const),\n };\n };\n let lock: Promise<unknown> = Promise.resolve();\n const serial = <T>(operation: () => Promise<T>): Promise<T> => {\n const run = lock.then(operation, operation);\n lock = run.catch(() => undefined);\n return run;\n };\n const rewindIn = (agentId: string | null, messageId: string | null, context: Context) =>\n serial(() => rewindAgent({ harness, agents, cone, scoops }, agentId, messageId, context));\n const decide = (\n agentId: string | null,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved> =>\n serial(async () => {\n if (!licks) return { done: false, text: null, error: 'this agent has no licks' };\n const action = state === 'confirmed' ? 'confirm' : 'dismiss';\n try {\n const conversation = await conversationFor(agentId, context);\n const text = await licks.licks.decide(conversation, lickId, action, undefined, context);\n await conversation.submit(\n {\n type: 'write',\n entry: {\n kind: LICK_STATE_KIND,\n data: { lick: lickId, state, by: 'user' },\n model: [\n {\n role: 'user',\n content: `The user ${state} lick ${lickId}: ${text}`,\n timestamp: Date.now(),\n },\n ],\n },\n },\n context\n );\n return { done: true, text, error: null };\n } catch (error) {\n return { done: false, text: null, error: (error as Error).message };\n }\n });\n return {\n send(request, context) {\n return serial(async () => {\n if (secrets && request.text.trim() === SECRETS_COMMAND)\n return accepted(async () =>\n (await conversationFor(request.agentId, context)).submit(\n { type: 'write', entry: secretsEntry(secrets()) },\n context\n )\n );\n const response = await accepted(async () => {\n const conversation = await conversationFor(request.agentId, context);\n const expanded = skills\n ? await skills.expand(request.text, context, conversation.id)\n : request.text;\n return conversation.submit(\n {\n type: 'input',\n content: await contentOf(expanded, request, conversation, uploads, context),\n whenBusy: request.whenBusy,\n ...(request.requestId ? { requestId: request.requestId } : {}),\n },\n context\n );\n });\n const id = response.submissionId === null ? undefined : submissionId(response.submissionId);\n if (id !== undefined) deliveries?.expect(id, request.whenBusy);\n return response;\n });\n },\n rewind: (messageId, context) => rewindIn(null, messageId, context),\n rewindAgent: (agentId, messageId, context) => rewindIn(agentId, messageId, context),\n\n async wait(id, context) {\n const parsed = submissionId(id);\n const submission =\n parsed === undefined ? undefined : await harness.submission(parsed, context);\n if (!submission) throw new Error(`unknown submission ${id}`);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) return { status: 'unanswered', text: null, reason: String(reason) };\n const entry = await harness.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return { status: 'done', text: answerText(entry?.model?.[0]?.content), reason: null };\n },\n withdraw: (id, context) => withdraw(current(), id, context),\n ...sprinkleControl(sprinkles),\n ...memoryControl(memory),\n ...freezerControl(freezer),\n ...changesControl(changes),\n ...guestControl(guests),\n ...monitorControl(monitor),\n image: (ref, context) => storedImage(harness, ref, context),\n async unqueue(agentId, id, context) {\n return withdraw(await conversationFor(agentId, context), id, context);\n },\n abort: (context) => current().abort(context),\n compact: (instructions, context) =>\n accepted(() => current().compact(instructions ?? undefined, context)),\n compactAgent: (agentId, instructions, context) =>\n accepted(async () =>\n (await conversationFor(agentId, context)).compact(instructions ?? undefined, context)\n ),\n reset: (handoff, context) => current().reset(handoff ?? undefined, context),\n resolveLick: (lickId, state, context) => decide(null, lickId, state, context),\n resolveAgentLick: (agentId, lickId, state, context) => decide(agentId, lickId, state, context),\n async webhook(name, delivery, context) {\n const delivered = licks\n ? await licks.sources.webhook(\n name,\n {\n ...(delivery.id ? { id: delivery.id } : {}),\n headers: delivery.headers,\n body: delivery.body,\n },\n context\n )\n : false;\n return { delivered };\n },\n async stopAgent(agentId, context) {\n await (await conversationFor(agentId, context)).abort(context);\n },\n async selectCone(agentId, context) {\n await agents?.selectCone(agentId, context);\n },\n createCone(name, context) {\n return created(async () => {\n if (!agents) throw new Error('This agent has a single cone.');\n return agents.createCone(name, context);\n });\n },\n createScoop(parentId, name, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const roles = await scoops.runtime.roles(context);\n const answer = await scoops.scoops.spawn(\n { cone: parentId, name, prompts: [], fromAgent: false, limits: roles.limits },\n context\n );\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return `scoop:${answer.out.trim()}`;\n });\n },\n drop(agentId, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const answer = await scoops.scoops.stop(agentId, false, context);\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return agentId;\n });\n },\n configure: async (change, context) =>\n (await conversationFor(change.agentId, context)).configure(\n {\n ...(change.model ? { model: change.model } : {}),\n ...(change.thinkingLevel ? { thinkingLevel: change.thinkingLevel } : {}),\n },\n context\n ),\n agentCommands: async (agentId) => skills?.commandsFor(agentId) ?? [],\n toolLabel: async (agentId, callIds, context) =>\n labels ? labels.label(agentId, callIds, context) : null,\n decide: noApprovals,\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type AgentChange,\n AssistantEntry,\n type Conversation,\n ConversationBusy,\n type ConversationView,\n type EntryId,\n type EntryRecord,\n type Harness,\n type SubmissionId,\n type UserInput,\n} from '@earendil-works/pi-durable';\nimport { answerText } from './agent.ts';\nimport type { Agents, ScoopRecord } from './agents.ts';\nimport { type ShownAttachment, splitUser, userParts } from './attachments.ts';\nimport type { ChangesRuntime } from './changes/index.ts';\nimport type { Cones } from './cone.ts';\nimport type { Deliveries } from './deliveries.ts';\nimport type { FreezerRuntime } from './freezer/index.ts';\nimport type { Guests } from './guests/index.ts';\nimport type { LickSources, Licks } from './licks/index.ts';\nimport { LICK_STATE_KIND } from './licks/state.ts';\nimport type { MemoryRuntime } from './memory/index.ts';\nimport type { Monitor } from './monitor/index.ts';\nimport type { Scoops, ScoopsRuntime } from './scoops/index.ts';\nimport { pinned, SECRETS_COMMAND, secretsEntry } from './secrets/extension.ts';\nimport type { SecretName } from './secrets/names.ts';\nimport { scrubItems, scrubText } from './secrets/scrub.ts';\nimport type {\n AgentControl,\n Created,\n ImageRef,\n LickResolved,\n OperationError,\n Rewound,\n SendRequest,\n SendResponse,\n StoredImage,\n} from './services.ts';\nimport type { SkillsRuntime } from './skills/index.ts';\nimport type { SprinklesRuntime } from './sprinkles/index.ts';\nimport type { LabelsRuntime } from './titles/labels.ts';\nimport { saverFor, type Uploads } from './uploads.ts';\n\nexport interface HostLicks {\n licks: Licks;\n sources: LickSources;\n}\n\nexport interface HostScoops {\n scoops: Scoops;\n runtime: ScoopsRuntime;\n}\n\ntype WithAgents = { agents?: Agents };\n\nexport function readable(out: string): string {\n const text = out.replace(/^(?:sub)?agent: /, '').trim();\n const sentence = text.charAt(0).toUpperCase() + text.slice(1);\n return /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;\n}\n\nfunction created(run: () => Promise<string>): Promise<Created> {\n return run().then(\n (id) => ({ id, error: null }),\n (error: Error) => ({ id: null, error: error.message })\n );\n}\n\nfunction monitorControl(\n monitor: Pick<Monitor, 'open' | 'close'> | undefined\n): Pick<AgentControl, 'monitorOpen' | 'monitorClose'> {\n return {\n monitorOpen: async (lease, resync, context) =>\n monitor ? monitor.open(lease, resync, context) : { lease: '', ttlMs: 0 },\n monitorClose: async (lease, context) => monitor?.close(lease, context),\n };\n}\n\nconst noApprovals: AgentControl['decide'] = async () => ({\n done: false,\n state: null,\n error: 'This agent has no approvals.',\n});\n\nfunction freezerControl(\n freezer: Pick<FreezerRuntime, 'freeze' | 'newChat' | 'thaw' | 'discard'> | undefined\n): Pick<AgentControl, 'freeze' | 'newChat' | 'thaw' | 'discard'> {\n const needed = () => {\n if (!freezer) throw new Error('This agent has no freezer.');\n return freezer;\n };\n return {\n freeze: (agentId, context) => created(async () => needed().freeze(agentId, context)),\n newChat: (agentId, context) =>\n created(async () => (await needed().newChat(agentId, context)) ?? ''),\n thaw: (id, context) => created(async () => needed().thaw(id, context)),\n discard: (id, context) =>\n created(async () => {\n await needed().discard(id, context);\n return id;\n }),\n };\n}\n\nfunction guestControl(\n guests: Pick<Guests, 'open' | 'revoke'> | undefined\n): Pick<AgentControl, 'guestOpen' | 'guestRevoke'> {\n return {\n guestOpen: async (draft, context) =>\n guests ? guests.open(draft, context) : { seat: null, error: NO_GUESTS },\n guestRevoke: async (id, context) => (guests ? guests.revoke(id, context) : { revoked: false }),\n };\n}\n\nconst NO_GUESTS = 'This agent has no guest seats.';\n\nfunction changesControl(\n changes: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'> | undefined\n): Pick<AgentControl, 'changesOpen' | 'changeAccept' | 'changeRevert'> {\n const needed = () => {\n if (!changes) throw new Error('This agent tracks no changes.');\n return changes;\n };\n const done = (path: string, run: (runtime: NonNullable<typeof changes>) => Promise<void>) =>\n created(async () => {\n await run(needed());\n return path;\n });\n return {\n changesOpen: async (context) => changes?.open(context),\n changeAccept: (path, context) => done(path, (runtime) => runtime.accept(path, context)),\n changeRevert: (path, context) => done(path, (runtime) => runtime.revert(path, context)),\n };\n}\n\nfunction memoryControl(\n memory: Pick<MemoryRuntime, 'save' | 'remove'> | undefined\n): Pick<AgentControl, 'memorySave' | 'memoryRemove'> {\n return {\n async memorySave(draft, context) {\n if (!memory) throw new Error('This agent has no memory.');\n const { id, ...rest } = draft;\n return memory.save({ ...rest, ...(id ? { id } : {}) }, context);\n },\n async memoryRemove(id, context) {\n return { removed: memory ? await memory.remove(id, context) : false };\n },\n };\n}\n\nfunction sprinkleControl(\n sprinkles: Pick<SprinklesRuntime, 'send' | 'call'> | undefined\n): Pick<AgentControl, 'sprinkleSend' | 'sprinkleCall'> {\n return {\n sprinkleSend: async (id, payload, context) =>\n sprinkles ? sprinkles.send(id, payload, context) : { delivered: false },\n async sprinkleCall(id, method, args, context) {\n if (!sprinkles) throw new Error('This agent has no sprinkles.');\n return sprinkles.call(id, method, args, context);\n },\n };\n}\n\nexport function submissionId(value: string): SubmissionId | undefined {\n const id = Number(value);\n return /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(id) ? (id as SubmissionId) : undefined;\n}\n\nfunction operationError(error: unknown): OperationError {\n if (error instanceof ConversationBusy) return { code: 'busy', message: error.message };\n return { code: 'failed', message: error instanceof Error ? error.message : String(error) };\n}\n\nasync function accepted(operation: () => Promise<{ id: number } | number>): Promise<SendResponse> {\n try {\n const result = await operation();\n const id = typeof result === 'number' ? result : result.id;\n return { accepted: true, submissionId: String(id), error: null };\n } catch (error) {\n return { accepted: false, submissionId: null, error: operationError(error) };\n }\n}\n\nfunction cones(target: Conversation | Cones): Cones {\n if ('cone' in target) return target;\n return {\n cone: () => target,\n switchCone: async () => {\n throw new Error('this conversation has no cone to rewind');\n },\n };\n}\n\ninterface Turn {\n text: string;\n attachments: ShownAttachment[];\n at: EntryId | null;\n files: boolean;\n}\n\ntype Block = { type?: string; name?: string };\n\nconst WRITERS = ['write', 'edit', 'bash', 'codemode'];\n\nfunction writesIn(entries: readonly EntryRecord[]): boolean {\n return entries.some((entry) =>\n (entry.model ?? []).some(\n (message) =>\n Array.isArray(message.content) &&\n (message.content as Block[]).some(\n (block) => block.type === 'toolCall' && WRITERS.includes(String(block.name))\n )\n )\n );\n}\n\nfunction lastTurn(view: ConversationView, messageId: string | null): Turn | null {\n const { entries } = view;\n const target = entries.findIndex((entry) => `e${entry.id}` === messageId);\n if (target < 0) return null;\n const turn = entries.slice(0, target).findLastIndex((entry) => entry.kind === 'pi.user');\n if (turn < 0) return null;\n const user = entries[turn] as EntryRecord;\n return {\n ...splitUser(user.model?.[0]?.content),\n at: entries[turn - 1]?.id ?? null,\n files: writesIn(entries.slice(turn)),\n };\n}\n\nfunction busy(view: ConversationView): boolean {\n const live = view.docs['pi.live'] as { run?: unknown } | undefined;\n const inbox = view.docs['pi.inbox'] as { items?: unknown[] } | undefined;\n return Boolean(live?.run) || Boolean(inbox?.items?.length);\n}\n\ntype RewindDeps = {\n harness: Harness;\n agents?: Agents;\n cone: Cones;\n scoops?: HostScoops;\n};\n\nasync function turnIn(\n harness: Harness,\n conversation: Conversation,\n messageId: string | null,\n context: Context\n): Promise<{ turn: Turn; view: ConversationView } | Rewound> {\n const attached = await conversation.viewState(context);\n const view = attached.value;\n attached.dispose();\n const pending = (await harness.inspect(context)).submissions.some(\n (submission) => submission.conversationId === conversation.id\n );\n if (pending || busy(view)) return { done: false, text: null, reason: 'busy' };\n const turn = lastTurn(view, messageId);\n return turn ? { turn, view } : { done: false, text: null, reason: 'no-turn' };\n}\n\nasync function forkAt(\n harness: Harness,\n conversation: Conversation,\n found: { turn: Turn },\n context: Context\n): Promise<Conversation> {\n const ownership = { kind: 'ownerless' } as const;\n if (found.turn.at !== null) return conversation.fork(found.turn.at, { ownership }, context);\n const agent = await conversation.agent(context);\n const change: AgentChange = {\n model: agent.model ?? null,\n thinkingLevel: agent.thinkingLevel,\n extensions: agent.extensions,\n tools: agent.tools,\n instructions: agent.instructions ?? null,\n cwd: agent.cwd ?? null,\n };\n return harness.createConversation({ ownership, agent: pinned(change) }, context);\n}\n\nasync function rewindAgent(\n deps: RewindDeps,\n agentId: string | null,\n messageId: string | null,\n context: Context\n): Promise<Rewound> {\n const { harness, agents, cone, scoops } = deps;\n const scoop = agentId && agents ? agents.state().scoops[agentId] : undefined;\n if (agentId && agents && !scoop && agentId !== agents.activeCone())\n await agents.selectCone(agentId, context);\n const conversation =\n scoop && agents\n ? ((await agents.conversation(agentId as string, context)) as Conversation)\n : cone.cone();\n const found = await turnIn(harness, conversation, messageId, context);\n if ('reason' in found) return found;\n const fork = await forkAt(harness, conversation, found, context);\n const changed =\n scoops && agents && !scoop\n ? await scoops.scoops.rewound(agents.activeCone(), fork, context)\n : { stopped: [], restored: [] };\n const files = found.turn.files;\n await fork.submit(\n { type: 'write', entry: { kind: 'slicc.rewound', data: { turns: 1, ...changed, files } } },\n context\n );\n if (scoop && agents)\n await agents.update((_tx, state) => {\n const record = state.scoops[agentId as string] as ScoopRecord;\n record.conversation = fork.id;\n delete record.title;\n delete record.titled;\n }, context);\n else await cone.switchCone(fork, context);\n const { attachments } = found.turn;\n return {\n done: true,\n text: found.turn.text,\n reason: null,\n ...(attachments.length ? { attachments } : {}),\n };\n}\n\ntype Picture = { type?: string; data?: string; mimeType?: string };\n\nasync function storedImage(\n harness: Harness,\n ref: ImageRef,\n context: Context\n): Promise<StoredImage> {\n const entry = await harness.commit((tx) => tx.entry(ref.entry as EntryId), context);\n const content = entry?.model?.[ref.message]?.content;\n const part = Array.isArray(content) ? (content[ref.part] as Picture | undefined) : undefined;\n return part?.type === 'image' && part.data && part.mimeType\n ? { data: part.data, mimeType: part.mimeType }\n : { data: null, mimeType: null };\n}\n\nasync function contentOf(\n text: string,\n request: SendRequest,\n conversation: Conversation,\n uploads: Uploads | undefined,\n context: Context\n): Promise<UserInput> {\n if (!request.attachments?.length) return scrubText(text);\n const saver = await saverFor(conversation, uploads, context);\n return scrubItems(await userParts(text, request.attachments, saver));\n}\n\nexport function createAgentControl(\n harness: Harness,\n target: Conversation | Cones,\n deliveries?: Deliveries,\n licks?: HostLicks,\n scoops?: HostScoops,\n skills?: Pick<SkillsRuntime, 'expand' | 'commandsFor'>,\n sprinkles?: Pick<SprinklesRuntime, 'send' | 'call'>,\n memory?: Pick<MemoryRuntime, 'save' | 'remove'>,\n freezer?: Pick<FreezerRuntime, 'freeze' | 'newChat' | 'thaw' | 'discard'>,\n changes?: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'>,\n monitor?: Pick<Monitor, 'open' | 'close'>,\n secrets?: () => readonly SecretName[],\n uploads?: Uploads,\n labels?: Pick<LabelsRuntime, 'label'>,\n guests?: Pick<Guests, 'open' | 'revoke'>\n): Omit<AgentControl, 'approvalPolicySet'> {\n const cone = cones(target);\n const current = () => cone.cone();\n const agents = (target as WithAgents).agents;\n const conversationFor = async (agentId: string | null | undefined, context: Context) => {\n if (!agentId || !agents) return current();\n const found = await agents.conversation(agentId, context);\n if (!found) throw new Error(`There is no agent ${agentId}.`);\n return found;\n };\n const withdraw = async (conversation: Conversation, id: string, context: Context) => {\n const parsed = submissionId(id);\n if (parsed === undefined) return { outcome: 'not_found' as const };\n const result = await harness.abortSubmission(parsed, context, conversation.id);\n if (result === 'aborted') return { outcome: 'withdrawn' as const };\n return {\n outcome: result === 'not_found' ? ('not_found' as const) : ('already_placed' as const),\n };\n };\n let lock: Promise<unknown> = Promise.resolve();\n const serial = <T>(operation: () => Promise<T>): Promise<T> => {\n const run = lock.then(operation, operation);\n lock = run.catch(() => undefined);\n return run;\n };\n const rewindIn = (agentId: string | null, messageId: string | null, context: Context) =>\n serial(() => rewindAgent({ harness, agents, cone, scoops }, agentId, messageId, context));\n const decide = (\n agentId: string | null,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved> =>\n serial(async () => {\n if (!licks) return { done: false, text: null, error: 'this agent has no licks' };\n const action = state === 'confirmed' ? 'confirm' : 'dismiss';\n try {\n const conversation = await conversationFor(agentId, context);\n const text = await licks.licks.decide(conversation, lickId, action, undefined, context);\n await conversation.submit(\n {\n type: 'write',\n entry: {\n kind: LICK_STATE_KIND,\n data: { lick: lickId, state, by: 'user' },\n model: [\n {\n role: 'user',\n content: `The user ${state} lick ${lickId}: ${text}`,\n timestamp: Date.now(),\n },\n ],\n },\n },\n context\n );\n return { done: true, text, error: null };\n } catch (error) {\n return { done: false, text: null, error: (error as Error).message };\n }\n });\n return {\n send(request, context) {\n return serial(async () => {\n if (secrets && request.text.trim() === SECRETS_COMMAND)\n return accepted(async () =>\n (await conversationFor(request.agentId, context)).submit(\n { type: 'write', entry: secretsEntry(secrets()) },\n context\n )\n );\n const response = await accepted(async () => {\n const conversation = await conversationFor(request.agentId, context);\n const expanded = skills\n ? await skills.expand(request.text, context, conversation.id)\n : request.text;\n return conversation.submit(\n {\n type: 'input',\n content: await contentOf(expanded, request, conversation, uploads, context),\n whenBusy: request.whenBusy,\n ...(request.requestId ? { requestId: request.requestId } : {}),\n },\n context\n );\n });\n const id = response.submissionId === null ? undefined : submissionId(response.submissionId);\n if (id !== undefined) deliveries?.expect(id, request.whenBusy);\n return response;\n });\n },\n rewind: (messageId, context) => rewindIn(null, messageId, context),\n rewindAgent: (agentId, messageId, context) => rewindIn(agentId, messageId, context),\n\n async wait(id, context) {\n const parsed = submissionId(id);\n const submission =\n parsed === undefined ? undefined : await harness.submission(parsed, context);\n if (!submission) throw new Error(`unknown submission ${id}`);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) return { status: 'unanswered', text: null, reason: String(reason) };\n const entry = await harness.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return { status: 'done', text: answerText(entry?.model?.[0]?.content), reason: null };\n },\n withdraw: (id, context) => withdraw(current(), id, context),\n ...sprinkleControl(sprinkles),\n ...memoryControl(memory),\n ...freezerControl(freezer),\n ...changesControl(changes),\n ...guestControl(guests),\n ...monitorControl(monitor),\n image: (ref, context) => storedImage(harness, ref, context),\n async unqueue(agentId, id, context) {\n return withdraw(await conversationFor(agentId, context), id, context);\n },\n abort: (context) => current().abort(context),\n compact: (instructions, context) =>\n accepted(() => current().compact(instructions ?? undefined, context)),\n compactAgent: (agentId, instructions, context) =>\n accepted(async () =>\n (await conversationFor(agentId, context)).compact(instructions ?? undefined, context)\n ),\n reset: (handoff, context) => current().reset(handoff ?? undefined, context),\n resolveLick: (lickId, state, context) => decide(null, lickId, state, context),\n resolveAgentLick: (agentId, lickId, state, context) => decide(agentId, lickId, state, context),\n async webhook(name, delivery, context) {\n const delivered = licks\n ? await licks.sources.webhook(\n name,\n {\n ...(delivery.id ? { id: delivery.id } : {}),\n headers: delivery.headers,\n body: delivery.body,\n },\n context\n )\n : false;\n return { delivered };\n },\n async stopAgent(agentId, context) {\n await (await conversationFor(agentId, context)).abort(context);\n },\n async selectCone(agentId, context) {\n await agents?.selectCone(agentId, context);\n },\n createCone(name, context) {\n return created(async () => {\n if (!agents) throw new Error('This agent has a single cone.');\n return agents.createCone(name, context);\n });\n },\n createScoop(parentId, name, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const roles = await scoops.runtime.roles(context);\n const answer = await scoops.scoops.spawn(\n { cone: parentId, name, prompts: [], fromAgent: false, limits: roles.limits },\n context\n );\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return `scoop:${answer.out.trim()}`;\n });\n },\n drop(agentId, context) {\n return created(async () => {\n if (!scoops) throw new Error('This agent has no scoops.');\n const answer = await scoops.scoops.stop(agentId, false, context);\n if (answer.code !== 0) throw new Error(readable(answer.out));\n return agentId;\n });\n },\n configure: async (change, context) =>\n (await conversationFor(change.agentId, context)).configure(\n {\n ...(change.model ? { model: change.model } : {}),\n ...(change.thinkingLevel ? { thinkingLevel: change.thinkingLevel } : {}),\n },\n context\n ),\n agentCommands: async (agentId) => skills?.commandsFor(agentId) ?? [],\n toolLabel: async (agentId, callIds, context) =>\n labels ? labels.label(agentId, callIds, context) : null,\n decide: noApprovals,\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }