@ai-ecoverse/slicc-agent 7.4.1 → 7.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 (56) hide show
  1. package/README.md +2 -2
  2. package/dist/agent.js +7 -0
  3. package/dist/agent.js.map +2 -2
  4. package/dist/agents.js +2 -2
  5. package/dist/agents.js.map +2 -2
  6. package/dist/approvals/index.js +8 -3
  7. package/dist/approvals/index.js.map +2 -2
  8. package/dist/approvals/policy.js +2 -2
  9. package/dist/approvals/policy.js.map +2 -2
  10. package/dist/approvals/record.js +2 -2
  11. package/dist/approvals/record.js.map +2 -2
  12. package/dist/checkpoints.js +4 -0
  13. package/dist/checkpoints.js.map +2 -2
  14. package/dist/clock.js +1 -0
  15. package/dist/clock.js.map +7 -0
  16. package/dist/codemode/index.js +2 -2
  17. package/dist/codemode/index.js.map +2 -2
  18. package/dist/cone.js +2 -2
  19. package/dist/cone.js.map +2 -2
  20. package/dist/guests/guard.js +3 -3
  21. package/dist/guests/guard.js.map +2 -2
  22. package/dist/guests/review.js +2 -2
  23. package/dist/guests/review.js.map +2 -2
  24. package/dist/guests/scoop.js +3 -3
  25. package/dist/guests/scoop.js.map +2 -2
  26. package/dist/guests/seats.js +2 -2
  27. package/dist/guests/seats.js.map +2 -2
  28. package/dist/guests/tags.js +2 -2
  29. package/dist/guests/tags.js.map +2 -2
  30. package/dist/index.js.map +2 -2
  31. package/dist/licks/boot.js +2 -2
  32. package/dist/licks/boot.js.map +2 -2
  33. package/dist/licks/licks.js +2 -2
  34. package/dist/licks/licks.js.map +2 -2
  35. package/dist/mcp/allow.js +2 -2
  36. package/dist/mcp/allow.js.map +2 -2
  37. package/dist/memory/extract.js +3 -3
  38. package/dist/memory/extract.js.map +2 -2
  39. package/dist/scoops/service.js +2 -2
  40. package/dist/scoops/service.js.map +2 -2
  41. package/dist/secrets/legacy.js +2 -2
  42. package/dist/secrets/legacy.js.map +2 -2
  43. package/dist/selection.js +2 -2
  44. package/dist/selection.js.map +2 -2
  45. package/dist/sprinkles/sprinkles.js +2 -2
  46. package/dist/sprinkles/sprinkles.js.map +2 -2
  47. package/dist/titles/labels.js +2 -2
  48. package/dist/titles/labels.js.map +2 -2
  49. package/dist/types/agent.d.ts +2 -0
  50. package/dist/types/approvals/index.d.ts +2 -0
  51. package/dist/types/checkpoints.d.ts +3 -0
  52. package/dist/types/clock.d.ts +4 -0
  53. package/dist/types/index.d.ts +1 -0
  54. package/dist/workflow/run.js +2 -2
  55. package/dist/workflow/run.js.map +2 -2
  56. package/package.json +2 -2
package/README.md CHANGED
@@ -26,8 +26,8 @@ agent.transcript.subscribe((view) => render(view));
26
26
  await agent.prompt('What is in /home?');
27
27
  ```
28
28
 
29
- - `openAgent({ models, model, storage?, registry?, settings? })` opens a durable Harness (in memory unless `storage` is given) and its root conversation, and resumes work a previous worker left unfinished.
30
- - `openOpfsSqliteStorage({ directory?, file? })` keeps the session in wasm SQLite ([`@sqlite.org/sqlite-wasm`](https://www.npmjs.com/package/@sqlite.org/sqlite-wasm)) in an `opfs-sahpool` pool, by default `/.slicc/agent/` in OPFS. It needs a dedicated worker. Only one worker holds a pool at a time: the opener takes a Web Lock per directory and waits until every file of the pool can be opened before sqlite-wasm installs it, because a failed install deletes the pool's directory. `openMemorySqliteStorage()` keeps the same database in memory. **Profile marker:** every open, under the pool's lock and before pi durable opens (and migrates) the database, writes `<directory>-profile.json` next to the pool, `/.slicc/agent-profile.json` by default: `{ "durableSchema": 2, "agent": "<version>" }`, with pi durable's `CURRENT_SQLITE_SCHEMA_VERSION` and the agent's version (the `agent` option, or the version in the agent's `package.json`). A `durableSchema` already in the file is never lowered. bios reads it from OPFS before it starts an agent, and refuses one whose pi durable schema is lower (slicc-agent 6.x is 1): the schema version lives in a `durable_schema` table, not in the SQLite header, so the database itself can't be checked without opening it. On pi durable 1.4, opening a database written by an earlier agent migrates it to durable's SQLite schema 2, once and in place; an agent on pi durable 1.3 or earlier (slicc-agent 6.19.13 and earlier) can't open it afterwards, so the agent can't be rolled back past this version. Requests through the Harness are attributed to SLICC (`identity: { attribution: { name: 'SLICC', url: 'https://www.sliccy.com' } }`), which pi-ai sends as OpenRouter's `HTTP-Referer` and `X-OpenRouter-Title`, NVIDIA's `X-BILLING-INVOKE-ORIGIN` and OpenCode's `x-opencode-client`; OpenRouter's preflight allows them from `https://seven.sliccy.ai`, and NVIDIA and OpenCode answer no browser preflight at all, so they go through a CORS-free transport either way. pi durable's crash-loop guard is on with its default: a task cut off 5 times in a row without committing a new state (a crash, a reload or `restart()`, which end the worker without closing the session) is parked instead of started again. The monitor shows it as `blocked: restart limit` with an error alert, "generating of sliccy is parked", whose command is `agent retry <task id>`; run from a terminal, that runs the task again (`Harness.retryTask`). Only the user can retry: an agent's `agent retry` is refused, and so is a task that isn't parked.
29
+ - `openAgent({ models, model, storage?, registry?, settings?, clock? })` opens a durable Harness (in memory unless `storage` is given) and its root conversation, and resumes work a previous worker left unfinished. `clock` (`{ now, setTimer }`) is the Harness clock: task times, sleeps and the timers of its tasks, such as a guest review's expiry, run on it; `attachApprovals` takes the same `now` and `setTimer`. Tests pass a fake one and move it forward.
30
+ - `openOpfsSqliteStorage({ directory?, file? })` keeps the session in wasm SQLite ([`@sqlite.org/sqlite-wasm`](https://www.npmjs.com/package/@sqlite.org/sqlite-wasm)) in an `opfs-sahpool` pool, by default `/.slicc/agent/` in OPFS. It needs a dedicated worker. Only one worker holds a pool at a time: the opener takes a Web Lock per directory and waits until every file of the pool can be opened before sqlite-wasm installs it, because a failed install deletes the pool's directory. `openMemorySqliteStorage()` keeps the same database in memory. **Document bases.** Every session and conversation document the agent defines stores a complete base after 64 deltas (`boundedDeltas` in `src/checkpoints.ts`, the policy pi durable's codemode journal uses), so reading one replays at most 64 changes: with deltas only, a document changed 5,000 times took about 26 ms to read after a reopen, against under 1 ms. Task documents, which end with their task, keep deltas only. **Profile marker:** every open, under the pool's lock and before pi durable opens (and migrates) the database, writes `<directory>-profile.json` next to the pool, `/.slicc/agent-profile.json` by default: `{ "durableSchema": 2, "agent": "<version>" }`, with pi durable's `CURRENT_SQLITE_SCHEMA_VERSION` and the agent's version (the `agent` option, or the version in the agent's `package.json`). A `durableSchema` already in the file is never lowered. bios reads it from OPFS before it starts an agent, and refuses one whose pi durable schema is lower (slicc-agent 6.x is 1): the schema version lives in a `durable_schema` table, not in the SQLite header, so the database itself can't be checked without opening it. On pi durable 1.4, opening a database written by an earlier agent migrates it to durable's SQLite schema 2, once and in place; an agent on pi durable 1.3 or earlier (slicc-agent 6.19.13 and earlier) can't open it afterwards, so the agent can't be rolled back past this version. Requests through the Harness are attributed to SLICC (`identity: { attribution: { name: 'SLICC', url: 'https://www.sliccy.com' } }`), which pi-ai sends as OpenRouter's `HTTP-Referer` and `X-OpenRouter-Title`, NVIDIA's `X-BILLING-INVOKE-ORIGIN` and OpenCode's `x-opencode-client`; OpenRouter's preflight allows them from `https://seven.sliccy.ai`, and NVIDIA and OpenCode answer no browser preflight at all, so they go through a CORS-free transport either way. pi durable's crash-loop guard is on with its default: a task cut off 5 times in a row without committing a new state (a crash, a reload or `restart()`, which end the worker without closing the session) is parked instead of started again. The monitor shows it as `blocked: restart limit` with an error alert, "generating of sliccy is parked", whose command is `agent retry <task id>`; run from a terminal, that runs the task again (`Harness.retryTask`). Only the user can retry: an agent's `agent retry` is refused, and so is a task that isn't parked.
31
31
  - `hostAgent(agent)` serves the agent from one Chord host named `agent` (`serveAgent` in [`src/host.ts`](src/host.ts)). Its remote contracts, in [`src/services.ts`](src/services.ts), are `slicc.agent.control` (send with `whenBusy` `steer`, `followUp` or `reject`, wait, withdraw, abort, compact) and `slicc.agent.transcript` (the conversation's durable view as replicated state, plus `deliveries`: how each user entry was delivered, `run`, `steer` or `follow-up`, derived by the host from where each send it accepted was placed: after a tool result it steered, queued first it's a follow-up or a late steer per its `whenBusy`, placed at once it started a run), `slicc.agent.settings`, `slicc.agent.remote` (remote control), `slicc.agent.pages` (Panels) and `slicc.agent.guest` (Guest seats). `serveConnections(self, host)` hands it every `MessagePort` the page sends, and each port becomes one Chord link whose principal is a seat: `owner` (a page of the owner, with its own Panels), `control` (a follower with Control), `view` (a follower without) or `guest` (a guest seat, with its id, label and epoch), plus the link's number, so every link gets its own objects. The principal is typed (`AgentPrincipal` in [`src/serving.ts`](src/serving.ts)); the host's own code acts as `{ seat: 'host' }`, which no link gets. Each factory picks a variant per seat. Every contract places its objects on the host `agent` (`hostNamed`), so no caller names a host. The contracts take their methods' arguments as one JSON array, and the client in [`src/serving.ts`](src/serving.ts) turns them back into the `AgentControl`, `AgentSettings`, `AgentRemote` and `AgentPages` interfaces and their replicated states, so callers don't see Chord.
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.
package/dist/agent.js CHANGED
@@ -25,6 +25,12 @@ async function prompt(root, text, context) {
25
25
  }
26
26
  const SLICC_ATTRIBUTION = { name: "SLICC", url: "https://www.sliccy.com" };
27
27
  const IDENTITY = { attribution: SLICC_ATTRIBUTION };
28
+ function clocked(clock) {
29
+ return {
30
+ now: () => clock.now(),
31
+ setTimer: (callback, delayMs) => clock.setTimer(callback, delayMs)
32
+ };
33
+ }
28
34
  async function openAgent(options) {
29
35
  const context = options.context ?? BACKGROUND_CONTEXT;
30
36
  const registry = options.registry ?? createRegistry();
@@ -44,6 +50,7 @@ async function openAgent(options) {
44
50
  identity: IDENTITY,
45
51
  ...options.settings ? { settings: options.settings } : {},
46
52
  onReport: (error, info) => reports.record(error, info),
53
+ ...options.clock ? clocked(options.clock) : {},
47
54
  ...options.env ? { env: options.env } : {},
48
55
  ...options.conversationCreated ? { conversationCreated: options.conversationCreated } : {}
49
56
  },
package/dist/agent.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/agent.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Models } from '@earendil-works/pi-ai/models';\nimport {\n AssistantEntry,\n type Conversation,\n createRegistry,\n type EntryId,\n Harness,\n type HarnessOptions,\n MemoryStorage,\n type ModelRef,\n type Registry,\n type Storage,\n} from '@earendil-works/pi-durable';\nimport { type Agents, openAgents } from './agents.ts';\nimport type { Cones } from './cone.ts';\nimport { type GuestToolGate, guestToolGate } from './guests/guard.ts';\nimport { type GuestReviews, guestReviews } from './guests/review.ts';\nimport { type ScoopReviews, scoopReviews } from './guests/scoop.ts';\nimport { createReports, type Reports } from './reports.ts';\n\nexport interface AgentOptions {\n models: Models;\n model: ModelRef;\n storage?: Storage;\n registry?: Registry;\n settings?: HarnessOptions['settings'];\n env?: HarnessOptions['env'];\n conversationCreated?: HarnessOptions['conversationCreated'];\n context?: Context;\n beforeResume?: (harness: Harness, context: Context) => Promise<unknown>;\n}\n\nexport interface Agent extends Cones {\n readonly harness: Harness;\n readonly root: Conversation;\n readonly agents: Agents;\n readonly reviews: GuestReviews;\n readonly gate: GuestToolGate;\n readonly scoopReviews: ScoopReviews;\n readonly reports: Reports;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n prompt(text: string): Promise<string>;\n close(): Promise<void>;\n}\n\ninterface Block {\n type: string;\n text?: string;\n}\n\nexport function answerText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return (content as Block[])\n .filter((block) => block.type === 'text')\n .map((block) => block.text ?? '')\n .join('');\n}\n\nasync function prompt(root: Conversation, text: string, context: Context): Promise<string> {\n const submission = await root.submit({ type: 'input', content: text }, context);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) throw new Error(`unanswered: ${reason}`);\n const entry = await root.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return answerText(entry?.model?.[0]?.content);\n}\n\nexport const SLICC_ATTRIBUTION = { name: 'SLICC', url: 'https://www.sliccy.com' };\nconst IDENTITY = { attribution: SLICC_ATTRIBUTION };\nexport async function openAgent(options: AgentOptions): Promise<Agent> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const registry = options.registry ?? createRegistry();\n let opened: Harness | undefined;\n const gate = guestToolGate(() => opened);\n registry.install(gate.extension);\n const reviews = guestReviews();\n registry.install(reviews.extension);\n const scooped = scoopReviews();\n registry.install(scooped.extension);\n const reports = createReports();\n const harness = await Harness.open(\n options.storage ?? new MemoryStorage(),\n {\n models: options.models,\n registry,\n identity: IDENTITY,\n ...(options.settings ? { settings: options.settings } : {}),\n onReport: (error, info) => reports.record(error, info),\n ...(options.env ? { env: options.env } : {}),\n ...(options.conversationCreated ? { conversationCreated: options.conversationCreated } : {}),\n },\n context\n );\n opened = harness;\n const root = await harness.root(context, { agent: { model: options.model } });\n const agents = await openAgents(harness, root, context);\n await options.beforeResume?.(harness, context);\n harness.resume();\n return {\n harness,\n root,\n agents,\n reviews,\n gate,\n scoopReviews: scooped,\n reports,\n cone: () => agents.cone(),\n switchCone: (conversation, switching) => agents.switchCone(conversation, switching),\n onCone: (listener) => agents.onCone(listener),\n prompt: (text) => prompt(agents.cone(), text, context),\n close: () => harness.close(context),\n };\n}\n"],
5
- "mappings": "AACA,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,OAIK;AACP,SAAsB,kBAAkB;AAExC,SAA6B,qBAAqB;AAClD,SAA4B,oBAAoB;AAChD,SAA4B,oBAAoB;AAChD,SAAS,qBAAmC;AAgCrC,SAAS,WAAW,SAA0B;AACnD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,SAAQ,QACL,OAAO,CAAC,UAAU,MAAM,SAAS,MAAM,EACvC,IAAI,CAAC,UAAU,MAAM,QAAQ,EAAE,EAC/B,KAAK,EAAE;AACZ;AAEA,eAAe,OAAO,MAAoB,MAAc,SAAmC;AACzF,QAAM,aAAa,MAAM,KAAK,OAAO,EAAE,MAAM,SAAS,SAAS,KAAK,GAAG,OAAO;AAC9E,QAAM,UAAU,MAAM,WAAW,KAAK,OAAO;AAC7C,QAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,MAAI,WAAW,OAAW,OAAM,IAAI,MAAM,eAAe,MAAM,EAAE;AACjE,QAAM,QAAQ,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,MAAM,gBAAgB,MAAM,GAAG,OAAO;AACjF,SAAO,WAAW,OAAO,QAAQ,CAAC,GAAG,OAAO;AAC9C;AAEO,MAAM,oBAAoB,EAAE,MAAM,SAAS,KAAK,yBAAyB;AAChF,MAAM,WAAW,EAAE,aAAa,kBAAkB;AAClD,eAAsB,UAAU,SAAuC;AACrE,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,WAAW,QAAQ,YAAY,eAAe;AACpD,MAAI;AACJ,QAAM,OAAO,cAAc,MAAM,MAAM;AACvC,WAAS,QAAQ,KAAK,SAAS;AAC/B,QAAM,UAAU,aAAa;AAC7B,WAAS,QAAQ,QAAQ,SAAS;AAClC,QAAM,UAAU,aAAa;AAC7B,WAAS,QAAQ,QAAQ,SAAS;AAClC,QAAM,UAAU,cAAc;AAC9B,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,QAAQ,WAAW,IAAI,cAAc;AAAA,IACrC;AAAA,MACE,QAAQ,QAAQ;AAAA,MAChB;AAAA,MACA,UAAU;AAAA,MACV,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,MACzD,UAAU,CAAC,OAAO,SAAS,QAAQ,OAAO,OAAO,IAAI;AAAA,MACrD,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,MAC1C,GAAI,QAAQ,sBAAsB,EAAE,qBAAqB,QAAQ,oBAAoB,IAAI,CAAC;AAAA,IAC5F;AAAA,IACA;AAAA,EACF;AACA,WAAS;AACT,QAAM,OAAO,MAAM,QAAQ,KAAK,SAAS,EAAE,OAAO,EAAE,OAAO,QAAQ,MAAM,EAAE,CAAC;AAC5E,QAAM,SAAS,MAAM,WAAW,SAAS,MAAM,OAAO;AACtD,QAAM,QAAQ,eAAe,SAAS,OAAO;AAC7C,UAAQ,OAAO;AACf,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA,MAAM,MAAM,OAAO,KAAK;AAAA,IACxB,YAAY,CAAC,cAAc,cAAc,OAAO,WAAW,cAAc,SAAS;AAAA,IAClF,QAAQ,CAAC,aAAa,OAAO,OAAO,QAAQ;AAAA,IAC5C,QAAQ,CAAC,SAAS,OAAO,OAAO,KAAK,GAAG,MAAM,OAAO;AAAA,IACrD,OAAO,MAAM,QAAQ,MAAM,OAAO;AAAA,EACpC;AACF;",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Models } from '@earendil-works/pi-ai/models';\nimport {\n AssistantEntry,\n type Conversation,\n createRegistry,\n type EntryId,\n Harness,\n type HarnessOptions,\n MemoryStorage,\n type ModelRef,\n type Registry,\n type Storage,\n} from '@earendil-works/pi-durable';\nimport { type Agents, openAgents } from './agents.ts';\nimport type { Clock } from './clock.ts';\nimport type { Cones } from './cone.ts';\nimport { type GuestToolGate, guestToolGate } from './guests/guard.ts';\nimport { type GuestReviews, guestReviews } from './guests/review.ts';\nimport { type ScoopReviews, scoopReviews } from './guests/scoop.ts';\nimport { createReports, type Reports } from './reports.ts';\n\nexport interface AgentOptions {\n models: Models;\n model: ModelRef;\n storage?: Storage;\n registry?: Registry;\n settings?: HarnessOptions['settings'];\n env?: HarnessOptions['env'];\n conversationCreated?: HarnessOptions['conversationCreated'];\n context?: Context;\n beforeResume?: (harness: Harness, context: Context) => Promise<unknown>;\n clock?: Clock;\n}\n\nexport interface Agent extends Cones {\n readonly harness: Harness;\n readonly root: Conversation;\n readonly agents: Agents;\n readonly reviews: GuestReviews;\n readonly gate: GuestToolGate;\n readonly scoopReviews: ScoopReviews;\n readonly reports: Reports;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n prompt(text: string): Promise<string>;\n close(): Promise<void>;\n}\n\ninterface Block {\n type: string;\n text?: string;\n}\n\nexport function answerText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return (content as Block[])\n .filter((block) => block.type === 'text')\n .map((block) => block.text ?? '')\n .join('');\n}\n\nasync function prompt(root: Conversation, text: string, context: Context): Promise<string> {\n const submission = await root.submit({ type: 'input', content: text }, context);\n const settled = await submission.wait(context);\n const { answer, reason } = settled as { answer?: EntryId; reason?: string };\n if (answer === undefined) throw new Error(`unanswered: ${reason}`);\n const entry = await root.commit((tx) => tx.entry(AssistantEntry, answer), context);\n return answerText(entry?.model?.[0]?.content);\n}\n\nexport const SLICC_ATTRIBUTION = { name: 'SLICC', url: 'https://www.sliccy.com' };\nconst IDENTITY = { attribution: SLICC_ATTRIBUTION };\nfunction clocked(clock: Clock): Pick<HarnessOptions, 'now' | 'setTimer'> {\n return {\n now: () => clock.now(),\n setTimer: (callback, delayMs) => clock.setTimer(callback, delayMs),\n };\n}\n\nexport async function openAgent(options: AgentOptions): Promise<Agent> {\n const context = options.context ?? BACKGROUND_CONTEXT;\n const registry = options.registry ?? createRegistry();\n let opened: Harness | undefined;\n const gate = guestToolGate(() => opened);\n registry.install(gate.extension);\n const reviews = guestReviews();\n registry.install(reviews.extension);\n const scooped = scoopReviews();\n registry.install(scooped.extension);\n const reports = createReports();\n const harness = await Harness.open(\n options.storage ?? new MemoryStorage(),\n {\n models: options.models,\n registry,\n identity: IDENTITY,\n ...(options.settings ? { settings: options.settings } : {}),\n onReport: (error, info) => reports.record(error, info),\n ...(options.clock ? clocked(options.clock) : {}),\n ...(options.env ? { env: options.env } : {}),\n ...(options.conversationCreated ? { conversationCreated: options.conversationCreated } : {}),\n },\n context\n );\n opened = harness;\n const root = await harness.root(context, { agent: { model: options.model } });\n const agents = await openAgents(harness, root, context);\n await options.beforeResume?.(harness, context);\n harness.resume();\n return {\n harness,\n root,\n agents,\n reviews,\n gate,\n scoopReviews: scooped,\n reports,\n cone: () => agents.cone(),\n switchCone: (conversation, switching) => agents.switchCone(conversation, switching),\n onCone: (listener) => agents.onCone(listener),\n prompt: (text) => prompt(agents.cone(), text, context),\n close: () => harness.close(context),\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/agents.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  defineDoc
3
3
  } from "@earendil-works/pi-durable";
4
- import { deltasOnly } from "./checkpoints.js";
4
+ import { boundedDeltas } from "./checkpoints.js";
5
5
  import { ConeDoc } from "./cone.js";
6
6
  const FIRST_CONE = "cone";
7
7
  function live(record) {
@@ -9,7 +9,7 @@ function live(record) {
9
9
  }
10
10
  const AgentsDoc = defineDoc({
11
11
  kind: "slicc.agents",
12
- checkpointWhen: deltasOnly,
12
+ checkpointWhen: boundedDeltas,
13
13
  version: 1,
14
14
  scope: "session",
15
15
  initial: () => ({ cones: {}, active: FIRST_CONE, next: 2, scoops: {} })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/agents.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type Conversation,\n type ConversationId,\n defineDoc,\n type Harness,\n type Tx,\n} from '@earendil-works/pi-durable';\nimport { deltasOnly } from './checkpoints.ts';\nimport { ConeDoc } from './cone.ts';\nimport type { RoleMemory } from './memory/store.ts';\n\nexport const FIRST_CONE = 'cone';\n\nexport type ConeRecord = { name: string; conversation: number; title?: string; titled?: number };\n\nexport type Mark = { in: number; at: number };\n\nexport type ScoopRecord = {\n name: string;\n folder: string;\n cone: string;\n conversation: number;\n anchor: number;\n role: string | null;\n memory?: RoleMemory;\n origin?: string;\n context?: { project: boolean; global: boolean };\n kind: 'async' | 'sync';\n parent: string;\n depth: number;\n roots: { write: string[]; read: string[] } | null;\n created: Mark | null;\n dropped: Mark | null;\n gone: boolean;\n frozen?: string;\n removed?: boolean;\n thawedAs?: string;\n title?: string;\n titled?: number;\n};\n\nexport type FrozenRecord = {\n cone: string;\n name: string;\n conversation: number;\n title: string;\n model: string;\n messages: number;\n frozenAt: number;\n scoops: string[];\n v6?: { origin: string; key: string; suffix?: string };\n};\n\nexport type V6Import = {\n at: number;\n origin: string;\n cones: Record<string, string>;\n tails: Record<string, string>;\n};\n\nexport function live(record: ScoopRecord): boolean {\n return record.dropped === null && !record.gone && !record.frozen;\n}\n\nexport type AgentsState = {\n cones: Record<string, ConeRecord>;\n active: string;\n next: number;\n scoops: Record<string, ScoopRecord>;\n frozen?: Record<string, FrozenRecord>;\n frozenNext?: number;\n imports?: Record<string, V6Import>;\n};\n\nexport const AgentsDoc = defineDoc<AgentsState>({\n kind: 'slicc.agents',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ cones: {}, active: FIRST_CONE, next: 2, scoops: {} }),\n});\n\nexport const SCOOP_PREFIX = 'scoop:';\n\nexport function scoopId(folder: string): string {\n return `${SCOOP_PREFIX}${folder}`;\n}\n\nexport function slug(name: string): string {\n const cleaned = name\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/^[-._]+|[-._]+$/g, '')\n .slice(0, 48);\n return cleaned || 'scoop';\n}\n\nexport function freeFolder(state: Pick<AgentsState, 'scoops'>, name: string): string {\n const base = slug(name);\n if (!state.scoops[scoopId(base)]) return base;\n for (let n = 2; ; n++) if (!state.scoops[scoopId(`${base}-${n}`)]) return `${base}-${n}`;\n}\n\nexport interface Agents {\n state(): Readonly<AgentsState>;\n cone(): Conversation;\n activeCone(): string;\n conversation(agentId: string, context: Context): Promise<Conversation | undefined>;\n selectCone(id: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<string>;\n switchCone(conversation: Conversation, context: Context): Promise<void>;\n setConversation(coneId: string, conversation: Conversation, context: Context): Promise<void>;\n update<T>(change: (tx: Tx, state: AgentsState) => T | Promise<T>, context: Context): Promise<T>;\n onChange(listener: (state: Readonly<AgentsState>) => void): () => void;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n}\n\nasync function initial(harness: Harness, root: Conversation, context: Context) {\n return harness.commit(async (tx) => {\n const doc = await tx.doc(AgentsDoc);\n if (!doc.cones[FIRST_CONE]) {\n const legacy = (await tx.doc(ConeDoc)).conversation;\n doc.cones[FIRST_CONE] = { name: 'sliccy', conversation: legacy ?? root.id };\n }\n if (!doc.cones[doc.active]) doc.active = FIRST_CONE;\n return JSON.parse(JSON.stringify(doc)) as AgentsState;\n }, context);\n}\n\nexport async function openAgents(\n harness: Harness,\n root: Conversation,\n context: Context\n): Promise<Agents> {\n let state = await initial(harness, root, context);\n const handles = new Map<number, Conversation>([[root.id, root]]);\n const handle = async (id: number, using: Context) => {\n const known = handles.get(id);\n if (known) return known;\n const found = await harness.conversation(id as ConversationId, using);\n if (found) handles.set(id, found);\n return found;\n };\n let cone =\n (await handle((state.cones[state.active] as ConeRecord).conversation, context)) ?? root;\n const changes = new Set<(state: Readonly<AgentsState>) => void>();\n const cones = new Set<(conversation: Conversation) => void | Promise<void>>();\n const notifyCone = async (next: Conversation) => {\n cone = next;\n await Promise.all([...cones].map((listener) => listener(next)));\n };\n const update: Agents['update'] = async (change, using) => {\n const [result, next] = await harness.commit(async (tx) => {\n const doc = await tx.doc(AgentsDoc);\n const value = await change(tx, doc as AgentsState);\n return [value, JSON.parse(JSON.stringify(doc)) as AgentsState] as const;\n }, using);\n state = next;\n for (const listener of changes) listener(state);\n return result;\n };\n const agents: Agents = {\n state: () => state,\n cone: () => cone,\n activeCone: () => state.active,\n async conversation(agentId, using) {\n const id = state.cones[agentId]?.conversation ?? state.scoops[agentId]?.conversation;\n return id === undefined ? undefined : handle(id, using);\n },\n async selectCone(id, using) {\n const record = state.cones[id];\n if (!record) throw new Error(`no cone ${id}`);\n if (state.active === id) return;\n await update((_tx, doc) => {\n doc.active = id;\n }, using);\n await notifyCone((await handle(record.conversation, using)) as Conversation);\n },\n async createCone(name, using) {\n const wanted = name.trim();\n if (!wanted) throw new Error('A cone needs a name.');\n if (Object.values(state.cones).some((record) => record.name === wanted))\n throw new Error(`A cone named ${wanted} already exists.`);\n const conversation = await harness.createConversation(\n {\n ownership: { kind: 'ownerless' },\n agent: { model: (await cone.agent(using)).model ?? null },\n },\n using\n );\n handles.set(conversation.id, conversation);\n return update((_tx, doc) => {\n const id = `cone-${doc.next}`;\n doc.next += 1;\n doc.cones[id] = { name: wanted, conversation: conversation.id };\n return id;\n }, using);\n },\n switchCone(conversation, using) {\n return agents.setConversation(state.active, conversation, using);\n },\n async setConversation(coneId, conversation, using) {\n handles.set(conversation.id, conversation);\n await update(async (tx, doc) => {\n const cone = doc.cones[coneId] as ConeRecord;\n cone.conversation = conversation.id;\n delete cone.title;\n delete cone.titled;\n if (coneId === doc.active) (await tx.doc(ConeDoc)).conversation = conversation.id;\n }, using);\n if (coneId === state.active) await notifyCone(conversation);\n },\n update,\n onChange(listener) {\n changes.add(listener);\n return () => changes.delete(listener);\n },\n onCone(listener) {\n cones.add(listener);\n return () => cones.delete(listener);\n },\n };\n return agents;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type Conversation,\n type ConversationId,\n defineDoc,\n type Harness,\n type Tx,\n} from '@earendil-works/pi-durable';\nimport { boundedDeltas } from './checkpoints.ts';\nimport { ConeDoc } from './cone.ts';\nimport type { RoleMemory } from './memory/store.ts';\n\nexport const FIRST_CONE = 'cone';\n\nexport type ConeRecord = { name: string; conversation: number; title?: string; titled?: number };\n\nexport type Mark = { in: number; at: number };\n\nexport type ScoopRecord = {\n name: string;\n folder: string;\n cone: string;\n conversation: number;\n anchor: number;\n role: string | null;\n memory?: RoleMemory;\n origin?: string;\n context?: { project: boolean; global: boolean };\n kind: 'async' | 'sync';\n parent: string;\n depth: number;\n roots: { write: string[]; read: string[] } | null;\n created: Mark | null;\n dropped: Mark | null;\n gone: boolean;\n frozen?: string;\n removed?: boolean;\n thawedAs?: string;\n title?: string;\n titled?: number;\n};\n\nexport type FrozenRecord = {\n cone: string;\n name: string;\n conversation: number;\n title: string;\n model: string;\n messages: number;\n frozenAt: number;\n scoops: string[];\n v6?: { origin: string; key: string; suffix?: string };\n};\n\nexport type V6Import = {\n at: number;\n origin: string;\n cones: Record<string, string>;\n tails: Record<string, string>;\n};\n\nexport function live(record: ScoopRecord): boolean {\n return record.dropped === null && !record.gone && !record.frozen;\n}\n\nexport type AgentsState = {\n cones: Record<string, ConeRecord>;\n active: string;\n next: number;\n scoops: Record<string, ScoopRecord>;\n frozen?: Record<string, FrozenRecord>;\n frozenNext?: number;\n imports?: Record<string, V6Import>;\n};\n\nexport const AgentsDoc = defineDoc<AgentsState>({\n kind: 'slicc.agents',\n checkpointWhen: boundedDeltas,\n version: 1,\n scope: 'session',\n initial: () => ({ cones: {}, active: FIRST_CONE, next: 2, scoops: {} }),\n});\n\nexport const SCOOP_PREFIX = 'scoop:';\n\nexport function scoopId(folder: string): string {\n return `${SCOOP_PREFIX}${folder}`;\n}\n\nexport function slug(name: string): string {\n const cleaned = name\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/^[-._]+|[-._]+$/g, '')\n .slice(0, 48);\n return cleaned || 'scoop';\n}\n\nexport function freeFolder(state: Pick<AgentsState, 'scoops'>, name: string): string {\n const base = slug(name);\n if (!state.scoops[scoopId(base)]) return base;\n for (let n = 2; ; n++) if (!state.scoops[scoopId(`${base}-${n}`)]) return `${base}-${n}`;\n}\n\nexport interface Agents {\n state(): Readonly<AgentsState>;\n cone(): Conversation;\n activeCone(): string;\n conversation(agentId: string, context: Context): Promise<Conversation | undefined>;\n selectCone(id: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<string>;\n switchCone(conversation: Conversation, context: Context): Promise<void>;\n setConversation(coneId: string, conversation: Conversation, context: Context): Promise<void>;\n update<T>(change: (tx: Tx, state: AgentsState) => T | Promise<T>, context: Context): Promise<T>;\n onChange(listener: (state: Readonly<AgentsState>) => void): () => void;\n onCone(listener: (conversation: Conversation) => void | Promise<void>): () => void;\n}\n\nasync function initial(harness: Harness, root: Conversation, context: Context) {\n return harness.commit(async (tx) => {\n const doc = await tx.doc(AgentsDoc);\n if (!doc.cones[FIRST_CONE]) {\n const legacy = (await tx.doc(ConeDoc)).conversation;\n doc.cones[FIRST_CONE] = { name: 'sliccy', conversation: legacy ?? root.id };\n }\n if (!doc.cones[doc.active]) doc.active = FIRST_CONE;\n return JSON.parse(JSON.stringify(doc)) as AgentsState;\n }, context);\n}\n\nexport async function openAgents(\n harness: Harness,\n root: Conversation,\n context: Context\n): Promise<Agents> {\n let state = await initial(harness, root, context);\n const handles = new Map<number, Conversation>([[root.id, root]]);\n const handle = async (id: number, using: Context) => {\n const known = handles.get(id);\n if (known) return known;\n const found = await harness.conversation(id as ConversationId, using);\n if (found) handles.set(id, found);\n return found;\n };\n let cone =\n (await handle((state.cones[state.active] as ConeRecord).conversation, context)) ?? root;\n const changes = new Set<(state: Readonly<AgentsState>) => void>();\n const cones = new Set<(conversation: Conversation) => void | Promise<void>>();\n const notifyCone = async (next: Conversation) => {\n cone = next;\n await Promise.all([...cones].map((listener) => listener(next)));\n };\n const update: Agents['update'] = async (change, using) => {\n const [result, next] = await harness.commit(async (tx) => {\n const doc = await tx.doc(AgentsDoc);\n const value = await change(tx, doc as AgentsState);\n return [value, JSON.parse(JSON.stringify(doc)) as AgentsState] as const;\n }, using);\n state = next;\n for (const listener of changes) listener(state);\n return result;\n };\n const agents: Agents = {\n state: () => state,\n cone: () => cone,\n activeCone: () => state.active,\n async conversation(agentId, using) {\n const id = state.cones[agentId]?.conversation ?? state.scoops[agentId]?.conversation;\n return id === undefined ? undefined : handle(id, using);\n },\n async selectCone(id, using) {\n const record = state.cones[id];\n if (!record) throw new Error(`no cone ${id}`);\n if (state.active === id) return;\n await update((_tx, doc) => {\n doc.active = id;\n }, using);\n await notifyCone((await handle(record.conversation, using)) as Conversation);\n },\n async createCone(name, using) {\n const wanted = name.trim();\n if (!wanted) throw new Error('A cone needs a name.');\n if (Object.values(state.cones).some((record) => record.name === wanted))\n throw new Error(`A cone named ${wanted} already exists.`);\n const conversation = await harness.createConversation(\n {\n ownership: { kind: 'ownerless' },\n agent: { model: (await cone.agent(using)).model ?? null },\n },\n using\n );\n handles.set(conversation.id, conversation);\n return update((_tx, doc) => {\n const id = `cone-${doc.next}`;\n doc.next += 1;\n doc.cones[id] = { name: wanted, conversation: conversation.id };\n return id;\n }, using);\n },\n switchCone(conversation, using) {\n return agents.setConversation(state.active, conversation, using);\n },\n async setConversation(coneId, conversation, using) {\n handles.set(conversation.id, conversation);\n await update(async (tx, doc) => {\n const cone = doc.cones[coneId] as ConeRecord;\n cone.conversation = conversation.id;\n delete cone.title;\n delete cone.titled;\n if (coneId === doc.active) (await tx.doc(ConeDoc)).conversation = conversation.id;\n }, using);\n if (coneId === state.active) await notifyCone(conversation);\n },\n update,\n onChange(listener) {\n changes.add(listener);\n return () => changes.delete(listener);\n },\n onCone(listener) {\n cones.add(listener);\n return () => cones.delete(listener);\n },\n };\n return agents;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["cone"]
7
7
  }
@@ -106,10 +106,15 @@ function settledIn(state, id, using) {
106
106
  off = state.subscribe((items) => check(items));
107
107
  });
108
108
  }
109
+ const systemTimer = (callback, delayMs) => {
110
+ const timer = setTimeout(callback, delayMs);
111
+ return () => clearTimeout(timer);
112
+ };
109
113
  async function attachApprovals(options, context = BACKGROUND_CONTEXT) {
110
114
  const { harness } = options;
111
115
  const now = options.now ?? Date.now;
112
116
  const ttl = options.ttlMs ?? APPROVAL_MS;
117
+ const setTimer = options.setTimer ?? systemTimer;
113
118
  const timers = /* @__PURE__ */ new Map();
114
119
  let line = Promise.resolve();
115
120
  let closed = false;
@@ -167,12 +172,12 @@ async function attachApprovals(options, context = BACKGROUND_CONTEXT) {
167
172
  if (timers.has(item.id) || closed) continue;
168
173
  timers.set(
169
174
  item.id,
170
- setTimeout(() => expire(item.id), Math.min(MAX_DELAY, Math.max(0, item.expiresAt - now())))
175
+ setTimer(() => expire(item.id), Math.min(MAX_DELAY, Math.max(0, item.expiresAt - now())))
171
176
  );
172
177
  }
173
178
  for (const [id, timer] of timers)
174
179
  if (!pending.has(id)) {
175
- clearTimeout(timer);
180
+ timer();
176
181
  timers.delete(id);
177
182
  }
178
183
  }
@@ -268,7 +273,7 @@ async function attachApprovals(options, context = BACKGROUND_CONTEXT) {
268
273
  },
269
274
  close() {
270
275
  closed = true;
271
- for (const timer of timers.values()) clearTimeout(timer);
276
+ for (const timer of timers.values()) timer();
272
277
  timers.clear();
273
278
  }
274
279
  };
@@ -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 { DocumentState, 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 scoopTier,\n sudoApprover,\n tierOf,\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 type Decider = DecidedBy | { by: DecidedBy; device?: string };\n\nconst deciderOf = (decider: Decider) => (typeof decider === 'string' ? { by: decider } : decider);\n\nexport interface Approvals {\n readonly state: ReplicatedState<Approval[]>;\n request(draft: ApprovalDraft & { id?: string }, context: Context): Promise<Approval>;\n decide(\n id: string,\n answer: unknown,\n reason: string | null,\n by: Decider,\n context: Context\n ): Promise<ApprovalDecided>;\n settled(id: string, context: Context): Promise<Approval>;\n assign(id: string, reviewer: string, context: Context): Promise<Approval | undefined>;\n withdraw(id: string, reason: string | null, context: Context): Promise<Approval | undefined>;\n withdrawSeat(seat: string, context: Context): Promise<number>;\n expire(id: string, context: Context): Promise<Approval | undefined>;\n close(): void;\n}\n\nexport interface ScoopRouter {\n route(item: Approval): Promise<void>;\n}\n\nexport interface ApprovalsAttach {\n harness: Pick<Harness, 'commit' | 'documentState'>;\n now?: () => number;\n ttlMs?: number;\n judge?: ApprovalJudge;\n scoops?: ScoopRouter;\n guestAlways?: (kind: ApprovalKind) => boolean;\n sudoApprover?: () => unknown;\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 offered = (options: ApprovalsAttach, draft: ApprovalDraft): boolean =>\n guestKind(draft.kind) ? options.guestAlways?.(draft.kind) === true : draft.always === true;\n\nfunction fresh(\n draft: ApprovalDraft & { id?: string },\n at: number,\n ttl: number,\n options: ApprovalsAttach\n): Approval {\n const item = {\n ...draft,\n tier: tierOf(draft.kind, draft.tier, sudoApprover(options.sudoApprover?.())),\n id: draft.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(item.tier))\n mark(item, { state: 'unanswered', by: 'host', reason: REASONS.unknownTier }, at);\n else if (!routable(draft.kind, item.tier))\n mark(item, { state: 'unanswered', by: 'host', reason: REASONS.unroutable }, at);\n return item;\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\nfunction following(\n committed: DocumentState<ApprovalsState>,\n changed: (using: Context, items: Approval[]) => void\n) {\n const items = () => (committed.value as ApprovalsState).items;\n let shown = items();\n return (using: Context) => {\n const now = items();\n if (now === shown) return;\n shown = now;\n changed(using, copy(now));\n };\n}\n\nfunction settledIn(\n state: ReplicatedState<Approval[]>,\n id: string,\n using: Context\n): Promise<Approval> {\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\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 committed = (await harness.documentState(\n ApprovalsDoc,\n context\n )) as DocumentState<ApprovalsState>;\n const follow = following(committed, (using, items) => {\n state.replace(using, items);\n arm(items);\n });\n committed.subscribe((_value, using) => follow(using));\n const serial = <R>(change: (doc: ApprovalsState) => R, using: Context): Promise<R> => {\n const run = line.then(async () => {\n const { result } = await write(change, using);\n follow(using);\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 toScoop = (item: Approval) => {\n if (options.scoops && item.state === 'pending' && scoopTier(item.tier))\n void options.scoops.route(item).catch(() => undefined);\n };\n for (const item of startup.items) toScoop(item);\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 let created = true;\n const made = await serial((doc) => {\n const kept = draft.id && doc.items.find((entry) => entry.id === draft.id);\n if (kept) {\n created = false;\n return copy([kept])[0] as Approval;\n }\n const item = fresh(draft, now(), ttl, options);\n doc.items.push(item);\n return copy([item])[0] as Approval;\n }, using);\n if (created && options.judge && made.state === 'pending' && made.tier === 'agent')\n void judging(options.judge, made, using);\n if (created) toScoop(made);\n return made;\n },\n decide(id, answer, reason, decider, using) {\n const { by, device } = deciderOf(decider);\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 if (device && ruling.settle.by === by) item.device = device;\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) => settledIn(state, id, using),\n assign(id, reviewer, using) {\n return serial((doc) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.state === 'pending' && scoopTier(item.tier)) item.reviewer ??= reviewer;\n return item ? (copy([item])[0] as Approval) : undefined;\n }, using);\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 async expire(id, using) {\n return 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 return item ? (copy([item])[0] as Approval) : undefined;\n }, using);\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 { DocumentState, Harness } from '@earendil-works/pi-durable';\nimport type { Clock } from '../clock.ts';\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 scoopTier,\n sudoApprover,\n tierOf,\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 type Decider = DecidedBy | { by: DecidedBy; device?: string };\n\nconst deciderOf = (decider: Decider) => (typeof decider === 'string' ? { by: decider } : decider);\n\nexport interface Approvals {\n readonly state: ReplicatedState<Approval[]>;\n request(draft: ApprovalDraft & { id?: string }, context: Context): Promise<Approval>;\n decide(\n id: string,\n answer: unknown,\n reason: string | null,\n by: Decider,\n context: Context\n ): Promise<ApprovalDecided>;\n settled(id: string, context: Context): Promise<Approval>;\n assign(id: string, reviewer: string, context: Context): Promise<Approval | undefined>;\n withdraw(id: string, reason: string | null, context: Context): Promise<Approval | undefined>;\n withdrawSeat(seat: string, context: Context): Promise<number>;\n expire(id: string, context: Context): Promise<Approval | undefined>;\n close(): void;\n}\n\nexport interface ScoopRouter {\n route(item: Approval): Promise<void>;\n}\n\nexport interface ApprovalsAttach {\n harness: Pick<Harness, 'commit' | 'documentState'>;\n now?: () => number;\n setTimer?: Clock['setTimer'];\n ttlMs?: number;\n judge?: ApprovalJudge;\n scoops?: ScoopRouter;\n guestAlways?: (kind: ApprovalKind) => boolean;\n sudoApprover?: () => unknown;\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 offered = (options: ApprovalsAttach, draft: ApprovalDraft): boolean =>\n guestKind(draft.kind) ? options.guestAlways?.(draft.kind) === true : draft.always === true;\n\nfunction fresh(\n draft: ApprovalDraft & { id?: string },\n at: number,\n ttl: number,\n options: ApprovalsAttach\n): Approval {\n const item = {\n ...draft,\n tier: tierOf(draft.kind, draft.tier, sudoApprover(options.sudoApprover?.())),\n id: draft.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(item.tier))\n mark(item, { state: 'unanswered', by: 'host', reason: REASONS.unknownTier }, at);\n else if (!routable(draft.kind, item.tier))\n mark(item, { state: 'unanswered', by: 'host', reason: REASONS.unroutable }, at);\n return item;\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\nfunction following(\n committed: DocumentState<ApprovalsState>,\n changed: (using: Context, items: Approval[]) => void\n) {\n const items = () => (committed.value as ApprovalsState).items;\n let shown = items();\n return (using: Context) => {\n const now = items();\n if (now === shown) return;\n shown = now;\n changed(using, copy(now));\n };\n}\n\nfunction settledIn(\n state: ReplicatedState<Approval[]>,\n id: string,\n using: Context\n): Promise<Approval> {\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\nconst systemTimer: Clock['setTimer'] = (callback, delayMs) => {\n const timer = setTimeout(callback, delayMs);\n return () => clearTimeout(timer);\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 setTimer = options.setTimer ?? systemTimer;\n const timers = new Map<string, () => void>();\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 committed = (await harness.documentState(\n ApprovalsDoc,\n context\n )) as DocumentState<ApprovalsState>;\n const follow = following(committed, (using, items) => {\n state.replace(using, items);\n arm(items);\n });\n committed.subscribe((_value, using) => follow(using));\n const serial = <R>(change: (doc: ApprovalsState) => R, using: Context): Promise<R> => {\n const run = line.then(async () => {\n const { result } = await write(change, using);\n follow(using);\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 setTimer(() => 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 timer();\n timers.delete(id);\n }\n }\n arm(startup.items);\n const toScoop = (item: Approval) => {\n if (options.scoops && item.state === 'pending' && scoopTier(item.tier))\n void options.scoops.route(item).catch(() => undefined);\n };\n for (const item of startup.items) toScoop(item);\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 let created = true;\n const made = await serial((doc) => {\n const kept = draft.id && doc.items.find((entry) => entry.id === draft.id);\n if (kept) {\n created = false;\n return copy([kept])[0] as Approval;\n }\n const item = fresh(draft, now(), ttl, options);\n doc.items.push(item);\n return copy([item])[0] as Approval;\n }, using);\n if (created && options.judge && made.state === 'pending' && made.tier === 'agent')\n void judging(options.judge, made, using);\n if (created) toScoop(made);\n return made;\n },\n decide(id, answer, reason, decider, using) {\n const { by, device } = deciderOf(decider);\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 if (device && ruling.settle.by === by) item.device = device;\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) => settledIn(state, id, using),\n assign(id, reviewer, using) {\n return serial((doc) => {\n const item = doc.items.find((entry) => entry.id === id);\n if (item?.state === 'pending' && scoopTier(item.tier)) item.reviewer ??= reviewer;\n return item ? (copy([item])[0] as Approval) : undefined;\n }, using);\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 async expire(id, using) {\n return 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 return item ? (copy([item])[0] as Approval) : undefined;\n }, using);\n },\n close() {\n closed = true;\n for (const timer of timers.values()) timer();\n timers.clear();\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,7 +3,7 @@ import {
3
3
  } from "@earendil-works/chord";
4
4
  import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
5
5
  import { defineDoc } from "@earendil-works/pi-durable";
6
- import { deltasOnly } from "../checkpoints.js";
6
+ import { boundedDeltas } from "../checkpoints.js";
7
7
  import {
8
8
  changedGuests,
9
9
  DEFAULT_GUEST_POLICY
@@ -14,7 +14,7 @@ const DEFAULT_APPROVAL_POLICY = {
14
14
  };
15
15
  const ApprovalPolicyDoc = defineDoc({
16
16
  kind: "slicc.approval-policy",
17
- checkpointWhen: deltasOnly,
17
+ checkpointWhen: boundedDeltas,
18
18
  version: 1,
19
19
  scope: "session",
20
20
  initial: () => ({ overrides: {} })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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 { deltasOnly } from '../checkpoints.ts';\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 checkpointWhen: deltasOnly,\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,SAAS,kBAAkB;AAC3B;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,gBAAgB;AAAA,EAChB,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;",
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 { boundedDeltas } from '../checkpoints.ts';\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 checkpointWhen: boundedDeltas,\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": "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",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
1
  import { defineDoc } from "@earendil-works/pi-durable";
2
- import { deltasOnly } from "../checkpoints.js";
2
+ import { boundedDeltas } from "../checkpoints.js";
3
3
  const ApprovalsDoc = defineDoc({
4
4
  kind: "slicc.approvals",
5
- checkpointWhen: deltasOnly,
5
+ checkpointWhen: boundedDeltas,
6
6
  version: 1,
7
7
  scope: "session",
8
8
  initial: () => ({ items: [] })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/approvals/record.ts"],
4
- "sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\nimport { deltasOnly } from '../checkpoints.ts';\n\nexport type ApprovalKind = 'guest-message' | 'guest-tool' | 'sudo';\n\nexport type ApprovalState = 'pending' | 'approved' | 'rejected' | 'unanswered' | 'withdrawn';\n\nexport type ApprovalTier = 'user' | 'agent' | 'cone' | `scoop:${string}`;\n\nexport type ApprovalAnswer = 'once' | 'always' | 'deny';\n\nexport type DecidedBy = 'owner' | 'follower' | 'judge' | 'host' | `scoop:${string}`;\n\nexport type ApprovalRequester = {\n kind: 'guest' | 'agent' | 'user';\n name: string;\n seat?: string;\n agentId?: string;\n where?: string;\n device?: string;\n};\n\nexport type ApprovalVerdict = {\n decision: 'allow' | 'deny' | 'unsure';\n reason: string;\n risk?: number;\n aligned?: number;\n model?: string;\n};\n\nexport type ApprovalAttachment = { name: string; mimeType: string; size: number };\n\nexport type GuestMessageDetail = { text: string; attachments: ApprovalAttachment[] };\n\nexport type GuestToolDetail = { tool: string; summary: string; arguments?: string; task?: number };\n\nexport type SudoDetail = {\n command: string;\n target?: string;\n cwd?: string;\n alwaysScope?: 'cwd';\n};\n\ntype Base = {\n id: string;\n seat?: string;\n agentId: string;\n requester: ApprovalRequester;\n tier: string;\n state: ApprovalState;\n decidedBy: DecidedBy | null;\n device?: string;\n answer?: 'once' | 'always';\n reviewer?: string;\n reason?: string;\n verdict?: ApprovalVerdict;\n always: boolean;\n createdAt: number;\n expiresAt: number;\n decidedAt?: number;\n};\n\nexport type GuestMessageApproval = Base & { kind: 'guest-message'; detail: GuestMessageDetail };\nexport type GuestToolApproval = Base & { kind: 'guest-tool'; detail: GuestToolDetail };\nexport type SudoApproval = Base & { kind: 'sudo'; detail: SudoDetail };\n\nexport type Approval = GuestMessageApproval | GuestToolApproval | SudoApproval;\n\ntype DraftOf<T extends Approval> = Pick<T, 'kind' | 'detail' | 'agentId' | 'requester'> & {\n seat?: string;\n tier: string;\n always?: boolean;\n expiresAt?: number;\n};\n\nexport type ApprovalDraft =\n | DraftOf<GuestMessageApproval>\n | DraftOf<GuestToolApproval>\n | DraftOf<SudoApproval>;\n\nexport type ApprovalsState = { items: Approval[] };\n\nexport const ApprovalsDoc = defineDoc<ApprovalsState>({\n kind: 'slicc.approvals',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ items: [] }),\n});\n\nexport const APPROVAL_MS = 300_000;\nexport const APPROVALS_KEPT = 100;\nexport const MAX_DELAY = 2_147_483_647;\nexport const REASON_LENGTH = 500;\n\nexport const REASONS = {\n timeout: 'timeout',\n unknownTier: 'unknown-tier',\n unroutable: 'unroutable',\n unknownAnswer: 'unknown-answer',\n restart: 'restart',\n revoked: 'revoked',\n aborted: 'aborted',\n disconnected: 'disconnected',\n noScoop: 'no-scoop',\n scoopStopped: 'scoop-stopped',\n scoopAsked: 'scoop-asked',\n scoopUndecided: 'scoop-undecided',\n} as const;\n\nexport const guestKind = (kind: ApprovalKind): boolean => kind !== 'sudo';\n\nexport const scoopTier = (tier: unknown): tier is `scoop:${string}` =>\n typeof tier === 'string' && /^scoop:\\S+$/.test(tier);\n\nexport function knownTier(tier: unknown): tier is ApprovalTier {\n if (tier === 'user' || tier === 'agent' || tier === 'cone') return true;\n return scoopTier(tier);\n}\n\nexport function routable(kind: ApprovalKind, tier: ApprovalTier): boolean {\n return !(kind === 'guest-tool' && tier === 'cone');\n}\n\nexport function answerOf(value: unknown): ApprovalAnswer | null {\n return value === 'once' || value === 'always' || value === 'deny' ? value : null;\n}\n\nexport const personal = (by: DecidedBy): boolean => by === 'owner' || by === 'follower';\n\nexport function answerer(by: DecidedBy, item: Pick<Approval, 'tier' | 'reviewer'>): boolean {\n if (personal(by)) return item.tier === 'user';\n if (by === 'judge') return item.tier === 'agent';\n if (scoopTier(by)) return scoopTier(item.tier) && item.reviewer === by;\n return true;\n}\n\nexport type SudoApprover = 'user' | 'agent';\n\nexport const sudoApprover = (value: unknown): SudoApprover =>\n value === 'agent' ? 'agent' : 'user';\n\nexport const tierOf = (kind: ApprovalKind, tier: string, sudo: SudoApprover): string =>\n kind === 'sudo' ? sudo : tier;\n\nexport function gone(state: ApprovalState): string {\n if (state === 'unanswered') return 'This request timed out.';\n if (state === 'withdrawn') return 'This request was withdrawn.';\n return 'Someone else already answered this.';\n}\n\nexport function cleanReason(reason: unknown): string | undefined {\n if (typeof reason !== 'string') return undefined;\n const text = reason.trim().slice(0, REASON_LENGTH);\n return text || undefined;\n}\n\nconst decided = (item: Approval) => item.decidedAt ?? item.createdAt;\n\nexport function prune(items: Approval[], kept = APPROVALS_KEPT): Approval[] {\n const settled = items.filter((item) => item.state !== 'pending');\n settled.sort((a, b) => decided(a) - decided(b));\n const drop = new Set(settled.slice(0, Math.max(0, settled.length - kept)));\n return items.filter((item) => !drop.has(item));\n}\n\nexport type Settle = { state: Exclude<ApprovalState, 'pending'>; by: DecidedBy; reason?: string };\n\nexport type Ruling = { error?: string; settle?: Settle; answer?: 'once' | 'always' };\n\nconst why = (reason: string | undefined) => (reason ? { reason } : {});\n\nexport function ruled(\n item: Approval,\n answer: unknown,\n reason: unknown,\n by: DecidedBy,\n at: number\n): Ruling {\n if (item.state !== 'pending') return { error: gone(item.state) };\n if (item.expiresAt <= at)\n return {\n error: gone('unanswered'),\n settle: { state: 'unanswered', by: 'host', reason: REASONS.timeout },\n };\n if (!answerer(by, item)) return { error: \"This request isn't yours to answer.\" };\n const known = answerOf(answer);\n if (known === 'always' && scoopTier(by)) return { error: 'A scoop answers once or deny.' };\n if (known === 'always' && !item.always)\n return { error: \"Always isn't offered for this request.\" };\n if (!known) return { settle: { state: 'rejected', by, reason: REASONS.unknownAnswer } };\n const given = why(cleanReason(reason));\n if (known === 'deny') return { settle: { state: 'rejected', by, ...given } };\n return { settle: { state: 'approved', by, ...given }, answer: known };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { defineDoc } from '@earendil-works/pi-durable';\nimport { boundedDeltas } from '../checkpoints.ts';\n\nexport type ApprovalKind = 'guest-message' | 'guest-tool' | 'sudo';\n\nexport type ApprovalState = 'pending' | 'approved' | 'rejected' | 'unanswered' | 'withdrawn';\n\nexport type ApprovalTier = 'user' | 'agent' | 'cone' | `scoop:${string}`;\n\nexport type ApprovalAnswer = 'once' | 'always' | 'deny';\n\nexport type DecidedBy = 'owner' | 'follower' | 'judge' | 'host' | `scoop:${string}`;\n\nexport type ApprovalRequester = {\n kind: 'guest' | 'agent' | 'user';\n name: string;\n seat?: string;\n agentId?: string;\n where?: string;\n device?: string;\n};\n\nexport type ApprovalVerdict = {\n decision: 'allow' | 'deny' | 'unsure';\n reason: string;\n risk?: number;\n aligned?: number;\n model?: string;\n};\n\nexport type ApprovalAttachment = { name: string; mimeType: string; size: number };\n\nexport type GuestMessageDetail = { text: string; attachments: ApprovalAttachment[] };\n\nexport type GuestToolDetail = { tool: string; summary: string; arguments?: string; task?: number };\n\nexport type SudoDetail = {\n command: string;\n target?: string;\n cwd?: string;\n alwaysScope?: 'cwd';\n};\n\ntype Base = {\n id: string;\n seat?: string;\n agentId: string;\n requester: ApprovalRequester;\n tier: string;\n state: ApprovalState;\n decidedBy: DecidedBy | null;\n device?: string;\n answer?: 'once' | 'always';\n reviewer?: string;\n reason?: string;\n verdict?: ApprovalVerdict;\n always: boolean;\n createdAt: number;\n expiresAt: number;\n decidedAt?: number;\n};\n\nexport type GuestMessageApproval = Base & { kind: 'guest-message'; detail: GuestMessageDetail };\nexport type GuestToolApproval = Base & { kind: 'guest-tool'; detail: GuestToolDetail };\nexport type SudoApproval = Base & { kind: 'sudo'; detail: SudoDetail };\n\nexport type Approval = GuestMessageApproval | GuestToolApproval | SudoApproval;\n\ntype DraftOf<T extends Approval> = Pick<T, 'kind' | 'detail' | 'agentId' | 'requester'> & {\n seat?: string;\n tier: string;\n always?: boolean;\n expiresAt?: number;\n};\n\nexport type ApprovalDraft =\n | DraftOf<GuestMessageApproval>\n | DraftOf<GuestToolApproval>\n | DraftOf<SudoApproval>;\n\nexport type ApprovalsState = { items: Approval[] };\n\nexport const ApprovalsDoc = defineDoc<ApprovalsState>({\n kind: 'slicc.approvals',\n checkpointWhen: boundedDeltas,\n version: 1,\n scope: 'session',\n initial: () => ({ items: [] }),\n});\n\nexport const APPROVAL_MS = 300_000;\nexport const APPROVALS_KEPT = 100;\nexport const MAX_DELAY = 2_147_483_647;\nexport const REASON_LENGTH = 500;\n\nexport const REASONS = {\n timeout: 'timeout',\n unknownTier: 'unknown-tier',\n unroutable: 'unroutable',\n unknownAnswer: 'unknown-answer',\n restart: 'restart',\n revoked: 'revoked',\n aborted: 'aborted',\n disconnected: 'disconnected',\n noScoop: 'no-scoop',\n scoopStopped: 'scoop-stopped',\n scoopAsked: 'scoop-asked',\n scoopUndecided: 'scoop-undecided',\n} as const;\n\nexport const guestKind = (kind: ApprovalKind): boolean => kind !== 'sudo';\n\nexport const scoopTier = (tier: unknown): tier is `scoop:${string}` =>\n typeof tier === 'string' && /^scoop:\\S+$/.test(tier);\n\nexport function knownTier(tier: unknown): tier is ApprovalTier {\n if (tier === 'user' || tier === 'agent' || tier === 'cone') return true;\n return scoopTier(tier);\n}\n\nexport function routable(kind: ApprovalKind, tier: ApprovalTier): boolean {\n return !(kind === 'guest-tool' && tier === 'cone');\n}\n\nexport function answerOf(value: unknown): ApprovalAnswer | null {\n return value === 'once' || value === 'always' || value === 'deny' ? value : null;\n}\n\nexport const personal = (by: DecidedBy): boolean => by === 'owner' || by === 'follower';\n\nexport function answerer(by: DecidedBy, item: Pick<Approval, 'tier' | 'reviewer'>): boolean {\n if (personal(by)) return item.tier === 'user';\n if (by === 'judge') return item.tier === 'agent';\n if (scoopTier(by)) return scoopTier(item.tier) && item.reviewer === by;\n return true;\n}\n\nexport type SudoApprover = 'user' | 'agent';\n\nexport const sudoApprover = (value: unknown): SudoApprover =>\n value === 'agent' ? 'agent' : 'user';\n\nexport const tierOf = (kind: ApprovalKind, tier: string, sudo: SudoApprover): string =>\n kind === 'sudo' ? sudo : tier;\n\nexport function gone(state: ApprovalState): string {\n if (state === 'unanswered') return 'This request timed out.';\n if (state === 'withdrawn') return 'This request was withdrawn.';\n return 'Someone else already answered this.';\n}\n\nexport function cleanReason(reason: unknown): string | undefined {\n if (typeof reason !== 'string') return undefined;\n const text = reason.trim().slice(0, REASON_LENGTH);\n return text || undefined;\n}\n\nconst decided = (item: Approval) => item.decidedAt ?? item.createdAt;\n\nexport function prune(items: Approval[], kept = APPROVALS_KEPT): Approval[] {\n const settled = items.filter((item) => item.state !== 'pending');\n settled.sort((a, b) => decided(a) - decided(b));\n const drop = new Set(settled.slice(0, Math.max(0, settled.length - kept)));\n return items.filter((item) => !drop.has(item));\n}\n\nexport type Settle = { state: Exclude<ApprovalState, 'pending'>; by: DecidedBy; reason?: string };\n\nexport type Ruling = { error?: string; settle?: Settle; answer?: 'once' | 'always' };\n\nconst why = (reason: string | undefined) => (reason ? { reason } : {});\n\nexport function ruled(\n item: Approval,\n answer: unknown,\n reason: unknown,\n by: DecidedBy,\n at: number\n): Ruling {\n if (item.state !== 'pending') return { error: gone(item.state) };\n if (item.expiresAt <= at)\n return {\n error: gone('unanswered'),\n settle: { state: 'unanswered', by: 'host', reason: REASONS.timeout },\n };\n if (!answerer(by, item)) return { error: \"This request isn't yours to answer.\" };\n const known = answerOf(answer);\n if (known === 'always' && scoopTier(by)) return { error: 'A scoop answers once or deny.' };\n if (known === 'always' && !item.always)\n return { error: \"Always isn't offered for this request.\" };\n if (!known) return { settle: { state: 'rejected', by, reason: REASONS.unknownAnswer } };\n const given = why(cleanReason(reason));\n if (known === 'deny') return { settle: { state: 'rejected', by, ...given } };\n return { settle: { state: 'approved', by, ...given }, answer: known };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,9 @@
1
+ const BASE_EVERY = 64;
1
2
  const deltasOnly = () => false;
3
+ const boundedDeltas = (_value, _ops, info) => info.deltasSinceBase >= BASE_EVERY;
2
4
  export {
5
+ BASE_EVERY,
6
+ boundedDeltas,
3
7
  deltasOnly
4
8
  };
5
9
  //# sourceMappingURL=checkpoints.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/checkpoints.ts"],
4
- "sourcesContent": ["export const deltasOnly = (): boolean => false;\n"],
5
- "mappings": "AAAO,MAAM,aAAa,MAAe;",
4
+ "sourcesContent": ["import type { CheckpointInfo } from '@earendil-works/pi-durable';\n\nexport const BASE_EVERY = 64;\n\nexport const deltasOnly = (): boolean => false;\n\nexport const boundedDeltas = (_value: unknown, _ops: unknown, info: CheckpointInfo): boolean =>\n info.deltasSinceBase >= BASE_EVERY;\n"],
5
+ "mappings": "AAEO,MAAM,aAAa;AAEnB,MAAM,aAAa,MAAe;AAElC,MAAM,gBAAgB,CAAC,QAAiB,MAAe,SAC5D,KAAK,mBAAmB;",
6
6
  "names": []
7
7
  }
package/dist/clock.js ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=clock.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -11,7 +11,7 @@ import {
11
11
  defineExtension,
12
12
  defineTool
13
13
  } from "@earendil-works/pi-durable";
14
- import { deltasOnly } from "../checkpoints.js";
14
+ import { boundedDeltas } from "../checkpoints.js";
15
15
  import { scrubItems } from "../secrets/scrub.js";
16
16
  const CODEMODE = "codemode";
17
17
  const DEFAULT_MAX_OUTPUT_TOKENS = 1e4;
@@ -19,7 +19,7 @@ const MEMORY_LIMIT_BYTES = 256 * 1024 * 1024;
19
19
  const CHARS_PER_TOKEN = 4;
20
20
  const CodemodeDoc = defineDoc({
21
21
  kind: "slicc.codemode",
22
- checkpointWhen: deltasOnly,
22
+ checkpointWhen: boundedDeltas,
23
23
  version: 1,
24
24
  scope: "conversation",
25
25
  history: "rewindable",