@ai-ecoverse/slicc-agent 7.4.1 → 7.4.2

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 (45) hide show
  1. package/README.md +1 -1
  2. package/dist/agents.js +2 -2
  3. package/dist/agents.js.map +2 -2
  4. package/dist/approvals/policy.js +2 -2
  5. package/dist/approvals/policy.js.map +2 -2
  6. package/dist/approvals/record.js +2 -2
  7. package/dist/approvals/record.js.map +2 -2
  8. package/dist/checkpoints.js +4 -0
  9. package/dist/checkpoints.js.map +2 -2
  10. package/dist/codemode/index.js +2 -2
  11. package/dist/codemode/index.js.map +2 -2
  12. package/dist/cone.js +2 -2
  13. package/dist/cone.js.map +2 -2
  14. package/dist/guests/guard.js +3 -3
  15. package/dist/guests/guard.js.map +2 -2
  16. package/dist/guests/review.js +2 -2
  17. package/dist/guests/review.js.map +2 -2
  18. package/dist/guests/scoop.js +3 -3
  19. package/dist/guests/scoop.js.map +2 -2
  20. package/dist/guests/seats.js +2 -2
  21. package/dist/guests/seats.js.map +2 -2
  22. package/dist/guests/tags.js +2 -2
  23. package/dist/guests/tags.js.map +2 -2
  24. package/dist/licks/boot.js +2 -2
  25. package/dist/licks/boot.js.map +2 -2
  26. package/dist/licks/licks.js +2 -2
  27. package/dist/licks/licks.js.map +2 -2
  28. package/dist/mcp/allow.js +2 -2
  29. package/dist/mcp/allow.js.map +2 -2
  30. package/dist/memory/extract.js +3 -3
  31. package/dist/memory/extract.js.map +2 -2
  32. package/dist/scoops/service.js +2 -2
  33. package/dist/scoops/service.js.map +2 -2
  34. package/dist/secrets/legacy.js +2 -2
  35. package/dist/secrets/legacy.js.map +2 -2
  36. package/dist/selection.js +2 -2
  37. package/dist/selection.js.map +2 -2
  38. package/dist/sprinkles/sprinkles.js +2 -2
  39. package/dist/sprinkles/sprinkles.js.map +2 -2
  40. package/dist/titles/labels.js +2 -2
  41. package/dist/titles/labels.js.map +2 -2
  42. package/dist/types/checkpoints.d.ts +3 -0
  43. package/dist/workflow/run.js +2 -2
  44. package/dist/workflow/run.js.map +2 -2
  45. package/package.json +2 -2
@@ -5,7 +5,7 @@ import {
5
5
  defineTask,
6
6
  hook
7
7
  } from "@earendil-works/pi-durable";
8
- import { deltasOnly } from "../checkpoints.js";
8
+ import { boundedDeltas } from "../checkpoints.js";
9
9
  import { MAX_BYTES, parseMemory, redactSecrets, serializeMemory } from "./format.js";
10
10
  import { whoIs } from "./prompt.js";
11
11
  import { placeOf } from "./store.js";
@@ -14,7 +14,7 @@ const TRANSCRIPT_CHARS = 6e4;
14
14
  const DEFAULT_IDLE_MINUTES = 30;
15
15
  const ExtractDoc = defineDoc({
16
16
  kind: "slicc.memory-extract",
17
- checkpointWhen: deltasOnly,
17
+ checkpointWhen: boundedDeltas,
18
18
  version: 1,
19
19
  scope: "session",
20
20
  initial: () => ({ cursors: {}, pending: {} })
@@ -211,7 +211,7 @@ function settingsReader(ready) {
211
211
  const IDLE_TASK = "slicc.memory-idle";
212
212
  const IdleDoc = defineDoc({
213
213
  kind: "slicc.memory-idle",
214
- checkpointWhen: deltasOnly,
214
+ checkpointWhen: boundedDeltas,
215
215
  version: 1,
216
216
  scope: "session",
217
217
  initial: () => ({ tasks: {} })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/memory/extract.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Message } from '@earendil-works/pi-ai';\nimport {\n CompactionTask,\n type ConversationId,\n defineDoc,\n defineTask,\n type EntryId,\n hook,\n type ModelRef,\n type TaskId,\n} from '@earendil-works/pi-durable';\nimport { deltasOnly } from '../checkpoints.ts';\nimport type { MemoryTag } from './format.ts';\nimport { MAX_BYTES, parseMemory, redactSecrets, serializeMemory } from './format.ts';\nimport { type Host, whoIs } from './prompt.ts';\nimport { type Place, placeOf } from './store.ts';\n\nexport const EXTRACT_TASK = 'slicc.memory-extract';\nexport const TRANSCRIPT_CHARS = 60_000;\nexport const DEFAULT_IDLE_MINUTES = 30;\n\nexport type ExtractState = {\n cursors: Record<string, number>;\n pending: Record<string, number>;\n};\n\nexport const ExtractDoc = defineDoc<ExtractState>({\n kind: 'slicc.memory-extract',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ cursors: {}, pending: {} }),\n});\n\nexport type ExtractSettings = { extract: boolean; model: ModelRef | null; idleMinutes: number };\n\nexport function extractSettings(text: string | undefined): ExtractSettings {\n let memory: { extract?: unknown; extractModel?: unknown; idleMinutes?: unknown } = {};\n try {\n const parsed = JSON.parse(text ?? '{}') as { memory?: typeof memory };\n memory = parsed.memory && typeof parsed.memory === 'object' ? parsed.memory : {};\n } catch {\n memory = {};\n }\n const named = typeof memory.extractModel === 'string' ? memory.extractModel.split('/') : [];\n const model =\n named.length >= 2 && named[0] && named.slice(1).join('/')\n ? { provider: named[0], modelId: named.slice(1).join('/') }\n : null;\n const idle =\n typeof memory.idleMinutes === 'number' && memory.idleMinutes >= 0\n ? memory.idleMinutes\n : DEFAULT_IDLE_MINUTES;\n return { extract: memory.extract !== false, model, idleMinutes: idle };\n}\n\nfunction textOf(message: Message): string {\n const content = (message as { content?: unknown }).content;\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content\n .map((part: { type?: string; text?: string; name?: string }) =>\n part.type === 'text'\n ? (part.text ?? '')\n : part.type === 'toolCall'\n ? `[tool ${part.name}]`\n : ''\n )\n .filter(Boolean)\n .join('\\n');\n}\n\nexport function transcriptOf(messages: readonly Message[]): string {\n const lines: string[] = [];\n for (const message of messages) {\n if (message.role !== 'user' && message.role !== 'assistant') continue;\n const text = textOf(message).trim();\n if (text) lines.push(`${message.role === 'user' ? 'User' : 'Agent'}: ${text}`);\n }\n const joined = redactSecrets(lines.join('\\n\\n')).text;\n return joined.length > TRANSCRIPT_CHARS ? joined.slice(-TRANSCRIPT_CHARS) : joined;\n}\n\nexport const EXTRACT_PROMPT = [\n 'You keep the long-term memory of a personal agent. Read the conversation excerpt and the current memory file, and propose memory entries worth keeping across conversations:',\n '- who the user is (tag \"user\"), how they want the agent to work, including corrections (tag \"feedback\"), and durable facts about their projects (tag \"project\");',\n '- only facts that will still matter in a later conversation; nothing about this one task, no transcripts, no guesses;',\n '- never secrets: no keys, tokens, passwords, Authorization values or URLs that carry a key;',\n '- to change an entry that exists, repeat its section and title with the new body; leave entries alone that need no change.',\n 'Answer with a JSON array and nothing else: [{\"section\": \"...\", \"title\": \"...\", \"body\": \"...\", \"tag\": \"user\" | \"feedback\" | \"project\"}]. An empty array is a fine answer.',\n].join('\\n');\n\nexport type Proposal = { section: string; title: string; body: string; tag: MemoryTag | null };\n\nconst TAGS = new Set(['user', 'feedback', 'project']);\n\nexport function proposals(text: string): Proposal[] {\n const start = text.indexOf('[');\n const end = text.lastIndexOf(']');\n if (start < 0 || end < start) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(text.slice(start, end + 1));\n } catch {\n return [];\n }\n if (!Array.isArray(parsed)) return [];\n const out: Proposal[] = [];\n for (const item of parsed as Record<string, unknown>[]) {\n if (!item || typeof item !== 'object') continue;\n const { section, title, body, tag } = item;\n if (typeof section !== 'string' || typeof title !== 'string' || typeof body !== 'string')\n continue;\n if (!section.trim() || !title.trim() || !body.trim()) continue;\n out.push({\n section,\n title,\n body,\n tag: typeof tag === 'string' && TAGS.has(tag) ? (tag as MemoryTag) : null,\n });\n }\n return out.slice(0, 20);\n}\n\ntype Input = { cone: string; after: number; tail: number; model: ModelRef | null };\ntype State = { phase: 'extract' };\nexport type ExtractResult = { saved: number; skipped: string | null };\n\nasync function save(host: Host, place: Place, found: Proposal[], context: Context) {\n let saved = 0;\n for (const proposal of found) {\n try {\n await host.files.change(place, { kind: 'save', ...proposal }, context);\n saved++;\n } catch {\n break;\n }\n }\n if (saved) await host.changed(context).catch(() => undefined);\n return saved;\n}\n\nexport function extractTask(\n ready: Promise<Host>,\n settings: (context: Context) => Promise<ExtractSettings>\n) {\n return defineTask<Input, State, ExtractResult>({\n name: EXTRACT_TASK,\n version: 1,\n initial: () => ({ phase: 'extract' }),\n phases: {\n async extract(task, runtime, context) {\n const { cone, after, tail } = task.input;\n const key = String(runtime.conversationId);\n const done = (result: ExtractResult) =>\n runtime.commit(async (tx) => {\n const doc = await tx.doc(ExtractDoc);\n doc.cursors[key] = Math.max(doc.cursors[key] ?? 0, tail);\n delete doc.pending[key];\n return { status: 'terminal', outcome: { status: 'completed', result } };\n }, context);\n if (!(await settings(context)).extract) return done({ saved: 0, skipped: 'turned off' });\n const host = await ready;\n const place = placeOf(host.home, cone);\n const view = await runtime.context(runtime.conversationId, context, {\n at: tail as EntryId,\n });\n const fresh = view.entries.flatMap((entry, index) =>\n entry.id > after ? (view.contributions[index] as readonly Message[]) : []\n );\n const transcript = transcriptOf(fresh);\n if (!place || !transcript) return done({ saved: 0, skipped: 'nothing new' });\n const ref = task.input.model ?? (await runtime.agent(context)).model;\n const model = ref ? runtime.models.getModel(ref.provider, ref.modelId) : undefined;\n if (!model) return done({ saved: 0, skipped: 'no model' });\n const current = await host.files.read(place.file, context);\n const memory = current ? serializeMemory(parseMemory(current.text)) : '';\n const now = Date.now();\n const message = await runtime.models.completeSimple(\n model,\n {\n messages: [\n { role: 'system', content: EXTRACT_PROMPT, timestamp: now } as unknown as Message,\n {\n role: 'user',\n content: [\n {\n type: 'text',\n text: `<memory_file bytes_left=\"${MAX_BYTES - new TextEncoder().encode(memory).length}\">\\n${memory}</memory_file>\\n\\n<conversation>\\n${transcript}\\n</conversation>`,\n },\n ],\n timestamp: now,\n },\n ],\n },\n { maxTokens: 2000, signal: runtime.signal, cacheRetention: 'none' }\n );\n runtime.signal.throwIfAborted();\n if (message.stopReason === 'error') return done({ saved: 0, skipped: 'model error' });\n const saved = await save(host, place, proposals(textOf(message as Message)), context);\n return done({ saved, skipped: null });\n },\n },\n abort: (_task, runtime, context) =>\n runtime.commit(async (tx) => {\n delete (await tx.doc(ExtractDoc)).pending[String(runtime.conversationId)];\n return { status: 'terminal', outcome: { status: 'aborted' } };\n }, context),\n });\n}\n\nexport type Schedule = (\n conversation: number,\n tail: number,\n context: Context,\n cone?: string\n) => Promise<boolean>;\n\nexport function compactionHook(schedule: Schedule) {\n return hook(CompactionTask, {\n beforeCompact(compaction, api) {\n const tail = compaction.entries.reduce((max, entry) => Math.max(max, entry.id), 0);\n void schedule(api.conversationId, tail, BACKGROUND_CONTEXT).catch(() => false);\n return undefined;\n },\n });\n}\n\nexport function scheduler(\n ready: Promise<Host>,\n task: ReturnType<typeof extractTask>,\n settings: (context: Context) => Promise<ExtractSettings>\n): Schedule {\n return async (conversation, tail, context, cone) => {\n const host = await ready;\n const chosen = await settings(context);\n const who = cone\n ? { kind: 'cone' as const, id: cone }\n : whoIs(host.agents.state(), conversation);\n if (who.kind !== 'cone') return false;\n return host.harness.commit(async (tx) => {\n const doc = await tx.doc(ExtractDoc);\n const key = String(conversation);\n const after = doc.cursors[key] ?? 0;\n if (doc.pending[key] !== undefined || tail <= after) return false;\n doc.pending[key] = (await tx.createTask(\n task,\n { cone: who.id, after, tail, model: chosen.model },\n {\n ownership: { kind: 'conversation' },\n conversationId: conversation as ConversationId,\n background: true,\n }\n )) as number;\n return true;\n }, context);\n };\n}\n\nexport function settingsReader(ready: Promise<Host>) {\n return async (context: Context): Promise<ExtractSettings> => {\n const host = await ready;\n const dir = host.agentDir ?? `${host.home}/.pi/agent`;\n const read = await host.env.readTextFile(`${dir}/settings.json`, context);\n return extractSettings(read.ok ? read.value : undefined);\n };\n}\n\nexport const IDLE_TASK = 'slicc.memory-idle';\n\nexport const IdleDoc = defineDoc<{ tasks: Record<string, number> }>({\n kind: 'slicc.memory-idle',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ tasks: {} }),\n});\n\ntype IdleState = { phase: 'watch'; tail: number; since: number };\n\nexport function idleTask(\n ready: Promise<Host>,\n schedule: Schedule,\n settings: (context: Context) => Promise<ExtractSettings>\n) {\n return defineTask<{ cone: string }, IdleState, null>({\n name: IDLE_TASK,\n version: 1,\n initial: () => ({ phase: 'watch', tail: -1, since: 0 }),\n phases: {\n async watch(task, runtime, context) {\n const host = await ready;\n const conversation = runtime.conversationId;\n const key = String(conversation);\n const who = whoIs(host.agents.state(), conversation);\n if (who.kind !== 'cone')\n return runtime.commit(async (tx) => {\n const doc = await tx.doc(IdleDoc);\n if (doc.tasks[key] === Number(task.id)) delete doc.tasks[key];\n return { status: 'terminal', outcome: { status: 'completed', result: null } };\n }, context);\n const { extract, idleMinutes } = await settings(context);\n const on = extract && idleMinutes > 0;\n const idleMs = (on ? idleMinutes : DEFAULT_IDLE_MINUTES) * 60_000;\n const now = runtime.now();\n const view = await runtime.context(conversation, context);\n const last = view.entries.at(-1);\n const tail = last?.id ?? 0;\n const seen = task.state.checkpoint;\n if (tail !== seen.tail) {\n const stamp = last?.model?.at(-1)?.timestamp;\n const since = typeof stamp === 'number' && stamp <= now ? stamp : now;\n return runtime.sleepUntil(since + idleMs, { phase: 'watch', tail, since }, context);\n }\n const due = seen.since + idleMs;\n if (now < due) return runtime.sleepUntil(due, seen, context);\n if (on) await schedule(conversation, tail, context, who.id);\n return runtime.sleepUntil(now + idleMs, seen, context);\n },\n },\n abort: (task, runtime, context) =>\n runtime.commit(async (tx) => {\n const doc = await tx.doc(IdleDoc);\n const key = String(runtime.conversationId);\n if (doc.tasks[key] === Number(task.id)) delete doc.tasks[key];\n return { status: 'terminal', outcome: { status: 'aborted' } };\n }, context),\n });\n}\n\nexport function idleWatch(\n host: Pick<Host, 'agents' | 'harness'>,\n task: ReturnType<typeof idleTask>,\n context: Context\n): () => void {\n let stopped = false;\n let line = Promise.resolve();\n const ensure = () => {\n line = line\n .then(async () => {\n if (stopped) return;\n await host.harness.commit(async (tx) => {\n const doc = await tx.doc(IdleDoc);\n for (const [id, cone] of Object.entries(host.agents.state().cones)) {\n const key = String(cone.conversation);\n const known = doc.tasks[key];\n const record = known === undefined ? undefined : await tx.task(known as TaskId);\n if (record && record.state.status !== 'terminal') continue;\n doc.tasks[key] = (await tx.createTask(\n task,\n { cone: id },\n {\n ownership: { kind: 'conversation' },\n conversationId: cone.conversation as ConversationId,\n background: true,\n }\n )) as number;\n }\n }, context);\n })\n .catch(() => undefined);\n };\n const off = host.agents.onChange(ensure);\n ensure();\n return () => {\n stopped = true;\n off();\n };\n}\n"],
5
- "mappings": "AACA,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EAEA;AAAA,OAGK;AACP,SAAS,kBAAkB;AAE3B,SAAS,WAAW,aAAa,eAAe,uBAAuB;AACvE,SAAoB,aAAa;AACjC,SAAqB,eAAe;AAE7B,MAAM,eAAe;AACrB,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAO7B,MAAM,aAAa,UAAwB;AAAA,EAChD,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC,EAAE;AAC7C,CAAC;AAIM,SAAS,gBAAgB,MAA2C;AACzE,MAAI,SAA+E,CAAC;AACpF,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,QAAQ,IAAI;AACtC,aAAS,OAAO,UAAU,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS,CAAC;AAAA,EACjF,QAAQ;AACN,aAAS,CAAC;AAAA,EACZ;AACA,QAAM,QAAQ,OAAO,OAAO,iBAAiB,WAAW,OAAO,aAAa,MAAM,GAAG,IAAI,CAAC;AAC1F,QAAM,QACJ,MAAM,UAAU,KAAK,MAAM,CAAC,KAAK,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,IACpD,EAAE,UAAU,MAAM,CAAC,GAAG,SAAS,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,EAAE,IACxD;AACN,QAAM,OACJ,OAAO,OAAO,gBAAgB,YAAY,OAAO,eAAe,IAC5D,OAAO,cACP;AACN,SAAO,EAAE,SAAS,OAAO,YAAY,OAAO,OAAO,aAAa,KAAK;AACvE;AAEA,SAAS,OAAO,SAA0B;AACxC,QAAM,UAAW,QAAkC;AACnD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,SAAO,QACJ;AAAA,IAAI,CAAC,SACJ,KAAK,SAAS,SACT,KAAK,QAAQ,KACd,KAAK,SAAS,aACZ,SAAS,KAAK,IAAI,MAClB;AAAA,EACR,EACC,OAAO,OAAO,EACd,KAAK,IAAI;AACd;AAEO,SAAS,aAAa,UAAsC;AACjE,QAAM,QAAkB,CAAC;AACzB,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,SAAS,UAAU,QAAQ,SAAS,YAAa;AAC7D,UAAM,OAAO,OAAO,OAAO,EAAE,KAAK;AAClC,QAAI,KAAM,OAAM,KAAK,GAAG,QAAQ,SAAS,SAAS,SAAS,OAAO,KAAK,IAAI,EAAE;AAAA,EAC/E;AACA,QAAM,SAAS,cAAc,MAAM,KAAK,MAAM,CAAC,EAAE;AACjD,SAAO,OAAO,SAAS,mBAAmB,OAAO,MAAM,CAAC,gBAAgB,IAAI;AAC9E;AAEO,MAAM,iBAAiB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,IAAI;AAIX,MAAM,OAAO,oBAAI,IAAI,CAAC,QAAQ,YAAY,SAAS,CAAC;AAE7C,SAAS,UAAU,MAA0B;AAClD,QAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,MAAI,QAAQ,KAAK,MAAM,MAAO,QAAO,CAAC;AACtC,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,KAAK,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA,EAChD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC;AACpC,QAAM,MAAkB,CAAC;AACzB,aAAW,QAAQ,QAAqC;AACtD,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,UAAM,EAAE,SAAS,OAAO,MAAM,IAAI,IAAI;AACtC,QAAI,OAAO,YAAY,YAAY,OAAO,UAAU,YAAY,OAAO,SAAS;AAC9E;AACF,QAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,KAAK,KAAK,EAAG;AACtD,QAAI,KAAK;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK,OAAO,QAAQ,YAAY,KAAK,IAAI,GAAG,IAAK,MAAoB;AAAA,IACvE,CAAC;AAAA,EACH;AACA,SAAO,IAAI,MAAM,GAAG,EAAE;AACxB;AAMA,eAAe,KAAK,MAAY,OAAc,OAAmB,SAAkB;AACjF,MAAI,QAAQ;AACZ,aAAW,YAAY,OAAO;AAC5B,QAAI;AACF,YAAM,KAAK,MAAM,OAAO,OAAO,EAAE,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO;AACrE;AAAA,IACF,QAAQ;AACN;AAAA,IACF;AAAA,EACF;AACA,MAAI,MAAO,OAAM,KAAK,QAAQ,OAAO,EAAE,MAAM,MAAM,MAAS;AAC5D,SAAO;AACT;AAEO,SAAS,YACd,OACA,UACA;AACA,SAAO,WAAwC;AAAA,IAC7C,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,OAAO,EAAE,OAAO,UAAU;AAAA,IACnC,QAAQ;AAAA,MACN,MAAM,QAAQ,MAAM,SAAS,SAAS;AACpC,cAAM,EAAE,MAAM,OAAO,KAAK,IAAI,KAAK;AACnC,cAAM,MAAM,OAAO,QAAQ,cAAc;AACzC,cAAM,OAAO,CAAC,WACZ,QAAQ,OAAO,OAAO,OAAO;AAC3B,gBAAM,MAAM,MAAM,GAAG,IAAI,UAAU;AACnC,cAAI,QAAQ,GAAG,IAAI,KAAK,IAAI,IAAI,QAAQ,GAAG,KAAK,GAAG,IAAI;AACvD,iBAAO,IAAI,QAAQ,GAAG;AACtB,iBAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,aAAa,OAAO,EAAE;AAAA,QACxE,GAAG,OAAO;AACZ,YAAI,EAAE,MAAM,SAAS,OAAO,GAAG,QAAS,QAAO,KAAK,EAAE,OAAO,GAAG,SAAS,aAAa,CAAC;AACvF,cAAM,OAAO,MAAM;AACnB,cAAM,QAAQ,QAAQ,KAAK,MAAM,IAAI;AACrC,cAAM,OAAO,MAAM,QAAQ,QAAQ,QAAQ,gBAAgB,SAAS;AAAA,UAClE,IAAI;AAAA,QACN,CAAC;AACD,cAAM,QAAQ,KAAK,QAAQ;AAAA,UAAQ,CAAC,OAAO,UACzC,MAAM,KAAK,QAAS,KAAK,cAAc,KAAK,IAA2B,CAAC;AAAA,QAC1E;AACA,cAAM,aAAa,aAAa,KAAK;AACrC,YAAI,CAAC,SAAS,CAAC,WAAY,QAAO,KAAK,EAAE,OAAO,GAAG,SAAS,cAAc,CAAC;AAC3E,cAAM,MAAM,KAAK,MAAM,UAAU,MAAM,QAAQ,MAAM,OAAO,GAAG;AAC/D,cAAM,QAAQ,MAAM,QAAQ,OAAO,SAAS,IAAI,UAAU,IAAI,OAAO,IAAI;AACzE,YAAI,CAAC,MAAO,QAAO,KAAK,EAAE,OAAO,GAAG,SAAS,WAAW,CAAC;AACzD,cAAM,UAAU,MAAM,KAAK,MAAM,KAAK,MAAM,MAAM,OAAO;AACzD,cAAM,SAAS,UAAU,gBAAgB,YAAY,QAAQ,IAAI,CAAC,IAAI;AACtE,cAAM,MAAM,KAAK,IAAI;AACrB,cAAM,UAAU,MAAM,QAAQ,OAAO;AAAA,UACnC;AAAA,UACA;AAAA,YACE,UAAU;AAAA,cACR,EAAE,MAAM,UAAU,SAAS,gBAAgB,WAAW,IAAI;AAAA,cAC1D;AAAA,gBACE,MAAM;AAAA,gBACN,SAAS;AAAA,kBACP;AAAA,oBACE,MAAM;AAAA,oBACN,MAAM,4BAA4B,YAAY,IAAI,YAAY,EAAE,OAAO,MAAM,EAAE,MAAM;AAAA,EAAO,MAAM;AAAA;AAAA;AAAA,EAAqC,UAAU;AAAA;AAAA,kBACnJ;AAAA,gBACF;AAAA,gBACA,WAAW;AAAA,cACb;AAAA,YACF;AAAA,UACF;AAAA,UACA,EAAE,WAAW,KAAM,QAAQ,QAAQ,QAAQ,gBAAgB,OAAO;AAAA,QACpE;AACA,gBAAQ,OAAO,eAAe;AAC9B,YAAI,QAAQ,eAAe,QAAS,QAAO,KAAK,EAAE,OAAO,GAAG,SAAS,cAAc,CAAC;AACpF,cAAM,QAAQ,MAAM,KAAK,MAAM,OAAO,UAAU,OAAO,OAAkB,CAAC,GAAG,OAAO;AACpF,eAAO,KAAK,EAAE,OAAO,SAAS,KAAK,CAAC;AAAA,MACtC;AAAA,IACF;AAAA,IACA,OAAO,CAAC,OAAO,SAAS,YACtB,QAAQ,OAAO,OAAO,OAAO;AAC3B,cAAQ,MAAM,GAAG,IAAI,UAAU,GAAG,QAAQ,OAAO,QAAQ,cAAc,CAAC;AACxE,aAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,UAAU,EAAE;AAAA,IAC9D,GAAG,OAAO;AAAA,EACd,CAAC;AACH;AASO,SAAS,eAAe,UAAoB;AACjD,SAAO,KAAK,gBAAgB;AAAA,IAC1B,cAAc,YAAY,KAAK;AAC7B,YAAM,OAAO,WAAW,QAAQ,OAAO,CAAC,KAAK,UAAU,KAAK,IAAI,KAAK,MAAM,EAAE,GAAG,CAAC;AACjF,WAAK,SAAS,IAAI,gBAAgB,MAAM,kBAAkB,EAAE,MAAM,MAAM,KAAK;AAC7E,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACH;AAEO,SAAS,UACd,OACA,MACA,UACU;AACV,SAAO,OAAO,cAAc,MAAM,SAAS,SAAS;AAClD,UAAM,OAAO,MAAM;AACnB,UAAM,SAAS,MAAM,SAAS,OAAO;AACrC,UAAM,MAAM,OACR,EAAE,MAAM,QAAiB,IAAI,KAAK,IAClC,MAAM,KAAK,OAAO,MAAM,GAAG,YAAY;AAC3C,QAAI,IAAI,SAAS,OAAQ,QAAO;AAChC,WAAO,KAAK,QAAQ,OAAO,OAAO,OAAO;AACvC,YAAM,MAAM,MAAM,GAAG,IAAI,UAAU;AACnC,YAAM,MAAM,OAAO,YAAY;AAC/B,YAAM,QAAQ,IAAI,QAAQ,GAAG,KAAK;AAClC,UAAI,IAAI,QAAQ,GAAG,MAAM,UAAa,QAAQ,MAAO,QAAO;AAC5D,UAAI,QAAQ,GAAG,IAAK,MAAM,GAAG;AAAA,QAC3B;AAAA,QACA,EAAE,MAAM,IAAI,IAAI,OAAO,MAAM,OAAO,OAAO,MAAM;AAAA,QACjD;AAAA,UACE,WAAW,EAAE,MAAM,eAAe;AAAA,UAClC,gBAAgB;AAAA,UAChB,YAAY;AAAA,QACd;AAAA,MACF;AACA,aAAO;AAAA,IACT,GAAG,OAAO;AAAA,EACZ;AACF;AAEO,SAAS,eAAe,OAAsB;AACnD,SAAO,OAAO,YAA+C;AAC3D,UAAM,OAAO,MAAM;AACnB,UAAM,MAAM,KAAK,YAAY,GAAG,KAAK,IAAI;AACzC,UAAM,OAAO,MAAM,KAAK,IAAI,aAAa,GAAG,GAAG,kBAAkB,OAAO;AACxE,WAAO,gBAAgB,KAAK,KAAK,KAAK,QAAQ,MAAS;AAAA,EACzD;AACF;AAEO,MAAM,YAAY;AAElB,MAAM,UAAU,UAA6C;AAAA,EAClE,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,OAAO,CAAC,EAAE;AAC9B,CAAC;AAIM,SAAS,SACd,OACA,UACA,UACA;AACA,SAAO,WAA8C;AAAA,IACnD,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,OAAO,EAAE,OAAO,SAAS,MAAM,IAAI,OAAO,EAAE;AAAA,IACrD,QAAQ;AAAA,MACN,MAAM,MAAM,MAAM,SAAS,SAAS;AAClC,cAAM,OAAO,MAAM;AACnB,cAAM,eAAe,QAAQ;AAC7B,cAAM,MAAM,OAAO,YAAY;AAC/B,cAAM,MAAM,MAAM,KAAK,OAAO,MAAM,GAAG,YAAY;AACnD,YAAI,IAAI,SAAS;AACf,iBAAO,QAAQ,OAAO,OAAO,OAAO;AAClC,kBAAM,MAAM,MAAM,GAAG,IAAI,OAAO;AAChC,gBAAI,IAAI,MAAM,GAAG,MAAM,OAAO,KAAK,EAAE,EAAG,QAAO,IAAI,MAAM,GAAG;AAC5D,mBAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,aAAa,QAAQ,KAAK,EAAE;AAAA,UAC9E,GAAG,OAAO;AACZ,cAAM,EAAE,SAAS,YAAY,IAAI,MAAM,SAAS,OAAO;AACvD,cAAM,KAAK,WAAW,cAAc;AACpC,cAAM,UAAU,KAAK,cAAc,wBAAwB;AAC3D,cAAM,MAAM,QAAQ,IAAI;AACxB,cAAM,OAAO,MAAM,QAAQ,QAAQ,cAAc,OAAO;AACxD,cAAM,OAAO,KAAK,QAAQ,GAAG,EAAE;AAC/B,cAAM,OAAO,MAAM,MAAM;AACzB,cAAM,OAAO,KAAK,MAAM;AACxB,YAAI,SAAS,KAAK,MAAM;AACtB,gBAAM,QAAQ,MAAM,OAAO,GAAG,EAAE,GAAG;AACnC,gBAAM,QAAQ,OAAO,UAAU,YAAY,SAAS,MAAM,QAAQ;AAClE,iBAAO,QAAQ,WAAW,QAAQ,QAAQ,EAAE,OAAO,SAAS,MAAM,MAAM,GAAG,OAAO;AAAA,QACpF;AACA,cAAM,MAAM,KAAK,QAAQ;AACzB,YAAI,MAAM,IAAK,QAAO,QAAQ,WAAW,KAAK,MAAM,OAAO;AAC3D,YAAI,GAAI,OAAM,SAAS,cAAc,MAAM,SAAS,IAAI,EAAE;AAC1D,eAAO,QAAQ,WAAW,MAAM,QAAQ,MAAM,OAAO;AAAA,MACvD;AAAA,IACF;AAAA,IACA,OAAO,CAAC,MAAM,SAAS,YACrB,QAAQ,OAAO,OAAO,OAAO;AAC3B,YAAM,MAAM,MAAM,GAAG,IAAI,OAAO;AAChC,YAAM,MAAM,OAAO,QAAQ,cAAc;AACzC,UAAI,IAAI,MAAM,GAAG,MAAM,OAAO,KAAK,EAAE,EAAG,QAAO,IAAI,MAAM,GAAG;AAC5D,aAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,UAAU,EAAE;AAAA,IAC9D,GAAG,OAAO;AAAA,EACd,CAAC;AACH;AAEO,SAAS,UACd,MACA,MACA,SACY;AACZ,MAAI,UAAU;AACd,MAAI,OAAO,QAAQ,QAAQ;AAC3B,QAAM,SAAS,MAAM;AACnB,WAAO,KACJ,KAAK,YAAY;AAChB,UAAI,QAAS;AACb,YAAM,KAAK,QAAQ,OAAO,OAAO,OAAO;AACtC,cAAM,MAAM,MAAM,GAAG,IAAI,OAAO;AAChC,mBAAW,CAAC,IAAI,IAAI,KAAK,OAAO,QAAQ,KAAK,OAAO,MAAM,EAAE,KAAK,GAAG;AAClE,gBAAM,MAAM,OAAO,KAAK,YAAY;AACpC,gBAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,gBAAM,SAAS,UAAU,SAAY,SAAY,MAAM,GAAG,KAAK,KAAe;AAC9E,cAAI,UAAU,OAAO,MAAM,WAAW,WAAY;AAClD,cAAI,MAAM,GAAG,IAAK,MAAM,GAAG;AAAA,YACzB;AAAA,YACA,EAAE,MAAM,GAAG;AAAA,YACX;AAAA,cACE,WAAW,EAAE,MAAM,eAAe;AAAA,cAClC,gBAAgB,KAAK;AAAA,cACrB,YAAY;AAAA,YACd;AAAA,UACF;AAAA,QACF;AAAA,MACF,GAAG,OAAO;AAAA,IACZ,CAAC,EACA,MAAM,MAAM,MAAS;AAAA,EAC1B;AACA,QAAM,MAAM,KAAK,OAAO,SAAS,MAAM;AACvC,SAAO;AACP,SAAO,MAAM;AACX,cAAU;AACV,QAAI;AAAA,EACN;AACF;",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { Message } from '@earendil-works/pi-ai';\nimport {\n CompactionTask,\n type ConversationId,\n defineDoc,\n defineTask,\n type EntryId,\n hook,\n type ModelRef,\n type TaskId,\n} from '@earendil-works/pi-durable';\nimport { boundedDeltas } from '../checkpoints.ts';\nimport type { MemoryTag } from './format.ts';\nimport { MAX_BYTES, parseMemory, redactSecrets, serializeMemory } from './format.ts';\nimport { type Host, whoIs } from './prompt.ts';\nimport { type Place, placeOf } from './store.ts';\n\nexport const EXTRACT_TASK = 'slicc.memory-extract';\nexport const TRANSCRIPT_CHARS = 60_000;\nexport const DEFAULT_IDLE_MINUTES = 30;\n\nexport type ExtractState = {\n cursors: Record<string, number>;\n pending: Record<string, number>;\n};\n\nexport const ExtractDoc = defineDoc<ExtractState>({\n kind: 'slicc.memory-extract',\n checkpointWhen: boundedDeltas,\n version: 1,\n scope: 'session',\n initial: () => ({ cursors: {}, pending: {} }),\n});\n\nexport type ExtractSettings = { extract: boolean; model: ModelRef | null; idleMinutes: number };\n\nexport function extractSettings(text: string | undefined): ExtractSettings {\n let memory: { extract?: unknown; extractModel?: unknown; idleMinutes?: unknown } = {};\n try {\n const parsed = JSON.parse(text ?? '{}') as { memory?: typeof memory };\n memory = parsed.memory && typeof parsed.memory === 'object' ? parsed.memory : {};\n } catch {\n memory = {};\n }\n const named = typeof memory.extractModel === 'string' ? memory.extractModel.split('/') : [];\n const model =\n named.length >= 2 && named[0] && named.slice(1).join('/')\n ? { provider: named[0], modelId: named.slice(1).join('/') }\n : null;\n const idle =\n typeof memory.idleMinutes === 'number' && memory.idleMinutes >= 0\n ? memory.idleMinutes\n : DEFAULT_IDLE_MINUTES;\n return { extract: memory.extract !== false, model, idleMinutes: idle };\n}\n\nfunction textOf(message: Message): string {\n const content = (message as { content?: unknown }).content;\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content\n .map((part: { type?: string; text?: string; name?: string }) =>\n part.type === 'text'\n ? (part.text ?? '')\n : part.type === 'toolCall'\n ? `[tool ${part.name}]`\n : ''\n )\n .filter(Boolean)\n .join('\\n');\n}\n\nexport function transcriptOf(messages: readonly Message[]): string {\n const lines: string[] = [];\n for (const message of messages) {\n if (message.role !== 'user' && message.role !== 'assistant') continue;\n const text = textOf(message).trim();\n if (text) lines.push(`${message.role === 'user' ? 'User' : 'Agent'}: ${text}`);\n }\n const joined = redactSecrets(lines.join('\\n\\n')).text;\n return joined.length > TRANSCRIPT_CHARS ? joined.slice(-TRANSCRIPT_CHARS) : joined;\n}\n\nexport const EXTRACT_PROMPT = [\n 'You keep the long-term memory of a personal agent. Read the conversation excerpt and the current memory file, and propose memory entries worth keeping across conversations:',\n '- who the user is (tag \"user\"), how they want the agent to work, including corrections (tag \"feedback\"), and durable facts about their projects (tag \"project\");',\n '- only facts that will still matter in a later conversation; nothing about this one task, no transcripts, no guesses;',\n '- never secrets: no keys, tokens, passwords, Authorization values or URLs that carry a key;',\n '- to change an entry that exists, repeat its section and title with the new body; leave entries alone that need no change.',\n 'Answer with a JSON array and nothing else: [{\"section\": \"...\", \"title\": \"...\", \"body\": \"...\", \"tag\": \"user\" | \"feedback\" | \"project\"}]. An empty array is a fine answer.',\n].join('\\n');\n\nexport type Proposal = { section: string; title: string; body: string; tag: MemoryTag | null };\n\nconst TAGS = new Set(['user', 'feedback', 'project']);\n\nexport function proposals(text: string): Proposal[] {\n const start = text.indexOf('[');\n const end = text.lastIndexOf(']');\n if (start < 0 || end < start) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(text.slice(start, end + 1));\n } catch {\n return [];\n }\n if (!Array.isArray(parsed)) return [];\n const out: Proposal[] = [];\n for (const item of parsed as Record<string, unknown>[]) {\n if (!item || typeof item !== 'object') continue;\n const { section, title, body, tag } = item;\n if (typeof section !== 'string' || typeof title !== 'string' || typeof body !== 'string')\n continue;\n if (!section.trim() || !title.trim() || !body.trim()) continue;\n out.push({\n section,\n title,\n body,\n tag: typeof tag === 'string' && TAGS.has(tag) ? (tag as MemoryTag) : null,\n });\n }\n return out.slice(0, 20);\n}\n\ntype Input = { cone: string; after: number; tail: number; model: ModelRef | null };\ntype State = { phase: 'extract' };\nexport type ExtractResult = { saved: number; skipped: string | null };\n\nasync function save(host: Host, place: Place, found: Proposal[], context: Context) {\n let saved = 0;\n for (const proposal of found) {\n try {\n await host.files.change(place, { kind: 'save', ...proposal }, context);\n saved++;\n } catch {\n break;\n }\n }\n if (saved) await host.changed(context).catch(() => undefined);\n return saved;\n}\n\nexport function extractTask(\n ready: Promise<Host>,\n settings: (context: Context) => Promise<ExtractSettings>\n) {\n return defineTask<Input, State, ExtractResult>({\n name: EXTRACT_TASK,\n version: 1,\n initial: () => ({ phase: 'extract' }),\n phases: {\n async extract(task, runtime, context) {\n const { cone, after, tail } = task.input;\n const key = String(runtime.conversationId);\n const done = (result: ExtractResult) =>\n runtime.commit(async (tx) => {\n const doc = await tx.doc(ExtractDoc);\n doc.cursors[key] = Math.max(doc.cursors[key] ?? 0, tail);\n delete doc.pending[key];\n return { status: 'terminal', outcome: { status: 'completed', result } };\n }, context);\n if (!(await settings(context)).extract) return done({ saved: 0, skipped: 'turned off' });\n const host = await ready;\n const place = placeOf(host.home, cone);\n const view = await runtime.context(runtime.conversationId, context, {\n at: tail as EntryId,\n });\n const fresh = view.entries.flatMap((entry, index) =>\n entry.id > after ? (view.contributions[index] as readonly Message[]) : []\n );\n const transcript = transcriptOf(fresh);\n if (!place || !transcript) return done({ saved: 0, skipped: 'nothing new' });\n const ref = task.input.model ?? (await runtime.agent(context)).model;\n const model = ref ? runtime.models.getModel(ref.provider, ref.modelId) : undefined;\n if (!model) return done({ saved: 0, skipped: 'no model' });\n const current = await host.files.read(place.file, context);\n const memory = current ? serializeMemory(parseMemory(current.text)) : '';\n const now = Date.now();\n const message = await runtime.models.completeSimple(\n model,\n {\n messages: [\n { role: 'system', content: EXTRACT_PROMPT, timestamp: now } as unknown as Message,\n {\n role: 'user',\n content: [\n {\n type: 'text',\n text: `<memory_file bytes_left=\"${MAX_BYTES - new TextEncoder().encode(memory).length}\">\\n${memory}</memory_file>\\n\\n<conversation>\\n${transcript}\\n</conversation>`,\n },\n ],\n timestamp: now,\n },\n ],\n },\n { maxTokens: 2000, signal: runtime.signal, cacheRetention: 'none' }\n );\n runtime.signal.throwIfAborted();\n if (message.stopReason === 'error') return done({ saved: 0, skipped: 'model error' });\n const saved = await save(host, place, proposals(textOf(message as Message)), context);\n return done({ saved, skipped: null });\n },\n },\n abort: (_task, runtime, context) =>\n runtime.commit(async (tx) => {\n delete (await tx.doc(ExtractDoc)).pending[String(runtime.conversationId)];\n return { status: 'terminal', outcome: { status: 'aborted' } };\n }, context),\n });\n}\n\nexport type Schedule = (\n conversation: number,\n tail: number,\n context: Context,\n cone?: string\n) => Promise<boolean>;\n\nexport function compactionHook(schedule: Schedule) {\n return hook(CompactionTask, {\n beforeCompact(compaction, api) {\n const tail = compaction.entries.reduce((max, entry) => Math.max(max, entry.id), 0);\n void schedule(api.conversationId, tail, BACKGROUND_CONTEXT).catch(() => false);\n return undefined;\n },\n });\n}\n\nexport function scheduler(\n ready: Promise<Host>,\n task: ReturnType<typeof extractTask>,\n settings: (context: Context) => Promise<ExtractSettings>\n): Schedule {\n return async (conversation, tail, context, cone) => {\n const host = await ready;\n const chosen = await settings(context);\n const who = cone\n ? { kind: 'cone' as const, id: cone }\n : whoIs(host.agents.state(), conversation);\n if (who.kind !== 'cone') return false;\n return host.harness.commit(async (tx) => {\n const doc = await tx.doc(ExtractDoc);\n const key = String(conversation);\n const after = doc.cursors[key] ?? 0;\n if (doc.pending[key] !== undefined || tail <= after) return false;\n doc.pending[key] = (await tx.createTask(\n task,\n { cone: who.id, after, tail, model: chosen.model },\n {\n ownership: { kind: 'conversation' },\n conversationId: conversation as ConversationId,\n background: true,\n }\n )) as number;\n return true;\n }, context);\n };\n}\n\nexport function settingsReader(ready: Promise<Host>) {\n return async (context: Context): Promise<ExtractSettings> => {\n const host = await ready;\n const dir = host.agentDir ?? `${host.home}/.pi/agent`;\n const read = await host.env.readTextFile(`${dir}/settings.json`, context);\n return extractSettings(read.ok ? read.value : undefined);\n };\n}\n\nexport const IDLE_TASK = 'slicc.memory-idle';\n\nexport const IdleDoc = defineDoc<{ tasks: Record<string, number> }>({\n kind: 'slicc.memory-idle',\n checkpointWhen: boundedDeltas,\n version: 1,\n scope: 'session',\n initial: () => ({ tasks: {} }),\n});\n\ntype IdleState = { phase: 'watch'; tail: number; since: number };\n\nexport function idleTask(\n ready: Promise<Host>,\n schedule: Schedule,\n settings: (context: Context) => Promise<ExtractSettings>\n) {\n return defineTask<{ cone: string }, IdleState, null>({\n name: IDLE_TASK,\n version: 1,\n initial: () => ({ phase: 'watch', tail: -1, since: 0 }),\n phases: {\n async watch(task, runtime, context) {\n const host = await ready;\n const conversation = runtime.conversationId;\n const key = String(conversation);\n const who = whoIs(host.agents.state(), conversation);\n if (who.kind !== 'cone')\n return runtime.commit(async (tx) => {\n const doc = await tx.doc(IdleDoc);\n if (doc.tasks[key] === Number(task.id)) delete doc.tasks[key];\n return { status: 'terminal', outcome: { status: 'completed', result: null } };\n }, context);\n const { extract, idleMinutes } = await settings(context);\n const on = extract && idleMinutes > 0;\n const idleMs = (on ? idleMinutes : DEFAULT_IDLE_MINUTES) * 60_000;\n const now = runtime.now();\n const view = await runtime.context(conversation, context);\n const last = view.entries.at(-1);\n const tail = last?.id ?? 0;\n const seen = task.state.checkpoint;\n if (tail !== seen.tail) {\n const stamp = last?.model?.at(-1)?.timestamp;\n const since = typeof stamp === 'number' && stamp <= now ? stamp : now;\n return runtime.sleepUntil(since + idleMs, { phase: 'watch', tail, since }, context);\n }\n const due = seen.since + idleMs;\n if (now < due) return runtime.sleepUntil(due, seen, context);\n if (on) await schedule(conversation, tail, context, who.id);\n return runtime.sleepUntil(now + idleMs, seen, context);\n },\n },\n abort: (task, runtime, context) =>\n runtime.commit(async (tx) => {\n const doc = await tx.doc(IdleDoc);\n const key = String(runtime.conversationId);\n if (doc.tasks[key] === Number(task.id)) delete doc.tasks[key];\n return { status: 'terminal', outcome: { status: 'aborted' } };\n }, context),\n });\n}\n\nexport function idleWatch(\n host: Pick<Host, 'agents' | 'harness'>,\n task: ReturnType<typeof idleTask>,\n context: Context\n): () => void {\n let stopped = false;\n let line = Promise.resolve();\n const ensure = () => {\n line = line\n .then(async () => {\n if (stopped) return;\n await host.harness.commit(async (tx) => {\n const doc = await tx.doc(IdleDoc);\n for (const [id, cone] of Object.entries(host.agents.state().cones)) {\n const key = String(cone.conversation);\n const known = doc.tasks[key];\n const record = known === undefined ? undefined : await tx.task(known as TaskId);\n if (record && record.state.status !== 'terminal') continue;\n doc.tasks[key] = (await tx.createTask(\n task,\n { cone: id },\n {\n ownership: { kind: 'conversation' },\n conversationId: cone.conversation as ConversationId,\n background: true,\n }\n )) as number;\n }\n }, context);\n })\n .catch(() => undefined);\n };\n const off = host.agents.onChange(ensure);\n ensure();\n return () => {\n stopped = true;\n off();\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,7 +7,7 @@ import {
7
7
  live,
8
8
  scoopId
9
9
  } from "../agents.js";
10
- import { deltasOnly } from "../checkpoints.js";
10
+ import { boundedDeltas } from "../checkpoints.js";
11
11
  import { CODEMODE } from "../codemode/index.js";
12
12
  import { distinctTaints, GuestTaintDoc } from "../guests/guard.js";
13
13
  import { LEGACY } from "../layout.js";
@@ -17,7 +17,7 @@ import { SCOOPS_ROOT } from "../layout.js";
17
17
  const LEDGER_SIZE = 512;
18
18
  const ScoopWorkDoc = defineDoc({
19
19
  kind: "slicc.scoops.work",
20
- checkpointWhen: deltasOnly,
20
+ checkpointWhen: boundedDeltas,
21
21
  version: 1,
22
22
  scope: "session",
23
23
  initial: () => ({ feeds: {}, waits: {}, ledger: {}, turns: {} })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/scoops/service.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type AgentChange,\n type AnyTask,\n type Conversation,\n type ConversationId,\n configure,\n defineDoc,\n type Extension,\n type Harness,\n type TaskId,\n type ToolRegistration,\n type Tx,\n} from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport {\n type Agents,\n type AgentsState,\n freeFolder,\n live,\n type Mark,\n type ScoopRecord,\n scoopId,\n} from '../agents.ts';\nimport { deltasOnly } from '../checkpoints.ts';\nimport { CODEMODE } from '../codemode/index.ts';\nimport { distinctTaints, GuestTaintDoc, type InheritedTaint } from '../guests/guard.ts';\nimport type { ProcessGroups } from '../kernel/groups.ts';\nimport { type Layout, LEGACY } from '../layout.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport { McpAllowDoc, mcpEntries } from '../mcp/index.ts';\nimport type { Role } from '../roles/roles.ts';\nimport type { Choosable } from '../selection.ts';\nimport { visible } from './visible.ts';\n\nexport { SCOOPS_ROOT } from '../layout.ts';\nexport const LEDGER_SIZE = 512;\n\nexport type Feed = {\n scoop: string;\n created: Mark | null;\n report: boolean;\n target: string | null;\n channel: 'scoop-notify' | 'bash';\n from?: string;\n agent?: boolean;\n};\ntype Wait = { scoops: string[]; done: Record<string, string>; cone: string };\nexport type Answer = { code: number; out: string; err?: string; tasks?: number[] };\ntype WorkState = {\n feeds: Record<string, Feed>;\n waits: Record<string, Wait>;\n ledger: Record<string, Answer>;\n turns: Record<string, { at: number; count: number }>;\n};\n\nexport const ScoopWorkDoc = defineDoc<WorkState>({\n kind: 'slicc.scoops.work',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ feeds: {}, waits: {}, ledger: {}, turns: {} }),\n});\n\nexport interface ScoopTasks {\n anchor: AnyTask;\n reporter: AnyTask;\n wait: AnyTask;\n}\n\nexport type EnvOf = (\n conversationId: ConversationId,\n context: Context\n) => Promise<Pick<ExecutionEnv, 'canonicalPath'> | undefined>;\n\nexport interface ScoopsHost {\n harness: Harness;\n agents: Agents;\n licks: Licks;\n groups?: ProcessGroups;\n tools?: readonly ToolRegistration[];\n reads?: () => readonly string[];\n files?: Pick<ExecutionEnv, 'remove' | 'createDir'>;\n envOf?: EnvOf;\n mcp?: (names: readonly string[]) => ToolRegistration[];\n layout?: Layout;\n enforced?: boolean;\n exclude?: readonly Extension[];\n}\n\nexport interface Rewound {\n stopped: string[];\n restored: string[];\n}\n\nexport interface ScoopSummary {\n id: string;\n folder: string;\n name: string;\n role: string | null;\n busy: boolean;\n}\n\nexport interface Limits {\n maxLiveScoops: number;\n maxPerTurn: number;\n}\n\nexport interface SpawnRequest {\n cone: string;\n kind?: 'async' | 'sync';\n parent?: string;\n depth?: number;\n roots?: { write: string[]; read: string[] };\n cwd?: string;\n origin?: string;\n instructions?: string;\n target?: string | null;\n asker?: string | null;\n name: string;\n role?: Role;\n model?: { provider: string; modelId: string };\n thinking?: AgentChange['thinkingLevel'];\n tools?: string[];\n prompts: string[];\n fromAgent: boolean;\n request?: string;\n limits: Limits;\n taints?: InheritedTaint[];\n created?: (tx: Tx, made: Spawned) => Promise<void>;\n}\n\nexport interface FeedOptions {\n fromAgent: boolean;\n request: string;\n followUp: boolean;\n target: string | null;\n taints?: InheritedTaint[];\n}\n\nexport interface Spawned {\n folder: string;\n conversation: number;\n tasks: number[];\n}\n\nexport function workspace(folder: string, layout: Layout = LEGACY): string {\n return layout.workspace(folder);\n}\n\nexport async function tail(conversation: Conversation, context: Context): Promise<Mark> {\n const { entries } = await conversation.context(context);\n return { in: conversation.id, at: Number(entries.at(-1)?.id ?? 0) };\n}\n\nexport async function turnStart(conversation: Conversation, context: Context): Promise<number> {\n const { entries } = await conversation.context(context);\n return Number(entries.findLast((entry) => entry.kind === 'pi.user')?.id ?? 0);\n}\n\nexport function coneOf(state: Readonly<AgentsState>, conversation: number): string | undefined {\n return Object.entries(state.cones).find(([, cone]) => cone.conversation === conversation)?.[0];\n}\n\nexport function findScoop(\n state: Readonly<AgentsState>,\n handle: string,\n cone: string | null\n): [string, ScoopRecord] | undefined {\n const id = handle.startsWith('scoop:') ? handle : scoopId(handle);\n const reachable = (record: ScoopRecord | undefined): record is ScoopRecord =>\n !!record && live(record) && (cone === null || record.cone === cone);\n const record = state.scoops[id];\n if (reachable(record)) return [id, record];\n return Object.entries(state.scoops).find(\n ([, candidate]) => reachable(candidate) && candidate.name === handle\n );\n}\n\nfunction remember(work: WorkState, request: string | undefined, answer: Answer): void {\n if (!request) return;\n work.ledger[request] = answer;\n const keys = Object.keys(work.ledger);\n for (const key of keys.slice(0, Math.max(0, keys.length - LEDGER_SIZE))) delete work.ledger[key];\n}\n\ntype Core = { host: Promise<ScoopsHost>; tasks: () => ScoopTasks };\n\nasync function coneOfId(core: Core, coneId: string, context: Context): Promise<Conversation> {\n const { agents } = await core.host;\n const found = await agents.conversation(coneId, context);\n if (!found) throw new Error(`There is no cone ${coneId}.`);\n return found;\n}\n\nasync function isBusy(core: Core, conversation: number, context: Context): Promise<boolean> {\n const { harness } = await core.host;\n const handle = await harness.conversation(conversation as ConversationId, context);\n const view = await handle?.viewState(context);\n const run = (view?.value.docs['pi.live'] as { run?: unknown } | undefined)?.run;\n view?.dispose();\n return Boolean(run);\n}\n\nasync function answered(\n core: Core,\n request: string | undefined,\n context: Context\n): Promise<Answer | undefined> {\n if (!request) return undefined;\n const { harness } = await core.host;\n return (await harness.snapshot(ScoopWorkDoc, context))?.ledger[request];\n}\n\ntype NewFeed = Omit<Feed, 'channel' | 'from'> & {\n from: string;\n agent: boolean;\n prompt: string;\n request?: string;\n followUp?: boolean;\n};\n\nasync function addFeed(core: Core, tx: Tx, parent: number, feed: NewFeed): Promise<number> {\n const task = await tx.createTask(\n core.tasks().reporter as never,\n {\n scoop: feed.scoop,\n prompt: feed.prompt,\n ...(feed.followUp ? { followUp: true } : {}),\n ...(feed.request ? { request: feed.request } : {}),\n },\n {\n ownership: { kind: 'conversation' },\n conversationId: parent as ConversationId,\n background: true,\n }\n );\n (await tx.doc(ScoopWorkDoc)).feeds[String(task)] = {\n scoop: feed.scoop,\n created: feed.created,\n report: feed.report,\n target: feed.target,\n channel: 'scoop-notify',\n from: feed.from,\n agent: feed.agent,\n };\n return Number(task);\n}\n\nexport function choosable(tools: readonly ToolRegistration[]): Choosable {\n const names = new Set(tools.map((tool) => tool.name));\n return (name) => names.has(name) || (mcpEntries([name]) ?? []).length > 0;\n}\n\nfunction selection(\n tools: readonly ToolRegistration[] | undefined,\n names: string[] | undefined,\n mcp?: (names: readonly string[]) => ToolRegistration[]\n) {\n if (!names || !tools) return undefined;\n const picked = tools.filter((tool) => names.includes(tool.name));\n const entries = mcpEntries(names) ?? [];\n if (!entries.length) return picked;\n const codemode = tools.find((tool) => tool.name === CODEMODE);\n if (codemode && !picked.includes(codemode)) picked.push(codemode);\n return [...picked, ...(mcp?.(entries) ?? [])];\n}\n\nfunction liveCount(state: Readonly<AgentsState>, cone: string): number {\n return Object.values(state.scoops).filter((scoop) => scoop.cone === cone && live(scoop)).length;\n}\n\nfunction claimTurn(\n work: WorkState,\n request: SpawnRequest,\n turn: number | null\n): Answer | undefined {\n if (turn === null) return undefined;\n const counted = work.turns[request.cone];\n const count = counted && counted.at === turn ? counted.count : 0;\n if (count >= request.limits.maxPerTurn)\n return {\n code: 1,\n out: `subagent: this turn already started ${count} scoops (the limit is ${request.limits.maxPerTurn})\\n`,\n };\n work.turns[request.cone] = { at: turn, count: count + 1 };\n return undefined;\n}\n\nexport function agentChange(\n request: SpawnRequest,\n folder: string,\n host: Pick<ScoopsHost, 'tools' | 'mcp' | 'layout' | 'exclude'>\n): AgentChange {\n const { tools, mcp } = host;\n const model = request.model ?? modelOf(request.role?.model);\n const thinking = request.thinking ?? request.role?.thinking;\n const chosen = selection(tools, request.tools ?? request.role?.tools, mcp);\n const instructions = [request.role?.prompt, request.instructions].filter(Boolean).join('\\n\\n');\n return {\n cwd: request.cwd ?? workspace(folder, host.layout),\n ...(instructions ? { instructions } : {}),\n ...(model ? { model } : {}),\n ...(thinking ? { thinkingLevel: thinking as AgentChange['thinkingLevel'] } : {}),\n ...(chosen ? { tools: chosen } : {}),\n ...(host.exclude?.length ? { extensions: { remove: host.exclude } } : {}),\n };\n}\n\nasync function narrowMcp(\n tx: Tx,\n state: Readonly<AgentsState>,\n cone: ConversationId,\n child: ConversationId,\n request: SpawnRequest\n): Promise<void> {\n const listed = mcpEntries(request.tools ?? request.role?.tools);\n const caller = state.scoops[request.parent ?? '']?.conversation as ConversationId | undefined;\n const inherited = (await tx.doc(McpAllowDoc, caller ?? cone)).allow;\n if (listed !== null || inherited !== null)\n (await tx.doc(McpAllowDoc, child)).allow = listed ?? inherited;\n}\n\nfunction scoopRecord(\n request: SpawnRequest,\n folder: string,\n conversation: number,\n anchor: number,\n created: Mark | null\n): ScoopRecord {\n return {\n name: request.name.trim() || folder,\n folder,\n cone: request.cone,\n conversation,\n anchor,\n role: request.role?.name ?? null,\n ...(request.role?.memory ? { memory: request.role.memory } : {}),\n ...(request.role?.memory && request.origin ? { origin: request.origin } : {}),\n ...(request.role?.context ? { context: request.role.context } : {}),\n kind: request.kind ?? 'async',\n parent: request.parent ?? request.cone,\n depth: request.depth ?? 1,\n roots: request.roots ?? null,\n created,\n dropped: null,\n gone: false,\n };\n}\n\nasync function spawn(core: Core, request: SpawnRequest, context: Context): Promise<Answer> {\n const done = await answered(core, request.request, context);\n if (done) return done;\n const host = await core.host;\n const { agents } = host;\n const parent = await coneOfId(core, request.cone, context);\n const count = liveCount(agents.state(), request.cone);\n if (count >= request.limits.maxLiveScoops)\n return {\n code: 1,\n out: `subagent: this cone already has ${count} live scoops (the limit is ${request.limits.maxLiveScoops}); stop one first\\n`,\n };\n const created = request.fromAgent ? await tail(parent, context) : null;\n const turn = request.fromAgent ? await turnStart(parent, context) : null;\n return agents.update(async (tx, doc) => {\n const work = await tx.doc(ScoopWorkDoc);\n const refused = claimTurn(work as WorkState, request, turn);\n if (refused) return refused;\n const folder = freeFolder(doc, request.name);\n const id = scoopId(folder);\n const anchor = await tx.createTask(core.tasks().anchor as never, null, {\n ownership: { kind: 'conversation' },\n conversationId: parent.id,\n background: true,\n });\n const child = await tx.createConversation({ ownership: { kind: 'task', taskId: anchor } });\n await configure(tx, child.id, agentChange(request, folder, host));\n await narrowMcp(tx, doc, parent.id, child.id, request);\n if (request.taints?.length) (await tx.doc(GuestTaintDoc, child.id)).taints = request.taints;\n doc.scoops[id] = scoopRecord(request, folder, child.id, Number(anchor), created);\n const target =\n request.target === undefined\n ? request.fromAgent\n ? `cone:${request.cone}`\n : null\n : request.target;\n const made: number[] = [];\n const asker = request.asker === undefined ? target : request.asker;\n const from = askerName(doc, asker);\n for (const prompt of request.prompts)\n made.push(\n await addFeed(core, tx, parent.id, {\n scoop: id,\n created,\n report: target !== null,\n target,\n from,\n agent: asker !== null,\n prompt,\n })\n );\n const answer = { code: 0, out: `${folder}\\n`, tasks: made };\n await request.created?.(tx, { folder, conversation: Number(child.id), tasks: made });\n remember(work as WorkState, request.request, answer);\n return answer;\n }, context);\n}\n\nexport function askerName(state: Readonly<AgentsState>, asker: string | null): string {\n if (asker === null) return 'terminal';\n if (asker.startsWith('cone:')) return state.cones[asker.slice('cone:'.length)]?.name ?? asker;\n return state.scoops[asker]?.name ?? asker;\n}\n\nconst missing = (id: string): Answer => ({\n code: 1,\n out: `subagent: there is no scoop ${id.replace('scoop:', '')}\\n`,\n});\n\nasync function feed(\n core: Core,\n id: string,\n prompt: string,\n options: FeedOptions,\n context: Context\n): Promise<Answer> {\n const done = await answered(core, options.request, context);\n if (done) return done;\n const { harness, agents } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n const parent = await coneOfId(core, record.cone, context);\n const created = options.fromAgent ? await tail(parent, context) : null;\n const { target } = options;\n return harness.commit(async (tx) => {\n if (options.taints?.length) {\n const doc = await tx.doc(GuestTaintDoc, record.conversation as ConversationId);\n doc.taints = distinctTaints([...doc.taints, ...options.taints]);\n }\n await addFeed(core, tx, parent.id, {\n scoop: id,\n created,\n report: target !== null,\n target,\n from: askerName(agents.state(), target),\n agent: target !== null,\n prompt,\n followUp: options.followUp,\n request: options.request,\n });\n const answer = { code: 0, out: `sent to ${record.folder}\\n` };\n remember((await tx.doc(ScoopWorkDoc)) as WorkState, options.request, answer);\n return answer;\n }, context);\n}\n\nasync function signal(core: Core, record: ScoopRecord, context: Context): Promise<void> {\n const { harness, groups } = await core.host;\n const conversation = await harness.conversation(record.conversation as ConversationId, context);\n await conversation?.abort(context, { background: true });\n await groups?.signal(record.conversation);\n}\n\nasync function stopFeeds(\n core: Core,\n scoop: string,\n keep: (feed: Feed) => Promise<boolean>,\n context: Context\n) {\n const { harness } = await core.host;\n const work = (await harness.snapshot(ScoopWorkDoc, context)) ?? { feeds: {} };\n const dropped: string[] = [];\n for (const [task, entry] of Object.entries(work.feeds)) {\n if (entry.scoop !== scoop || (await keep(entry))) continue;\n const record = await harness.getTask(Number(task) as TaskId, context);\n if (record && record.state.status !== 'terminal')\n await harness.abortTask(Number(task) as TaskId, context);\n dropped.push(task);\n }\n if (dropped.length)\n await harness.commit(async (tx) => {\n const doc = await tx.doc(ScoopWorkDoc);\n for (const task of dropped) delete doc.feeds[task];\n }, context);\n return dropped.length;\n}\n\nasync function stop(core: Core, id: string, fromAgent: boolean, context: Context): Promise<Answer> {\n const { agents, files, layout = LEGACY } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n await stopFeeds(core, id, async () => false, context);\n await signal(core, record, context);\n const mark = fromAgent\n ? await tail(await coneOfId(core, record.cone, context), context)\n : { in: 0, at: 0 };\n await agents.update((_tx, state) => {\n (state.scoops[id] as ScoopRecord).dropped = mark;\n }, context);\n const place = layout.workspace(record.folder);\n const removed = await files?.remove(place, { recursive: true, force: true }, context);\n const folder = removed?.ok ? `removed ${place}` : `kept ${place}`;\n return {\n code: 0,\n out: `stopped ${record.folder}; its transcript is kept, ${folder}, and its reports stay in ${layout.reports(record.folder, record.cone)}\\n`,\n };\n}\n\nasync function rename(core: Core, id: string, name: string, context: Context): Promise<Answer> {\n const { agents } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n const wanted = name.trim();\n if (!wanted) return { code: 2, out: 'subagent: rename needs a non-empty name\\n' };\n await agents.update((_tx, state) => {\n (state.scoops[id] as ScoopRecord).name = wanted;\n }, context);\n return { code: 0, out: `${record.folder} is now called ${wanted}\\n` };\n}\n\nasync function resetWorkspace(\n host: Pick<ScoopsHost, 'files' | 'layout'>,\n folder: string,\n context: Context\n): Promise<boolean> {\n const { files } = host;\n if (!files) return true;\n const place = workspace(folder, host.layout);\n const cleared = await files.remove(place, { recursive: true, force: true }, context);\n if (!cleared.ok) return false;\n if (host.layout?.users) return true;\n return (await files.createDir(place, { recursive: true }, context)).ok;\n}\n\ntype Seen = (mark: Mark | null) => Promise<boolean>;\n\nasync function rewindOne(\n core: Core,\n coneId: string,\n id: string,\n record: ScoopRecord,\n seen: Seen,\n result: Rewound,\n context: Context\n): Promise<Partial<ScoopRecord> | undefined> {\n const host = await core.host;\n const { harness, licks } = host;\n if (!(await seen(record.created))) {\n if (live(record)) {\n await stopFeeds(core, id, async () => false, context);\n await signal(core, record, context);\n result.stopped.push(record.name);\n }\n return { gone: true };\n }\n if (record.dropped !== null) {\n if (await seen(record.dropped)) return undefined;\n const reset = await resetWorkspace(host, record.folder, context);\n result.restored.push(\n reset ? record.name : `${record.name} (its working folder could not be reset)`\n );\n return { dropped: null };\n }\n const withdrawn = await stopFeeds(core, id, (entry) => seen(entry.created), context);\n if (!withdrawn) return undefined;\n const conversation = await harness.conversation(record.conversation as ConversationId, context);\n await conversation?.abort(context);\n await licks.forget(`cone:${coneId}`, 'scoop-notify', id, context);\n return undefined;\n}\n\nasync function rewound(\n core: Core,\n coneId: string,\n fork: Conversation,\n context: Context\n): Promise<Rewound> {\n const { harness, agents } = await core.host;\n const read = (id: number) =>\n harness.commit((tx) => tx.conversation(id as ConversationId), context);\n const seen: Seen = async (mark) =>\n mark === null || mark.in === 0 || (await visible(read, fork.id, mark));\n const result: Rewound = { stopped: [], restored: [] };\n const changes: [string, Partial<ScoopRecord>][] = [];\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n if (record.cone !== coneId || record.gone) continue;\n const change = await rewindOne(core, coneId, id, record, seen, result, context);\n if (change) changes.push([id, change]);\n }\n if (changes.length)\n await agents.update((_tx, state) => {\n for (const [id, change] of changes) Object.assign(state.scoops[id] as ScoopRecord, change);\n }, context);\n return result;\n}\n\nasync function list(core: Core, coneId: string | null, context: Context): Promise<ScoopSummary[]> {\n const { agents } = await core.host;\n const out: ScoopSummary[] = [];\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n if ((coneId !== null && record.cone !== coneId) || !live(record)) continue;\n out.push({\n id,\n folder: record.folder,\n name: record.name,\n role: record.role,\n busy: await isBusy(core, record.conversation, context),\n });\n }\n return out;\n}\n\nasync function working(core: Core, ids: readonly string[], context: Context): Promise<string[]> {\n const { harness, agents } = await core.host;\n const feeds = Object.values((await harness.snapshot(ScoopWorkDoc, context))?.feeds ?? {});\n const out: string[] = [];\n for (const id of ids) {\n const record = agents.state().scoops[id];\n if (!record) continue;\n if (\n feeds.some((entry) => entry.scoop === id) ||\n (await isBusy(core, record.conversation, context))\n )\n out.push(id);\n }\n return out;\n}\n\nasync function notifyWhenDone(\n core: Core,\n coneId: string,\n ids: string[],\n deadline: number,\n taints: readonly InheritedTaint[],\n context: Context\n) {\n const { harness } = await core.host;\n const parent = await coneOfId(core, coneId, context);\n await harness.commit(\n (tx) =>\n tx.createTask(\n core.tasks().wait as never,\n { scoops: ids, cone: coneId, deadline, ...(taints.length ? { taints } : {}) },\n { ownership: { kind: 'conversation' }, conversationId: parent.id, background: true }\n ),\n context\n );\n}\n\nasync function detach(core: Core, tasks: readonly number[], target: string, context: Context) {\n const { harness } = await core.host;\n await harness.commit(async (tx) => {\n const work = await tx.doc(ScoopWorkDoc);\n for (const task of tasks) {\n const entry = work.feeds[String(task)];\n if (!entry) continue;\n entry.report = true;\n entry.target = target;\n entry.channel = 'bash';\n }\n }, context);\n}\n\nasync function note(\n core: Core,\n id: string,\n text: string,\n taints: readonly InheritedTaint[],\n context: Context\n): Promise<Answer> {\n const { agents, licks } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n const label = `${record.name} (${record.role ?? 'scoop'})`;\n await licks.deliver(\n {\n channel: 'scoop-notify',\n source: `${id}#note`,\n title: label,\n text: `[scoop ${label} progress]`,\n body: text,\n target: `cone:${record.cone}`,\n ...(taints.length ? { taints } : {}),\n },\n context\n );\n return { code: 0, out: 'note sent to the cone\\n' };\n}\n\nexport function createScoops(host: Promise<ScoopsHost>, tasks: () => ScoopTasks) {\n const core: Core = { host, tasks };\n return {\n detach: (made: readonly number[], target: string, context: Context) =>\n detach(core, made, target, context),\n note: (id: string, text: string, taints: readonly InheritedTaint[], context: Context) =>\n note(core, id, text, taints, context),\n lineage: async (pid: number | null) => (await core.host).groups?.lineage(pid),\n spawn: (request: SpawnRequest, context: Context) => spawn(core, request, context),\n feed: (id: string, prompt: string, options: FeedOptions, context: Context) =>\n feed(core, id, prompt, options, context),\n stop: (id: string, fromAgent: boolean, context: Context) => stop(core, id, fromAgent, context),\n rename: (id: string, name: string, context: Context) => rename(core, id, name, context),\n rewound: (coneId: string, fork: Conversation, context: Context) =>\n rewound(core, coneId, fork, context),\n list: (coneId: string | null, context: Context) => list(core, coneId, context),\n busy: (conversation: number, context: Context) => isBusy(core, conversation, context),\n working: (ids: readonly string[], context: Context) => working(core, ids, context),\n notifyWhenDone: (\n coneId: string,\n ids: string[],\n deadline: number,\n taints: readonly InheritedTaint[],\n context: Context\n ) => notifyWhenDone(core, coneId, ids, deadline, taints, context),\n };\n}\n\nexport function modelOf(\n value: string | undefined\n): { provider: string; modelId: string } | undefined {\n if (!value) return undefined;\n const at = value.indexOf('/');\n if (at <= 0) return undefined;\n return { provider: value.slice(0, at), modelId: value.slice(at + 1) };\n}\n\nexport type Scoops = ReturnType<typeof createScoops>;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type AgentChange,\n type AnyTask,\n type Conversation,\n type ConversationId,\n configure,\n defineDoc,\n type Extension,\n type Harness,\n type TaskId,\n type ToolRegistration,\n type Tx,\n} from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport {\n type Agents,\n type AgentsState,\n freeFolder,\n live,\n type Mark,\n type ScoopRecord,\n scoopId,\n} from '../agents.ts';\nimport { boundedDeltas } from '../checkpoints.ts';\nimport { CODEMODE } from '../codemode/index.ts';\nimport { distinctTaints, GuestTaintDoc, type InheritedTaint } from '../guests/guard.ts';\nimport type { ProcessGroups } from '../kernel/groups.ts';\nimport { type Layout, LEGACY } from '../layout.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport { McpAllowDoc, mcpEntries } from '../mcp/index.ts';\nimport type { Role } from '../roles/roles.ts';\nimport type { Choosable } from '../selection.ts';\nimport { visible } from './visible.ts';\n\nexport { SCOOPS_ROOT } from '../layout.ts';\nexport const LEDGER_SIZE = 512;\n\nexport type Feed = {\n scoop: string;\n created: Mark | null;\n report: boolean;\n target: string | null;\n channel: 'scoop-notify' | 'bash';\n from?: string;\n agent?: boolean;\n};\ntype Wait = { scoops: string[]; done: Record<string, string>; cone: string };\nexport type Answer = { code: number; out: string; err?: string; tasks?: number[] };\ntype WorkState = {\n feeds: Record<string, Feed>;\n waits: Record<string, Wait>;\n ledger: Record<string, Answer>;\n turns: Record<string, { at: number; count: number }>;\n};\n\nexport const ScoopWorkDoc = defineDoc<WorkState>({\n kind: 'slicc.scoops.work',\n checkpointWhen: boundedDeltas,\n version: 1,\n scope: 'session',\n initial: () => ({ feeds: {}, waits: {}, ledger: {}, turns: {} }),\n});\n\nexport interface ScoopTasks {\n anchor: AnyTask;\n reporter: AnyTask;\n wait: AnyTask;\n}\n\nexport type EnvOf = (\n conversationId: ConversationId,\n context: Context\n) => Promise<Pick<ExecutionEnv, 'canonicalPath'> | undefined>;\n\nexport interface ScoopsHost {\n harness: Harness;\n agents: Agents;\n licks: Licks;\n groups?: ProcessGroups;\n tools?: readonly ToolRegistration[];\n reads?: () => readonly string[];\n files?: Pick<ExecutionEnv, 'remove' | 'createDir'>;\n envOf?: EnvOf;\n mcp?: (names: readonly string[]) => ToolRegistration[];\n layout?: Layout;\n enforced?: boolean;\n exclude?: readonly Extension[];\n}\n\nexport interface Rewound {\n stopped: string[];\n restored: string[];\n}\n\nexport interface ScoopSummary {\n id: string;\n folder: string;\n name: string;\n role: string | null;\n busy: boolean;\n}\n\nexport interface Limits {\n maxLiveScoops: number;\n maxPerTurn: number;\n}\n\nexport interface SpawnRequest {\n cone: string;\n kind?: 'async' | 'sync';\n parent?: string;\n depth?: number;\n roots?: { write: string[]; read: string[] };\n cwd?: string;\n origin?: string;\n instructions?: string;\n target?: string | null;\n asker?: string | null;\n name: string;\n role?: Role;\n model?: { provider: string; modelId: string };\n thinking?: AgentChange['thinkingLevel'];\n tools?: string[];\n prompts: string[];\n fromAgent: boolean;\n request?: string;\n limits: Limits;\n taints?: InheritedTaint[];\n created?: (tx: Tx, made: Spawned) => Promise<void>;\n}\n\nexport interface FeedOptions {\n fromAgent: boolean;\n request: string;\n followUp: boolean;\n target: string | null;\n taints?: InheritedTaint[];\n}\n\nexport interface Spawned {\n folder: string;\n conversation: number;\n tasks: number[];\n}\n\nexport function workspace(folder: string, layout: Layout = LEGACY): string {\n return layout.workspace(folder);\n}\n\nexport async function tail(conversation: Conversation, context: Context): Promise<Mark> {\n const { entries } = await conversation.context(context);\n return { in: conversation.id, at: Number(entries.at(-1)?.id ?? 0) };\n}\n\nexport async function turnStart(conversation: Conversation, context: Context): Promise<number> {\n const { entries } = await conversation.context(context);\n return Number(entries.findLast((entry) => entry.kind === 'pi.user')?.id ?? 0);\n}\n\nexport function coneOf(state: Readonly<AgentsState>, conversation: number): string | undefined {\n return Object.entries(state.cones).find(([, cone]) => cone.conversation === conversation)?.[0];\n}\n\nexport function findScoop(\n state: Readonly<AgentsState>,\n handle: string,\n cone: string | null\n): [string, ScoopRecord] | undefined {\n const id = handle.startsWith('scoop:') ? handle : scoopId(handle);\n const reachable = (record: ScoopRecord | undefined): record is ScoopRecord =>\n !!record && live(record) && (cone === null || record.cone === cone);\n const record = state.scoops[id];\n if (reachable(record)) return [id, record];\n return Object.entries(state.scoops).find(\n ([, candidate]) => reachable(candidate) && candidate.name === handle\n );\n}\n\nfunction remember(work: WorkState, request: string | undefined, answer: Answer): void {\n if (!request) return;\n work.ledger[request] = answer;\n const keys = Object.keys(work.ledger);\n for (const key of keys.slice(0, Math.max(0, keys.length - LEDGER_SIZE))) delete work.ledger[key];\n}\n\ntype Core = { host: Promise<ScoopsHost>; tasks: () => ScoopTasks };\n\nasync function coneOfId(core: Core, coneId: string, context: Context): Promise<Conversation> {\n const { agents } = await core.host;\n const found = await agents.conversation(coneId, context);\n if (!found) throw new Error(`There is no cone ${coneId}.`);\n return found;\n}\n\nasync function isBusy(core: Core, conversation: number, context: Context): Promise<boolean> {\n const { harness } = await core.host;\n const handle = await harness.conversation(conversation as ConversationId, context);\n const view = await handle?.viewState(context);\n const run = (view?.value.docs['pi.live'] as { run?: unknown } | undefined)?.run;\n view?.dispose();\n return Boolean(run);\n}\n\nasync function answered(\n core: Core,\n request: string | undefined,\n context: Context\n): Promise<Answer | undefined> {\n if (!request) return undefined;\n const { harness } = await core.host;\n return (await harness.snapshot(ScoopWorkDoc, context))?.ledger[request];\n}\n\ntype NewFeed = Omit<Feed, 'channel' | 'from'> & {\n from: string;\n agent: boolean;\n prompt: string;\n request?: string;\n followUp?: boolean;\n};\n\nasync function addFeed(core: Core, tx: Tx, parent: number, feed: NewFeed): Promise<number> {\n const task = await tx.createTask(\n core.tasks().reporter as never,\n {\n scoop: feed.scoop,\n prompt: feed.prompt,\n ...(feed.followUp ? { followUp: true } : {}),\n ...(feed.request ? { request: feed.request } : {}),\n },\n {\n ownership: { kind: 'conversation' },\n conversationId: parent as ConversationId,\n background: true,\n }\n );\n (await tx.doc(ScoopWorkDoc)).feeds[String(task)] = {\n scoop: feed.scoop,\n created: feed.created,\n report: feed.report,\n target: feed.target,\n channel: 'scoop-notify',\n from: feed.from,\n agent: feed.agent,\n };\n return Number(task);\n}\n\nexport function choosable(tools: readonly ToolRegistration[]): Choosable {\n const names = new Set(tools.map((tool) => tool.name));\n return (name) => names.has(name) || (mcpEntries([name]) ?? []).length > 0;\n}\n\nfunction selection(\n tools: readonly ToolRegistration[] | undefined,\n names: string[] | undefined,\n mcp?: (names: readonly string[]) => ToolRegistration[]\n) {\n if (!names || !tools) return undefined;\n const picked = tools.filter((tool) => names.includes(tool.name));\n const entries = mcpEntries(names) ?? [];\n if (!entries.length) return picked;\n const codemode = tools.find((tool) => tool.name === CODEMODE);\n if (codemode && !picked.includes(codemode)) picked.push(codemode);\n return [...picked, ...(mcp?.(entries) ?? [])];\n}\n\nfunction liveCount(state: Readonly<AgentsState>, cone: string): number {\n return Object.values(state.scoops).filter((scoop) => scoop.cone === cone && live(scoop)).length;\n}\n\nfunction claimTurn(\n work: WorkState,\n request: SpawnRequest,\n turn: number | null\n): Answer | undefined {\n if (turn === null) return undefined;\n const counted = work.turns[request.cone];\n const count = counted && counted.at === turn ? counted.count : 0;\n if (count >= request.limits.maxPerTurn)\n return {\n code: 1,\n out: `subagent: this turn already started ${count} scoops (the limit is ${request.limits.maxPerTurn})\\n`,\n };\n work.turns[request.cone] = { at: turn, count: count + 1 };\n return undefined;\n}\n\nexport function agentChange(\n request: SpawnRequest,\n folder: string,\n host: Pick<ScoopsHost, 'tools' | 'mcp' | 'layout' | 'exclude'>\n): AgentChange {\n const { tools, mcp } = host;\n const model = request.model ?? modelOf(request.role?.model);\n const thinking = request.thinking ?? request.role?.thinking;\n const chosen = selection(tools, request.tools ?? request.role?.tools, mcp);\n const instructions = [request.role?.prompt, request.instructions].filter(Boolean).join('\\n\\n');\n return {\n cwd: request.cwd ?? workspace(folder, host.layout),\n ...(instructions ? { instructions } : {}),\n ...(model ? { model } : {}),\n ...(thinking ? { thinkingLevel: thinking as AgentChange['thinkingLevel'] } : {}),\n ...(chosen ? { tools: chosen } : {}),\n ...(host.exclude?.length ? { extensions: { remove: host.exclude } } : {}),\n };\n}\n\nasync function narrowMcp(\n tx: Tx,\n state: Readonly<AgentsState>,\n cone: ConversationId,\n child: ConversationId,\n request: SpawnRequest\n): Promise<void> {\n const listed = mcpEntries(request.tools ?? request.role?.tools);\n const caller = state.scoops[request.parent ?? '']?.conversation as ConversationId | undefined;\n const inherited = (await tx.doc(McpAllowDoc, caller ?? cone)).allow;\n if (listed !== null || inherited !== null)\n (await tx.doc(McpAllowDoc, child)).allow = listed ?? inherited;\n}\n\nfunction scoopRecord(\n request: SpawnRequest,\n folder: string,\n conversation: number,\n anchor: number,\n created: Mark | null\n): ScoopRecord {\n return {\n name: request.name.trim() || folder,\n folder,\n cone: request.cone,\n conversation,\n anchor,\n role: request.role?.name ?? null,\n ...(request.role?.memory ? { memory: request.role.memory } : {}),\n ...(request.role?.memory && request.origin ? { origin: request.origin } : {}),\n ...(request.role?.context ? { context: request.role.context } : {}),\n kind: request.kind ?? 'async',\n parent: request.parent ?? request.cone,\n depth: request.depth ?? 1,\n roots: request.roots ?? null,\n created,\n dropped: null,\n gone: false,\n };\n}\n\nasync function spawn(core: Core, request: SpawnRequest, context: Context): Promise<Answer> {\n const done = await answered(core, request.request, context);\n if (done) return done;\n const host = await core.host;\n const { agents } = host;\n const parent = await coneOfId(core, request.cone, context);\n const count = liveCount(agents.state(), request.cone);\n if (count >= request.limits.maxLiveScoops)\n return {\n code: 1,\n out: `subagent: this cone already has ${count} live scoops (the limit is ${request.limits.maxLiveScoops}); stop one first\\n`,\n };\n const created = request.fromAgent ? await tail(parent, context) : null;\n const turn = request.fromAgent ? await turnStart(parent, context) : null;\n return agents.update(async (tx, doc) => {\n const work = await tx.doc(ScoopWorkDoc);\n const refused = claimTurn(work as WorkState, request, turn);\n if (refused) return refused;\n const folder = freeFolder(doc, request.name);\n const id = scoopId(folder);\n const anchor = await tx.createTask(core.tasks().anchor as never, null, {\n ownership: { kind: 'conversation' },\n conversationId: parent.id,\n background: true,\n });\n const child = await tx.createConversation({ ownership: { kind: 'task', taskId: anchor } });\n await configure(tx, child.id, agentChange(request, folder, host));\n await narrowMcp(tx, doc, parent.id, child.id, request);\n if (request.taints?.length) (await tx.doc(GuestTaintDoc, child.id)).taints = request.taints;\n doc.scoops[id] = scoopRecord(request, folder, child.id, Number(anchor), created);\n const target =\n request.target === undefined\n ? request.fromAgent\n ? `cone:${request.cone}`\n : null\n : request.target;\n const made: number[] = [];\n const asker = request.asker === undefined ? target : request.asker;\n const from = askerName(doc, asker);\n for (const prompt of request.prompts)\n made.push(\n await addFeed(core, tx, parent.id, {\n scoop: id,\n created,\n report: target !== null,\n target,\n from,\n agent: asker !== null,\n prompt,\n })\n );\n const answer = { code: 0, out: `${folder}\\n`, tasks: made };\n await request.created?.(tx, { folder, conversation: Number(child.id), tasks: made });\n remember(work as WorkState, request.request, answer);\n return answer;\n }, context);\n}\n\nexport function askerName(state: Readonly<AgentsState>, asker: string | null): string {\n if (asker === null) return 'terminal';\n if (asker.startsWith('cone:')) return state.cones[asker.slice('cone:'.length)]?.name ?? asker;\n return state.scoops[asker]?.name ?? asker;\n}\n\nconst missing = (id: string): Answer => ({\n code: 1,\n out: `subagent: there is no scoop ${id.replace('scoop:', '')}\\n`,\n});\n\nasync function feed(\n core: Core,\n id: string,\n prompt: string,\n options: FeedOptions,\n context: Context\n): Promise<Answer> {\n const done = await answered(core, options.request, context);\n if (done) return done;\n const { harness, agents } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n const parent = await coneOfId(core, record.cone, context);\n const created = options.fromAgent ? await tail(parent, context) : null;\n const { target } = options;\n return harness.commit(async (tx) => {\n if (options.taints?.length) {\n const doc = await tx.doc(GuestTaintDoc, record.conversation as ConversationId);\n doc.taints = distinctTaints([...doc.taints, ...options.taints]);\n }\n await addFeed(core, tx, parent.id, {\n scoop: id,\n created,\n report: target !== null,\n target,\n from: askerName(agents.state(), target),\n agent: target !== null,\n prompt,\n followUp: options.followUp,\n request: options.request,\n });\n const answer = { code: 0, out: `sent to ${record.folder}\\n` };\n remember((await tx.doc(ScoopWorkDoc)) as WorkState, options.request, answer);\n return answer;\n }, context);\n}\n\nasync function signal(core: Core, record: ScoopRecord, context: Context): Promise<void> {\n const { harness, groups } = await core.host;\n const conversation = await harness.conversation(record.conversation as ConversationId, context);\n await conversation?.abort(context, { background: true });\n await groups?.signal(record.conversation);\n}\n\nasync function stopFeeds(\n core: Core,\n scoop: string,\n keep: (feed: Feed) => Promise<boolean>,\n context: Context\n) {\n const { harness } = await core.host;\n const work = (await harness.snapshot(ScoopWorkDoc, context)) ?? { feeds: {} };\n const dropped: string[] = [];\n for (const [task, entry] of Object.entries(work.feeds)) {\n if (entry.scoop !== scoop || (await keep(entry))) continue;\n const record = await harness.getTask(Number(task) as TaskId, context);\n if (record && record.state.status !== 'terminal')\n await harness.abortTask(Number(task) as TaskId, context);\n dropped.push(task);\n }\n if (dropped.length)\n await harness.commit(async (tx) => {\n const doc = await tx.doc(ScoopWorkDoc);\n for (const task of dropped) delete doc.feeds[task];\n }, context);\n return dropped.length;\n}\n\nasync function stop(core: Core, id: string, fromAgent: boolean, context: Context): Promise<Answer> {\n const { agents, files, layout = LEGACY } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n await stopFeeds(core, id, async () => false, context);\n await signal(core, record, context);\n const mark = fromAgent\n ? await tail(await coneOfId(core, record.cone, context), context)\n : { in: 0, at: 0 };\n await agents.update((_tx, state) => {\n (state.scoops[id] as ScoopRecord).dropped = mark;\n }, context);\n const place = layout.workspace(record.folder);\n const removed = await files?.remove(place, { recursive: true, force: true }, context);\n const folder = removed?.ok ? `removed ${place}` : `kept ${place}`;\n return {\n code: 0,\n out: `stopped ${record.folder}; its transcript is kept, ${folder}, and its reports stay in ${layout.reports(record.folder, record.cone)}\\n`,\n };\n}\n\nasync function rename(core: Core, id: string, name: string, context: Context): Promise<Answer> {\n const { agents } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n const wanted = name.trim();\n if (!wanted) return { code: 2, out: 'subagent: rename needs a non-empty name\\n' };\n await agents.update((_tx, state) => {\n (state.scoops[id] as ScoopRecord).name = wanted;\n }, context);\n return { code: 0, out: `${record.folder} is now called ${wanted}\\n` };\n}\n\nasync function resetWorkspace(\n host: Pick<ScoopsHost, 'files' | 'layout'>,\n folder: string,\n context: Context\n): Promise<boolean> {\n const { files } = host;\n if (!files) return true;\n const place = workspace(folder, host.layout);\n const cleared = await files.remove(place, { recursive: true, force: true }, context);\n if (!cleared.ok) return false;\n if (host.layout?.users) return true;\n return (await files.createDir(place, { recursive: true }, context)).ok;\n}\n\ntype Seen = (mark: Mark | null) => Promise<boolean>;\n\nasync function rewindOne(\n core: Core,\n coneId: string,\n id: string,\n record: ScoopRecord,\n seen: Seen,\n result: Rewound,\n context: Context\n): Promise<Partial<ScoopRecord> | undefined> {\n const host = await core.host;\n const { harness, licks } = host;\n if (!(await seen(record.created))) {\n if (live(record)) {\n await stopFeeds(core, id, async () => false, context);\n await signal(core, record, context);\n result.stopped.push(record.name);\n }\n return { gone: true };\n }\n if (record.dropped !== null) {\n if (await seen(record.dropped)) return undefined;\n const reset = await resetWorkspace(host, record.folder, context);\n result.restored.push(\n reset ? record.name : `${record.name} (its working folder could not be reset)`\n );\n return { dropped: null };\n }\n const withdrawn = await stopFeeds(core, id, (entry) => seen(entry.created), context);\n if (!withdrawn) return undefined;\n const conversation = await harness.conversation(record.conversation as ConversationId, context);\n await conversation?.abort(context);\n await licks.forget(`cone:${coneId}`, 'scoop-notify', id, context);\n return undefined;\n}\n\nasync function rewound(\n core: Core,\n coneId: string,\n fork: Conversation,\n context: Context\n): Promise<Rewound> {\n const { harness, agents } = await core.host;\n const read = (id: number) =>\n harness.commit((tx) => tx.conversation(id as ConversationId), context);\n const seen: Seen = async (mark) =>\n mark === null || mark.in === 0 || (await visible(read, fork.id, mark));\n const result: Rewound = { stopped: [], restored: [] };\n const changes: [string, Partial<ScoopRecord>][] = [];\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n if (record.cone !== coneId || record.gone) continue;\n const change = await rewindOne(core, coneId, id, record, seen, result, context);\n if (change) changes.push([id, change]);\n }\n if (changes.length)\n await agents.update((_tx, state) => {\n for (const [id, change] of changes) Object.assign(state.scoops[id] as ScoopRecord, change);\n }, context);\n return result;\n}\n\nasync function list(core: Core, coneId: string | null, context: Context): Promise<ScoopSummary[]> {\n const { agents } = await core.host;\n const out: ScoopSummary[] = [];\n for (const [id, record] of Object.entries(agents.state().scoops)) {\n if ((coneId !== null && record.cone !== coneId) || !live(record)) continue;\n out.push({\n id,\n folder: record.folder,\n name: record.name,\n role: record.role,\n busy: await isBusy(core, record.conversation, context),\n });\n }\n return out;\n}\n\nasync function working(core: Core, ids: readonly string[], context: Context): Promise<string[]> {\n const { harness, agents } = await core.host;\n const feeds = Object.values((await harness.snapshot(ScoopWorkDoc, context))?.feeds ?? {});\n const out: string[] = [];\n for (const id of ids) {\n const record = agents.state().scoops[id];\n if (!record) continue;\n if (\n feeds.some((entry) => entry.scoop === id) ||\n (await isBusy(core, record.conversation, context))\n )\n out.push(id);\n }\n return out;\n}\n\nasync function notifyWhenDone(\n core: Core,\n coneId: string,\n ids: string[],\n deadline: number,\n taints: readonly InheritedTaint[],\n context: Context\n) {\n const { harness } = await core.host;\n const parent = await coneOfId(core, coneId, context);\n await harness.commit(\n (tx) =>\n tx.createTask(\n core.tasks().wait as never,\n { scoops: ids, cone: coneId, deadline, ...(taints.length ? { taints } : {}) },\n { ownership: { kind: 'conversation' }, conversationId: parent.id, background: true }\n ),\n context\n );\n}\n\nasync function detach(core: Core, tasks: readonly number[], target: string, context: Context) {\n const { harness } = await core.host;\n await harness.commit(async (tx) => {\n const work = await tx.doc(ScoopWorkDoc);\n for (const task of tasks) {\n const entry = work.feeds[String(task)];\n if (!entry) continue;\n entry.report = true;\n entry.target = target;\n entry.channel = 'bash';\n }\n }, context);\n}\n\nasync function note(\n core: Core,\n id: string,\n text: string,\n taints: readonly InheritedTaint[],\n context: Context\n): Promise<Answer> {\n const { agents, licks } = await core.host;\n const record = agents.state().scoops[id];\n if (!record || !live(record)) return missing(id);\n const label = `${record.name} (${record.role ?? 'scoop'})`;\n await licks.deliver(\n {\n channel: 'scoop-notify',\n source: `${id}#note`,\n title: label,\n text: `[scoop ${label} progress]`,\n body: text,\n target: `cone:${record.cone}`,\n ...(taints.length ? { taints } : {}),\n },\n context\n );\n return { code: 0, out: 'note sent to the cone\\n' };\n}\n\nexport function createScoops(host: Promise<ScoopsHost>, tasks: () => ScoopTasks) {\n const core: Core = { host, tasks };\n return {\n detach: (made: readonly number[], target: string, context: Context) =>\n detach(core, made, target, context),\n note: (id: string, text: string, taints: readonly InheritedTaint[], context: Context) =>\n note(core, id, text, taints, context),\n lineage: async (pid: number | null) => (await core.host).groups?.lineage(pid),\n spawn: (request: SpawnRequest, context: Context) => spawn(core, request, context),\n feed: (id: string, prompt: string, options: FeedOptions, context: Context) =>\n feed(core, id, prompt, options, context),\n stop: (id: string, fromAgent: boolean, context: Context) => stop(core, id, fromAgent, context),\n rename: (id: string, name: string, context: Context) => rename(core, id, name, context),\n rewound: (coneId: string, fork: Conversation, context: Context) =>\n rewound(core, coneId, fork, context),\n list: (coneId: string | null, context: Context) => list(core, coneId, context),\n busy: (conversation: number, context: Context) => isBusy(core, conversation, context),\n working: (ids: readonly string[], context: Context) => working(core, ids, context),\n notifyWhenDone: (\n coneId: string,\n ids: string[],\n deadline: number,\n taints: readonly InheritedTaint[],\n context: Context\n ) => notifyWhenDone(core, coneId, ids, deadline, taints, context),\n };\n}\n\nexport function modelOf(\n value: string | undefined\n): { provider: string; modelId: string } | undefined {\n if (!value) return undefined;\n const at = value.indexOf('/');\n if (at <= 0) return undefined;\n return { provider: value.slice(0, at), modelId: value.slice(at + 1) };\n}\n\nexport type Scoops = ReturnType<typeof createScoops>;\n"],
5
+ "mappings": "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",
6
6
  "names": ["record", "feed"]
7
7
  }
@@ -1,11 +1,11 @@
1
1
  import { replicatedState } from "@earendil-works/chord";
2
2
  import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
3
3
  import { defineDoc } from "@earendil-works/pi-durable";
4
- import { deltasOnly } from "../checkpoints.js";
4
+ import { boundedDeltas } from "../checkpoints.js";
5
5
  const LEGACY_DATABASE = "slicc-agent-credentials";
6
6
  const MovedDoc = defineDoc({
7
7
  kind: "slicc.keys-moved",
8
- checkpointWhen: deltasOnly,
8
+ checkpointWhen: boundedDeltas,
9
9
  version: 1,
10
10
  scope: "session",
11
11
  initial: () => ({ accounts: [], dismissed: false })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/secrets/legacy.ts"],
4
- "sourcesContent": ["import { type Context, type ReplicatedState, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { defineDoc, type Harness } from '@earendil-works/pi-durable';\nimport { deltasOnly } from '../checkpoints.ts';\nimport type { VaultRecords } from './records.ts';\nimport type { Vault } from './vault.ts';\n\nexport const LEGACY_DATABASE = 'slicc-agent-credentials';\n\nexport type MovedAccount =\n | { kind: 'model'; provider: string; label: string }\n | { kind: 'mcp'; server: string; label: string };\n\nexport type MovedState = {\n accounts: MovedAccount[];\n dismissed: boolean;\n};\n\nexport const MovedDoc = defineDoc<MovedState>({\n kind: 'slicc.keys-moved',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ accounts: [], dismissed: false }),\n});\n\nconst NAMES: Record<string, string> = { adobe: 'Adobe', 'amazon-bedrock': 'Bedrock' };\n\nfunction accountId(account: MovedAccount): string {\n return account.kind === 'mcp' ? `mcp:${account.server}` : account.provider;\n}\n\nexport function movedAccount(key: string): MovedAccount {\n if (key.startsWith('mcp:')) {\n const server = key.slice(4).split('|')[0] as string;\n return { kind: 'mcp', server, label: server };\n }\n return { kind: 'model', provider: key, label: NAMES[key] ?? key };\n}\n\nfunction request<T>(operation: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n operation.onsuccess = () => resolve(operation.result);\n operation.onerror = () => reject(operation.error);\n });\n}\n\nasync function legacyKeys(factory: IDBFactory): Promise<string[]> {\n const db = await request(factory.open(LEGACY_DATABASE));\n try {\n if (!db.objectStoreNames.contains('credentials')) return [];\n const store = db.transaction('credentials', 'readonly').objectStore('credentials');\n return ((await request(store.getAllKeys())) as IDBValidKey[]).map(String);\n } finally {\n db.close();\n }\n}\n\nfunction deleted(factory: IDBFactory): Promise<boolean> {\n return new Promise((resolve) => {\n const deleting = factory.deleteDatabase(LEGACY_DATABASE);\n deleting.onsuccess = () => resolve(true);\n deleting.onerror = () => resolve(false);\n });\n}\n\nexport const WIPE_WAIT_MS = 3000;\n\nexport interface Wiped {\n now: MovedAccount[];\n later: Promise<MovedAccount[]>;\n}\n\nconst nothing = (): Wiped => ({ now: [], later: Promise.resolve([]) });\n\nexport async function wipeLegacy(\n factory: IDBFactory | undefined = globalThis.indexedDB,\n wait = WIPE_WAIT_MS\n): Promise<Wiped> {\n if (!factory) return nothing();\n const listed = await factory.databases().catch(() => []);\n if (!listed.some((database) => database.name === LEGACY_DATABASE)) return nothing();\n const keys = await legacyKeys(factory).catch(() => []);\n const found = new Map(keys.map(movedAccount).map((account) => [accountId(account), account]));\n const done = deleted(factory).then((ok) => (ok ? [...found.values()] : []));\n const { promise: bound, resolve } = Promise.withResolvers<undefined>();\n const timer = setTimeout(() => resolve(undefined), wait);\n const first = await Promise.race([done, bound]);\n clearTimeout(timer);\n return first ? { now: first, later: Promise.resolve([]) } : { now: [], later: done };\n}\n\nexport type MovedName = { kind: 'model' | 'mcp'; label: string };\n\nexport interface KeysMoved {\n readonly state: ReplicatedState<MovedName[]>;\n dismiss(context: Context): Promise<void>;\n}\n\ntype Records = Pick<VaultRecords, 'state' | 'model' | 'mcp' | 'refresh'>;\n\nexport async function keysMoved(\n harness: Pick<Harness, 'commit'>,\n sources: { records: Records; vault: Pick<Vault, 'connected'> },\n wiped: Wiped,\n context: Context = BACKGROUND_CONTEXT\n): Promise<KeysMoved> {\n const { records, vault } = sources;\n const add = (accounts: readonly MovedAccount[], using: Context) =>\n harness.commit(async (tx) => {\n const doc = await tx.doc(MovedDoc);\n const known = new Set(doc.accounts.map(accountId));\n for (const account of accounts)\n if (!known.has(accountId(account))) doc.accounts.push(account);\n return JSON.parse(JSON.stringify(doc)) as MovedState;\n }, using);\n let saved = await add(wiped.now, context);\n let read = false;\n const missing = (account: MovedAccount) =>\n account.kind === 'mcp' ? !records.mcp(account.server) : !records.model(account.provider);\n const pending = () =>\n read && !saved.dismissed\n ? saved.accounts.filter(missing).map(({ kind, label }) => ({ kind, label }))\n : [];\n const state = replicatedState<MovedName[]>(pending());\n const update = (using: Context) => {\n const next = pending();\n if (JSON.stringify(next) !== JSON.stringify(state.value)) state.replace(using, next);\n };\n const mark = (using: Context) => {\n if (vault.connected) read = true;\n update(using);\n };\n records.state.subscribe((_value, using) => mark(using));\n wiped.later\n .then(async (late) => {\n if (!late.length) return;\n saved = await add(late, context);\n update(context);\n })\n .catch(() => undefined);\n void records.refresh(context).then(\n () => mark(context),\n () => undefined\n );\n return {\n state,\n async dismiss(using) {\n saved = await harness.commit(async (tx) => {\n const doc = await tx.doc(MovedDoc);\n doc.dismissed = true;\n return JSON.parse(JSON.stringify(doc)) as MovedState;\n }, using);\n update(using);\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 { defineDoc, type Harness } from '@earendil-works/pi-durable';\nimport { boundedDeltas } from '../checkpoints.ts';\nimport type { VaultRecords } from './records.ts';\nimport type { Vault } from './vault.ts';\n\nexport const LEGACY_DATABASE = 'slicc-agent-credentials';\n\nexport type MovedAccount =\n | { kind: 'model'; provider: string; label: string }\n | { kind: 'mcp'; server: string; label: string };\n\nexport type MovedState = {\n accounts: MovedAccount[];\n dismissed: boolean;\n};\n\nexport const MovedDoc = defineDoc<MovedState>({\n kind: 'slicc.keys-moved',\n checkpointWhen: boundedDeltas,\n version: 1,\n scope: 'session',\n initial: () => ({ accounts: [], dismissed: false }),\n});\n\nconst NAMES: Record<string, string> = { adobe: 'Adobe', 'amazon-bedrock': 'Bedrock' };\n\nfunction accountId(account: MovedAccount): string {\n return account.kind === 'mcp' ? `mcp:${account.server}` : account.provider;\n}\n\nexport function movedAccount(key: string): MovedAccount {\n if (key.startsWith('mcp:')) {\n const server = key.slice(4).split('|')[0] as string;\n return { kind: 'mcp', server, label: server };\n }\n return { kind: 'model', provider: key, label: NAMES[key] ?? key };\n}\n\nfunction request<T>(operation: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n operation.onsuccess = () => resolve(operation.result);\n operation.onerror = () => reject(operation.error);\n });\n}\n\nasync function legacyKeys(factory: IDBFactory): Promise<string[]> {\n const db = await request(factory.open(LEGACY_DATABASE));\n try {\n if (!db.objectStoreNames.contains('credentials')) return [];\n const store = db.transaction('credentials', 'readonly').objectStore('credentials');\n return ((await request(store.getAllKeys())) as IDBValidKey[]).map(String);\n } finally {\n db.close();\n }\n}\n\nfunction deleted(factory: IDBFactory): Promise<boolean> {\n return new Promise((resolve) => {\n const deleting = factory.deleteDatabase(LEGACY_DATABASE);\n deleting.onsuccess = () => resolve(true);\n deleting.onerror = () => resolve(false);\n });\n}\n\nexport const WIPE_WAIT_MS = 3000;\n\nexport interface Wiped {\n now: MovedAccount[];\n later: Promise<MovedAccount[]>;\n}\n\nconst nothing = (): Wiped => ({ now: [], later: Promise.resolve([]) });\n\nexport async function wipeLegacy(\n factory: IDBFactory | undefined = globalThis.indexedDB,\n wait = WIPE_WAIT_MS\n): Promise<Wiped> {\n if (!factory) return nothing();\n const listed = await factory.databases().catch(() => []);\n if (!listed.some((database) => database.name === LEGACY_DATABASE)) return nothing();\n const keys = await legacyKeys(factory).catch(() => []);\n const found = new Map(keys.map(movedAccount).map((account) => [accountId(account), account]));\n const done = deleted(factory).then((ok) => (ok ? [...found.values()] : []));\n const { promise: bound, resolve } = Promise.withResolvers<undefined>();\n const timer = setTimeout(() => resolve(undefined), wait);\n const first = await Promise.race([done, bound]);\n clearTimeout(timer);\n return first ? { now: first, later: Promise.resolve([]) } : { now: [], later: done };\n}\n\nexport type MovedName = { kind: 'model' | 'mcp'; label: string };\n\nexport interface KeysMoved {\n readonly state: ReplicatedState<MovedName[]>;\n dismiss(context: Context): Promise<void>;\n}\n\ntype Records = Pick<VaultRecords, 'state' | 'model' | 'mcp' | 'refresh'>;\n\nexport async function keysMoved(\n harness: Pick<Harness, 'commit'>,\n sources: { records: Records; vault: Pick<Vault, 'connected'> },\n wiped: Wiped,\n context: Context = BACKGROUND_CONTEXT\n): Promise<KeysMoved> {\n const { records, vault } = sources;\n const add = (accounts: readonly MovedAccount[], using: Context) =>\n harness.commit(async (tx) => {\n const doc = await tx.doc(MovedDoc);\n const known = new Set(doc.accounts.map(accountId));\n for (const account of accounts)\n if (!known.has(accountId(account))) doc.accounts.push(account);\n return JSON.parse(JSON.stringify(doc)) as MovedState;\n }, using);\n let saved = await add(wiped.now, context);\n let read = false;\n const missing = (account: MovedAccount) =>\n account.kind === 'mcp' ? !records.mcp(account.server) : !records.model(account.provider);\n const pending = () =>\n read && !saved.dismissed\n ? saved.accounts.filter(missing).map(({ kind, label }) => ({ kind, label }))\n : [];\n const state = replicatedState<MovedName[]>(pending());\n const update = (using: Context) => {\n const next = pending();\n if (JSON.stringify(next) !== JSON.stringify(state.value)) state.replace(using, next);\n };\n const mark = (using: Context) => {\n if (vault.connected) read = true;\n update(using);\n };\n records.state.subscribe((_value, using) => mark(using));\n wiped.later\n .then(async (late) => {\n if (!late.length) return;\n saved = await add(late, context);\n update(context);\n })\n .catch(() => undefined);\n void records.refresh(context).then(\n () => mark(context),\n () => undefined\n );\n return {\n state,\n async dismiss(using) {\n saved = await harness.commit(async (tx) => {\n const doc = await tx.doc(MovedDoc);\n doc.dismissed = true;\n return JSON.parse(JSON.stringify(doc)) as MovedState;\n }, using);\n update(using);\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/selection.js CHANGED
@@ -2,12 +2,12 @@ import {
2
2
  AgentDoc,
3
3
  defineDoc
4
4
  } from "@earendil-works/pi-durable";
5
- import { deltasOnly } from "./checkpoints.js";
5
+ import { boundedDeltas } from "./checkpoints.js";
6
6
  const SELECTION = ["extensions", "tools", "modelTools", "instructions"];
7
7
  const ALL = ["model", "thinkingLevel", "cwd", ...SELECTION];
8
8
  const SelectionDoc = defineDoc({
9
9
  kind: "slicc.selection",
10
- checkpointWhen: deltasOnly,
10
+ checkpointWhen: boundedDeltas,
11
11
  version: 1,
12
12
  scope: "session",
13
13
  initial: () => ({ version: 0 })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/selection.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n AgentDoc,\n type AgentState,\n type ConversationId,\n type ConversationInit,\n type Cursor,\n defineDoc,\n type Harness,\n type HarnessOptions,\n type Storage,\n} from '@earendil-works/pi-durable';\nimport { deltasOnly } from './checkpoints.ts';\n\nexport type Choosable = (tool: string) => boolean;\n\ntype Field = keyof AgentState;\n\nexport const SELECTION: readonly Field[] = ['extensions', 'tools', 'modelTools', 'instructions'];\n\nconst ALL: readonly Field[] = ['model', 'thinkingLevel', 'cwd', ...SELECTION];\n\nexport const SelectionDoc = defineDoc<{ version: number }>({\n kind: 'slicc.selection',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ version: 0 }),\n});\n\nexport const SELECTION_VERSION = 1;\n\nexport async function storedAgent(\n harness: Pick<Harness, 'snapshot'>,\n conversationId: ConversationId,\n context: Context\n): Promise<AgentState> {\n return structuredClone((await harness.snapshot(AgentDoc, conversationId, context)) ?? {});\n}\n\nexport function copyAgent(from: AgentState, fields: readonly Field[] = ALL): ConversationInit {\n return async (tx, conversationId) => {\n const agent = (await tx.doc(AgentDoc, conversationId)) as Record<string, unknown>;\n for (const field of fields) {\n const value = from[field];\n if (value === undefined) delete agent[field];\n else agent[field] = structuredClone(value);\n }\n };\n}\n\nexport function unfrozen(agent: AgentState, choosable: Choosable): boolean {\n let changed = false;\n if (Array.isArray(agent.extensions)) {\n delete agent.extensions;\n changed = true;\n }\n if (Array.isArray(agent.tools) && !agent.tools.every(choosable)) {\n delete agent.tools;\n changed = true;\n }\n return changed;\n}\n\nexport function unfreezeCreated(choosable: Choosable): HarnessOptions['conversationCreated'] {\n return async (tx, conversation) => {\n unfrozen(await tx.doc(AgentDoc, conversation.id), choosable);\n };\n}\n\nexport async function unfreeze(\n harness: Pick<Harness, 'snapshot' | 'commit'>,\n storage: Pick<Storage, 'scanConversations'>,\n choosable: Choosable,\n context: Context\n): Promise<number> {\n const done = await harness.snapshot(SelectionDoc, context);\n if ((done?.version ?? 0) >= SELECTION_VERSION) return 0;\n let fixed = 0;\n let cursor: Cursor | undefined;\n do {\n const page = await storage.scanConversations({}, 100, cursor, context);\n for (const record of page.items)\n if (\n await harness.commit(\n async (tx) => unfrozen(await tx.doc(AgentDoc, record.id), choosable),\n context\n )\n )\n fixed++;\n cursor = page.next;\n } while (cursor);\n await harness.commit(async (tx) => {\n (await tx.doc(SelectionDoc)).version = SELECTION_VERSION;\n }, context);\n return fixed;\n}\n"],
5
- "mappings": "AACA;AAAA,EACE;AAAA,EAKA;AAAA,OAIK;AACP,SAAS,kBAAkB;AAMpB,MAAM,YAA8B,CAAC,cAAc,SAAS,cAAc,cAAc;AAE/F,MAAM,MAAwB,CAAC,SAAS,iBAAiB,OAAO,GAAG,SAAS;AAErE,MAAM,eAAe,UAA+B;AAAA,EACzD,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,SAAS,EAAE;AAC/B,CAAC;AAEM,MAAM,oBAAoB;AAEjC,eAAsB,YACpB,SACA,gBACA,SACqB;AACrB,SAAO,gBAAiB,MAAM,QAAQ,SAAS,UAAU,gBAAgB,OAAO,KAAM,CAAC,CAAC;AAC1F;AAEO,SAAS,UAAU,MAAkB,SAA2B,KAAuB;AAC5F,SAAO,OAAO,IAAI,mBAAmB;AACnC,UAAM,QAAS,MAAM,GAAG,IAAI,UAAU,cAAc;AACpD,eAAW,SAAS,QAAQ;AAC1B,YAAM,QAAQ,KAAK,KAAK;AACxB,UAAI,UAAU,OAAW,QAAO,MAAM,KAAK;AAAA,UACtC,OAAM,KAAK,IAAI,gBAAgB,KAAK;AAAA,IAC3C;AAAA,EACF;AACF;AAEO,SAAS,SAAS,OAAmB,WAA+B;AACzE,MAAI,UAAU;AACd,MAAI,MAAM,QAAQ,MAAM,UAAU,GAAG;AACnC,WAAO,MAAM;AACb,cAAU;AAAA,EACZ;AACA,MAAI,MAAM,QAAQ,MAAM,KAAK,KAAK,CAAC,MAAM,MAAM,MAAM,SAAS,GAAG;AAC/D,WAAO,MAAM;AACb,cAAU;AAAA,EACZ;AACA,SAAO;AACT;AAEO,SAAS,gBAAgB,WAA6D;AAC3F,SAAO,OAAO,IAAI,iBAAiB;AACjC,aAAS,MAAM,GAAG,IAAI,UAAU,aAAa,EAAE,GAAG,SAAS;AAAA,EAC7D;AACF;AAEA,eAAsB,SACpB,SACA,SACA,WACA,SACiB;AACjB,QAAM,OAAO,MAAM,QAAQ,SAAS,cAAc,OAAO;AACzD,OAAK,MAAM,WAAW,MAAM,kBAAmB,QAAO;AACtD,MAAI,QAAQ;AACZ,MAAI;AACJ,KAAG;AACD,UAAM,OAAO,MAAM,QAAQ,kBAAkB,CAAC,GAAG,KAAK,QAAQ,OAAO;AACrE,eAAW,UAAU,KAAK;AACxB,UACE,MAAM,QAAQ;AAAA,QACZ,OAAO,OAAO,SAAS,MAAM,GAAG,IAAI,UAAU,OAAO,EAAE,GAAG,SAAS;AAAA,QACnE;AAAA,MACF;AAEA;AACJ,aAAS,KAAK;AAAA,EAChB,SAAS;AACT,QAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,KAAC,MAAM,GAAG,IAAI,YAAY,GAAG,UAAU;AAAA,EACzC,GAAG,OAAO;AACV,SAAO;AACT;",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n AgentDoc,\n type AgentState,\n type ConversationId,\n type ConversationInit,\n type Cursor,\n defineDoc,\n type Harness,\n type HarnessOptions,\n type Storage,\n} from '@earendil-works/pi-durable';\nimport { boundedDeltas } from './checkpoints.ts';\n\nexport type Choosable = (tool: string) => boolean;\n\ntype Field = keyof AgentState;\n\nexport const SELECTION: readonly Field[] = ['extensions', 'tools', 'modelTools', 'instructions'];\n\nconst ALL: readonly Field[] = ['model', 'thinkingLevel', 'cwd', ...SELECTION];\n\nexport const SelectionDoc = defineDoc<{ version: number }>({\n kind: 'slicc.selection',\n checkpointWhen: boundedDeltas,\n version: 1,\n scope: 'session',\n initial: () => ({ version: 0 }),\n});\n\nexport const SELECTION_VERSION = 1;\n\nexport async function storedAgent(\n harness: Pick<Harness, 'snapshot'>,\n conversationId: ConversationId,\n context: Context\n): Promise<AgentState> {\n return structuredClone((await harness.snapshot(AgentDoc, conversationId, context)) ?? {});\n}\n\nexport function copyAgent(from: AgentState, fields: readonly Field[] = ALL): ConversationInit {\n return async (tx, conversationId) => {\n const agent = (await tx.doc(AgentDoc, conversationId)) as Record<string, unknown>;\n for (const field of fields) {\n const value = from[field];\n if (value === undefined) delete agent[field];\n else agent[field] = structuredClone(value);\n }\n };\n}\n\nexport function unfrozen(agent: AgentState, choosable: Choosable): boolean {\n let changed = false;\n if (Array.isArray(agent.extensions)) {\n delete agent.extensions;\n changed = true;\n }\n if (Array.isArray(agent.tools) && !agent.tools.every(choosable)) {\n delete agent.tools;\n changed = true;\n }\n return changed;\n}\n\nexport function unfreezeCreated(choosable: Choosable): HarnessOptions['conversationCreated'] {\n return async (tx, conversation) => {\n unfrozen(await tx.doc(AgentDoc, conversation.id), choosable);\n };\n}\n\nexport async function unfreeze(\n harness: Pick<Harness, 'snapshot' | 'commit'>,\n storage: Pick<Storage, 'scanConversations'>,\n choosable: Choosable,\n context: Context\n): Promise<number> {\n const done = await harness.snapshot(SelectionDoc, context);\n if ((done?.version ?? 0) >= SELECTION_VERSION) return 0;\n let fixed = 0;\n let cursor: Cursor | undefined;\n do {\n const page = await storage.scanConversations({}, 100, cursor, context);\n for (const record of page.items)\n if (\n await harness.commit(\n async (tx) => unfrozen(await tx.doc(AgentDoc, record.id), choosable),\n context\n )\n )\n fixed++;\n cursor = page.next;\n } while (cursor);\n await harness.commit(async (tx) => {\n (await tx.doc(SelectionDoc)).version = SELECTION_VERSION;\n }, context);\n return fixed;\n}\n"],
5
+ "mappings": "AACA;AAAA,EACE;AAAA,EAKA;AAAA,OAIK;AACP,SAAS,qBAAqB;AAMvB,MAAM,YAA8B,CAAC,cAAc,SAAS,cAAc,cAAc;AAE/F,MAAM,MAAwB,CAAC,SAAS,iBAAiB,OAAO,GAAG,SAAS;AAErE,MAAM,eAAe,UAA+B;AAAA,EACzD,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS,OAAO,EAAE,SAAS,EAAE;AAC/B,CAAC;AAEM,MAAM,oBAAoB;AAEjC,eAAsB,YACpB,SACA,gBACA,SACqB;AACrB,SAAO,gBAAiB,MAAM,QAAQ,SAAS,UAAU,gBAAgB,OAAO,KAAM,CAAC,CAAC;AAC1F;AAEO,SAAS,UAAU,MAAkB,SAA2B,KAAuB;AAC5F,SAAO,OAAO,IAAI,mBAAmB;AACnC,UAAM,QAAS,MAAM,GAAG,IAAI,UAAU,cAAc;AACpD,eAAW,SAAS,QAAQ;AAC1B,YAAM,QAAQ,KAAK,KAAK;AACxB,UAAI,UAAU,OAAW,QAAO,MAAM,KAAK;AAAA,UACtC,OAAM,KAAK,IAAI,gBAAgB,KAAK;AAAA,IAC3C;AAAA,EACF;AACF;AAEO,SAAS,SAAS,OAAmB,WAA+B;AACzE,MAAI,UAAU;AACd,MAAI,MAAM,QAAQ,MAAM,UAAU,GAAG;AACnC,WAAO,MAAM;AACb,cAAU;AAAA,EACZ;AACA,MAAI,MAAM,QAAQ,MAAM,KAAK,KAAK,CAAC,MAAM,MAAM,MAAM,SAAS,GAAG;AAC/D,WAAO,MAAM;AACb,cAAU;AAAA,EACZ;AACA,SAAO;AACT;AAEO,SAAS,gBAAgB,WAA6D;AAC3F,SAAO,OAAO,IAAI,iBAAiB;AACjC,aAAS,MAAM,GAAG,IAAI,UAAU,aAAa,EAAE,GAAG,SAAS;AAAA,EAC7D;AACF;AAEA,eAAsB,SACpB,SACA,SACA,WACA,SACiB;AACjB,QAAM,OAAO,MAAM,QAAQ,SAAS,cAAc,OAAO;AACzD,OAAK,MAAM,WAAW,MAAM,kBAAmB,QAAO;AACtD,MAAI,QAAQ;AACZ,MAAI;AACJ,KAAG;AACD,UAAM,OAAO,MAAM,QAAQ,kBAAkB,CAAC,GAAG,KAAK,QAAQ,OAAO;AACrE,eAAW,UAAU,KAAK;AACxB,UACE,MAAM,QAAQ;AAAA,QACZ,OAAO,OAAO,SAAS,MAAM,GAAG,IAAI,UAAU,OAAO,EAAE,GAAG,SAAS;AAAA,QACnE;AAAA,MACF;AAEA;AACJ,aAAS,KAAK;AAAA,EAChB,SAAS;AACT,QAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,KAAC,MAAM,GAAG,IAAI,YAAY,GAAG,UAAU;AAAA,EACzC,GAAG,OAAO;AACV,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
1
  import { defineDoc } from "@earendil-works/pi-durable";
2
- import { deltasOnly } from "../checkpoints.js";
2
+ import { boundedDeltas } from "../checkpoints.js";
3
3
  import { normalize } from "../kernel/paths.js";
4
4
  import { SPRINKLE_KIND } from "./kind.js";
5
5
  const SPRINKLES_DIR = "/home/sprinkles";
@@ -12,7 +12,7 @@ const WELCOME_PROFILE = "/home/.welcome.json";
12
12
  const DEFAULT_ICON = "sparkles";
13
13
  const SprinklesDoc = defineDoc({
14
14
  kind: "slicc.sprinkles",
15
- checkpointWhen: deltasOnly,
15
+ checkpointWhen: boundedDeltas,
16
16
  version: 1,
17
17
  scope: "session",
18
18
  initial: () => ({ owners: {}, state: {}, welcomed: false })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/sprinkles/sprinkles.ts"],
4
- "sourcesContent": ["import type { Context, JsonValue } from '@earendil-works/chord';\nimport { defineDoc } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { deltasOnly } from '../checkpoints.ts';\nimport { normalize } from '../kernel/paths.ts';\n\nexport { SPRINKLE_KIND, type Sprinkle } from './kind.ts';\n\nexport const SPRINKLES_DIR = '/home/sprinkles';\nexport const WELCOME = 'welcome';\nexport const SUGGESTIONS = 'suggestions';\nexport const SUGGESTIONS_DIR = '/home/.gelatiere';\nexport const SUGGESTIONS_STORE = `${SUGGESTIONS_DIR}/suggestions.json`;\nexport const WELCOMED = '/home/.welcomed';\nexport const WELCOME_PROFILE = '/home/.welcome.json';\nexport const DEFAULT_ICON = 'sparkles';\n\nexport type SprinkleMethod = 'readFile' | 'exists' | 'getState' | 'setState';\n\nexport const SprinklesDoc = defineDoc<{\n owners: Record<string, string>;\n state: Record<string, JsonValue>;\n welcomed: boolean;\n}>({\n kind: 'slicc.sprinkles',\n checkpointWhen: deltasOnly,\n version: 1,\n scope: 'session',\n initial: () => ({ owners: {}, state: {}, welcomed: false }),\n});\n\nfunction decode(text: string): string {\n return text\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&amp;/g, '&');\n}\n\nexport function describe(name: string, html: string): { title: string; icon: string } {\n const title = /<title>([\\s\\S]*?)<\\/title>/i.exec(html)?.[1]?.trim();\n const icon =\n /<link\\b[^>]*\\brel=[\"']icon[\"'][^>]*\\bhref=[\"']([^\"']+)[\"']/i.exec(html)?.[1] ??\n /<link\\b[^>]*\\bhref=[\"']([^\"']+)[\"'][^>]*\\brel=[\"']icon[\"']/i.exec(html)?.[1];\n return { title: title ? decode(title) : name, icon: icon ?? DEFAULT_ICON };\n}\n\nexport const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;\n\nexport async function sprinkleFiles(\n env: Pick<ExecutionEnv, 'listDir'>,\n dir: string,\n context: Context\n): Promise<{ name: string; path: string }[]> {\n const listed = await env.listDir(dir, context);\n if (!listed.ok) return [];\n const out = new Map<string, string>();\n for (const entry of [...listed.value].sort((a, b) => a.name.localeCompare(b.name))) {\n if (entry.kind !== 'directory' && entry.name.endsWith('.shtml')) {\n const name = entry.name.slice(0, -'.shtml'.length);\n if (NAME.test(name)) out.set(name, `${dir}/${entry.name}`);\n }\n if (entry.kind === 'directory' && NAME.test(entry.name) && !out.has(entry.name)) {\n const inner = await env.listDir(`${dir}/${entry.name}`, context);\n if (inner.ok && inner.value.some((item) => item.name === `${entry.name}.shtml`))\n out.set(entry.name, `${dir}/${entry.name}/${entry.name}.shtml`);\n }\n }\n return [...out].map(([name, path]) => ({ name, path }));\n}\n\nexport function homePath(path: unknown, shared = '/home', root = '/home'): string {\n if (typeof path !== 'string' || !path) throw new Error('a sprinkle reads files by absolute path');\n const mapped = path.startsWith('/shared/') ? `${shared}/${path.slice('/shared/'.length)}` : path;\n const segments = mapped.split('/');\n if (segments.includes('.') || segments.includes('..'))\n throw new Error(`a sprinkle reads paths without . or .. segments, not ${path}`);\n const resolved = normalize(mapped);\n if (resolved !== root && !resolved.startsWith(`${root}/`))\n throw new Error(`a sprinkle can only read files in ${root}, not ${path}`);\n return resolved;\n}\n\nexport function sharedPaths(shared: string) {\n return {\n suggestionsDir: `${shared}/.gelatiere`,\n suggestions: `${shared}/.gelatiere/suggestions.json`,\n welcomed: `${shared}/.welcomed`,\n profile: `${shared}/.welcome.json`,\n };\n}\n\nexport const NO_EXEC =\n \"sprinkles can't run commands in SLICC; send the agent a message with slicc.lick() and let it run them\";\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context, JsonValue } from '@earendil-works/chord';\nimport { defineDoc } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { boundedDeltas } from '../checkpoints.ts';\nimport { normalize } from '../kernel/paths.ts';\n\nexport { SPRINKLE_KIND, type Sprinkle } from './kind.ts';\n\nexport const SPRINKLES_DIR = '/home/sprinkles';\nexport const WELCOME = 'welcome';\nexport const SUGGESTIONS = 'suggestions';\nexport const SUGGESTIONS_DIR = '/home/.gelatiere';\nexport const SUGGESTIONS_STORE = `${SUGGESTIONS_DIR}/suggestions.json`;\nexport const WELCOMED = '/home/.welcomed';\nexport const WELCOME_PROFILE = '/home/.welcome.json';\nexport const DEFAULT_ICON = 'sparkles';\n\nexport type SprinkleMethod = 'readFile' | 'exists' | 'getState' | 'setState';\n\nexport const SprinklesDoc = defineDoc<{\n owners: Record<string, string>;\n state: Record<string, JsonValue>;\n welcomed: boolean;\n}>({\n kind: 'slicc.sprinkles',\n checkpointWhen: boundedDeltas,\n version: 1,\n scope: 'session',\n initial: () => ({ owners: {}, state: {}, welcomed: false }),\n});\n\nfunction decode(text: string): string {\n return text\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&amp;/g, '&');\n}\n\nexport function describe(name: string, html: string): { title: string; icon: string } {\n const title = /<title>([\\s\\S]*?)<\\/title>/i.exec(html)?.[1]?.trim();\n const icon =\n /<link\\b[^>]*\\brel=[\"']icon[\"'][^>]*\\bhref=[\"']([^\"']+)[\"']/i.exec(html)?.[1] ??\n /<link\\b[^>]*\\bhref=[\"']([^\"']+)[\"'][^>]*\\brel=[\"']icon[\"']/i.exec(html)?.[1];\n return { title: title ? decode(title) : name, icon: icon ?? DEFAULT_ICON };\n}\n\nexport const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;\n\nexport async function sprinkleFiles(\n env: Pick<ExecutionEnv, 'listDir'>,\n dir: string,\n context: Context\n): Promise<{ name: string; path: string }[]> {\n const listed = await env.listDir(dir, context);\n if (!listed.ok) return [];\n const out = new Map<string, string>();\n for (const entry of [...listed.value].sort((a, b) => a.name.localeCompare(b.name))) {\n if (entry.kind !== 'directory' && entry.name.endsWith('.shtml')) {\n const name = entry.name.slice(0, -'.shtml'.length);\n if (NAME.test(name)) out.set(name, `${dir}/${entry.name}`);\n }\n if (entry.kind === 'directory' && NAME.test(entry.name) && !out.has(entry.name)) {\n const inner = await env.listDir(`${dir}/${entry.name}`, context);\n if (inner.ok && inner.value.some((item) => item.name === `${entry.name}.shtml`))\n out.set(entry.name, `${dir}/${entry.name}/${entry.name}.shtml`);\n }\n }\n return [...out].map(([name, path]) => ({ name, path }));\n}\n\nexport function homePath(path: unknown, shared = '/home', root = '/home'): string {\n if (typeof path !== 'string' || !path) throw new Error('a sprinkle reads files by absolute path');\n const mapped = path.startsWith('/shared/') ? `${shared}/${path.slice('/shared/'.length)}` : path;\n const segments = mapped.split('/');\n if (segments.includes('.') || segments.includes('..'))\n throw new Error(`a sprinkle reads paths without . or .. segments, not ${path}`);\n const resolved = normalize(mapped);\n if (resolved !== root && !resolved.startsWith(`${root}/`))\n throw new Error(`a sprinkle can only read files in ${root}, not ${path}`);\n return resolved;\n}\n\nexport function sharedPaths(shared: string) {\n return {\n suggestionsDir: `${shared}/.gelatiere`,\n suggestions: `${shared}/.gelatiere/suggestions.json`,\n welcomed: `${shared}/.welcomed`,\n profile: `${shared}/.welcome.json`,\n };\n}\n\nexport const NO_EXEC =\n \"sprinkles can't run commands in SLICC; send the agent a message with slicc.lick() and let it run them\";\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,14 +4,14 @@ import {
4
4
  defineDoc,
5
5
  UsageDoc
6
6
  } from "@earendil-works/pi-durable";
7
- import { deltasOnly } from "../checkpoints.js";
7
+ import { boundedDeltas } from "../checkpoints.js";
8
8
  import { vaultHold } from "../secrets/view.js";
9
9
  import { callIdsOf, clusterOf, describe } from "./cluster.js";
10
10
  import { scrubbed } from "./generate.js";
11
11
  import { labelKey, shownLabel } from "./shown.js";
12
12
  const LabelsDoc = defineDoc({
13
13
  kind: "slicc.labels",
14
- checkpointWhen: deltasOnly,
14
+ checkpointWhen: boundedDeltas,
15
15
  version: 1,
16
16
  scope: "session",
17
17
  initial: () => ({ labels: {} })