@ai-ecoverse/slicc-agent 4.3.5 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +8 -9
  2. package/dist/browser/guard.js +1 -1
  3. package/dist/browser/guard.js.map +2 -2
  4. package/dist/client.js +89 -84
  5. package/dist/client.js.map +3 -3
  6. package/dist/codemode/index.js +8 -12
  7. package/dist/codemode/index.js.map +2 -2
  8. package/dist/host.js +69 -117
  9. package/dist/host.js.map +3 -3
  10. package/dist/images/index.js +2 -2
  11. package/dist/images/index.js.map +2 -2
  12. package/dist/index.js +4 -13
  13. package/dist/index.js.map +2 -2
  14. package/dist/licks/extension.js +2 -2
  15. package/dist/licks/extension.js.map +2 -2
  16. package/dist/mcp/tools.js +3 -3
  17. package/dist/mcp/tools.js.map +2 -2
  18. package/dist/memory/extract.js +3 -1
  19. package/dist/memory/extract.js.map +2 -2
  20. package/dist/memory/prompt.js +2 -2
  21. package/dist/memory/prompt.js.map +2 -2
  22. package/dist/mismatch.js +9 -4
  23. package/dist/mismatch.js.map +2 -2
  24. package/dist/patches.js +1 -22
  25. package/dist/patches.js.map +2 -2
  26. package/dist/readonly.js +12 -38
  27. package/dist/readonly.js.map +2 -2
  28. package/dist/scoops/extension.js +1 -1
  29. package/dist/scoops/extension.js.map +2 -2
  30. package/dist/secrets/extension.js +13 -5
  31. package/dist/secrets/extension.js.map +2 -2
  32. package/dist/services.js +111 -13
  33. package/dist/services.js.map +2 -2
  34. package/dist/serving.js +174 -0
  35. package/dist/serving.js.map +7 -0
  36. package/dist/settings.js +4 -18
  37. package/dist/settings.js.map +2 -2
  38. package/dist/spectrum/agent-port.js +5 -1
  39. package/dist/spectrum/agent-port.js.map +2 -2
  40. package/dist/spectrum/index.js +11 -2
  41. package/dist/spectrum/index.js.map +2 -2
  42. package/dist/spectrum/notices-port.js +17 -2
  43. package/dist/spectrum/notices-port.js.map +2 -2
  44. package/dist/types/host.d.ts +15 -3
  45. package/dist/types/index.d.ts +2 -4
  46. package/dist/types/mismatch.d.ts +2 -2
  47. package/dist/types/patches.d.ts +0 -10
  48. package/dist/types/readonly.d.ts +6 -4
  49. package/dist/types/services.d.ts +13 -13
  50. package/dist/types/serving.d.ts +37 -0
  51. package/dist/types/settings.d.ts +0 -1
  52. package/dist/types/spectrum/agent-port.d.ts +2 -0
  53. package/dist/types/spectrum/index.d.ts +1 -1
  54. package/dist/types/spectrum/notices-port.d.ts +3 -1
  55. package/dist/types/wire.d.ts +10 -8
  56. package/dist/wire.js +70 -77
  57. package/dist/wire.js.map +2 -2
  58. package/package.json +11 -12
  59. package/dist/listener.js +0 -73
  60. package/dist/listener.js.map +0 -7
  61. package/dist/types/listener.d.ts +0 -11
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/scoops/extension.ts"],
4
- "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n AssistantEntry,\n type ConversationId,\n defineExtension,\n defineTask,\n type Extension,\n type PromptInput,\n type Submission,\n section,\n type ToolRegistration,\n type Tx,\n wrapTool,\n} from '@earendil-works/pi-durable';\nimport { answerText } from '../agent.ts';\nimport { AgentsDoc, live, type ScoopRecord } from '../agents.ts';\nimport { HOME } from '../kernel/env.ts';\nimport { normalize, resolve } from '../kernel/paths.ts';\nimport { type Layout, LEGACY } from '../layout.ts';\nimport type { LickEvent } from '../licks/licks.ts';\nimport { FROM_KIND } from './from.ts';\nimport { type Feed, type ScoopsHost, ScoopWorkDoc } from './service.ts';\n\nexport const PREVIEW = 1000;\n\ntype Lookup = () => Promise<ScoopsHost>;\n\nexport function scoopFacts(\n record: Pick<ScoopRecord, 'name' | 'folder' | 'role'> & { cone?: string },\n layout: Layout = LEGACY\n): string {\n const place = layout.workspace(record.folder);\n return [\n `You are the scoop \"${record.name}\"${record.role ? ` (role: ${record.role})` : ''}, a helper agent working for a cone. Messages come from the cone or from the user at a terminal; your final answer goes back to whoever asked, so end every task with a complete answer.`,\n layout.users\n ? `You run as the user ${layout.scoopRoot(record.folder).slice('/home/'.length)}, and your home is ${place}: work there. Your cone can read your home; you can't read your cone's home or another scoop's. /tmp is shared scratch.`\n : `Work in ${place}. Your read, write and edit tools only reach ${layout.scoopRoot(record.folder)} and /tmp, and read ${HOME}.`,\n layout.users\n ? 'Your bash runs as your user too, so the kernel decides what it may read and change. Stay in your home, and change nothing outside it unless the request says so.'\n : 'Your bash is not confined: it sees the whole file system and every process. Stay in your folder anyway, and change nothing outside it unless the request says so.',\n 'You may run `agent` synchronously for a bounded side task (calls nest at most three deep), but you cannot start asynchronous scoops (`agent --async`, `subagent spawn`).',\n ].join('\\n');\n}\n\nfunction lines(text: string): number {\n return text ? text.split('\\n').length : 0;\n}\n\nexport type ReportResult = { text: string; failed: string | null; path: string | null };\n\nexport function reportLick(\n record: ScoopRecord,\n id: string,\n report: ReportResult,\n route: { target?: string | null; channel?: 'scoop-notify' | 'bash' } = {}\n): LickEvent {\n const status = report.failed ? (report.failed === 'aborted' ? 'stopped' : 'failed') : 'completed';\n const label = `${record.name} (${record.role ?? 'scoop'})`;\n const preview = report.text.length > PREVIEW ? `${report.text.slice(0, PREVIEW)}\u2026` : report.text;\n const where = report.path ? `Output: ${report.path} (${lines(report.text)} lines)` : null;\n const reason = report.failed && report.failed !== 'aborted' ? `Reason: ${report.failed}` : null;\n return {\n channel: route.channel ?? 'scoop-notify',\n source: id,\n title: label,\n text: `[scoop ${record.name} (${record.role ?? 'scoop'}) ${status}]`,\n body: [where, reason, preview].filter(Boolean).join('\\n\\n'),\n target: (route.target ?? `cone:${record.cone}`) as LickEvent['target'],\n ...(report.failed && report.failed !== 'aborted' ? { severity: 'warn' as const } : {}),\n };\n}\n\ntype Noting = {\n commit: (\n write: (\n tx: Tx\n ) => Promise<{ status: 'running'; checkpoint: { phase: 'deliver'; noted: true } }>,\n context: Context\n ) => Promise<unknown>;\n};\n\nexport async function noteWhenPlaced(\n runtime: Noting,\n submission: Pick<Submission, 'status'>,\n conversation: ConversationId,\n note: { from: string | undefined; agent?: boolean; noted: boolean | undefined },\n context: Context\n): Promise<void> {\n const { from } = note;\n while (from && !note.noted && !context.abortSignal?.aborted) {\n const record = await submission.status(context);\n if (record.status !== 'queued') {\n if (!('entry' in record) || record.entry === undefined) return;\n const entry = record.entry;\n await runtime\n .commit(async (tx) => {\n await tx.appendEntry(conversation, {\n kind: FROM_KIND,\n data: { from, entry, ...(note.agent ? { agent: true } : {}) },\n });\n return { status: 'running', checkpoint: { phase: 'deliver', noted: true } };\n }, context)\n .catch(() => undefined);\n return;\n }\n await new Promise((done) => setTimeout(done, NOTE_POLL_MS));\n }\n}\n\nexport const NOTE_POLL_MS = 100;\n\nexport function waitTask(lookup: Lookup) {\n type WaitInput = { scoops: string[]; cone: string; deadline: number };\n\n return defineTask<WaitInput, { phase: 'start' } | { phase: 'watch' }, null>({\n name: 'slicc.scoop-wait',\n version: 1,\n initial: () => ({ phase: 'start' }),\n phases: {\n async start(task, runtime, context) {\n await runtime.commit(async (tx) => {\n (await tx.doc(ScoopWorkDoc)).waits[String(task.id)] = {\n scoops: task.input.scoops,\n done: {},\n cone: task.input.cone,\n };\n return { status: 'running', checkpoint: { phase: 'watch' } };\n }, context);\n },\n async watch(task, runtime, context) {\n const { agents, licks } = await lookup();\n const key = String(task.id);\n for (;;) {\n const work = await runtime.snapshot(ScoopWorkDoc, context);\n const state = work?.waits[key];\n if (!state) break;\n const feeding = new Set(Object.values(work?.feeds ?? {}).map((feed) => feed.scoop));\n const open = state.scoops.filter((id) => !(id in state.done) && feeding.has(id));\n const expired = runtime.now() >= task.input.deadline;\n if (open.length && !expired) {\n await runtime.sleep(Math.min(runtime.now() + 300, task.input.deadline), context);\n continue;\n }\n const names = (id: string) => agents.state().scoops[id]?.name ?? id;\n const body = state.scoops.map((id) =>\n open.includes(id)\n ? `## ${names(id)}\\n(still working when the wait timed out)`\n : `## ${names(id)}\\n${state.done[id] ?? '(nothing was running)'}`\n );\n await licks.deliver(\n {\n channel: 'scoop-wait',\n source: key,\n title: `Waited for ${state.scoops.map(names).join(', ')}`,\n text: open.length\n ? `${state.scoops.length - open.length} of ${state.scoops.length} done; timed out waiting for ${open.map(names).join(', ')}`\n : `All ${state.scoops.length} done`,\n body: body.join('\\n\\n'),\n target: `cone:${state.cone}`,\n coalesce: false,\n eventId: `wait:${key}`,\n },\n context\n );\n break;\n }\n await runtime.commit(async (tx) => {\n delete (await tx.doc(ScoopWorkDoc)).waits[key];\n return { status: 'terminal', outcome: { status: 'completed', result: null } };\n }, context);\n },\n },\n abort: (task, runtime, context) =>\n runtime.commit(async (tx) => {\n delete (await tx.doc(ScoopWorkDoc)).waits[String(task.id)];\n return { status: 'terminal', outcome: { status: 'aborted' } };\n }, context),\n });\n}\n\nexport function scoopTasks(lookup: Lookup) {\n const anchor = defineTask<null, { phase: 'done' }, null>({\n name: 'slicc.scoop-anchor',\n version: 1,\n initial: () => ({ phase: 'done' }),\n phases: {\n done: (_task, runtime, context) =>\n runtime.commit(\n () => ({ status: 'terminal', outcome: { status: 'completed', result: null } }),\n context\n ),\n },\n abort: (_task, runtime, context) =>\n runtime.commit(() => ({ status: 'terminal', outcome: { status: 'aborted' } }), context),\n });\n\n type ReporterInput = {\n scoop: string;\n prompt: string;\n followUp?: boolean;\n request?: string;\n };\n type ReporterState =\n | { phase: 'deliver'; noted?: boolean }\n | { phase: 'report'; text: string; failed: string | null };\n\n const reporter = defineTask<ReporterInput, ReporterState, ReportResult>({\n name: 'slicc.scoop-reporter',\n version: 1,\n initial: () => ({ phase: 'deliver' }),\n phases: {\n async deliver(task, runtime, context) {\n const { agents } = await lookup();\n const record = agents.state().scoops[task.input.scoop];\n const handle =\n record && live(record)\n ? await runtime.conversation(record.conversation as ConversationId, context)\n : undefined;\n if (!handle) {\n await runtime.commit(async (tx) => {\n delete (await tx.doc(ScoopWorkDoc)).feeds[String(task.id)];\n return {\n status: 'terminal',\n outcome: {\n status: 'completed',\n result: { text: '', failed: 'the scoop is gone', path: null },\n },\n };\n }, context);\n return;\n }\n const requestId = task.input.request ? `subagent:${task.input.request}` : `feed:${task.id}`;\n const feed = (await runtime.snapshot(ScoopWorkDoc, context))?.feeds[String(task.id)];\n const submission = await handle.submit(\n {\n type: 'input',\n content: task.input.prompt,\n whenBusy: task.input.followUp ? 'followUp' : 'steer',\n requestId,\n },\n context\n );\n const noting = noteWhenPlaced(\n runtime,\n submission,\n record?.conversation as ConversationId,\n { from: feed?.from, agent: feed?.agent === true, noted: task.state.checkpoint.noted },\n context\n );\n const settled = await submission.wait(context);\n await noting;\n await runtime.commit(async (tx) => {\n if (settled.status !== 'done' || settled.type !== 'input')\n return {\n status: 'running',\n checkpoint: {\n phase: 'report',\n text: '',\n failed: String(settled.reason),\n },\n };\n const answer = await tx.entry(AssistantEntry, settled.answer);\n return {\n status: 'running',\n checkpoint: {\n phase: 'report',\n text: answerText(answer?.model?.[0]?.content),\n failed: null,\n },\n };\n }, context);\n },\n async report(task, runtime, context) {\n const { agents, licks, layout = LEGACY } = await lookup();\n const id = task.input.scoop;\n const record = agents.state().scoops[id];\n const { text, failed } = task.state.checkpoint;\n const path =\n record && text ? `${layout.reports(record.folder, record.cone)}/${task.id}.md` : null;\n if (path)\n await (await runtime.env(context))?.writeFile(\n path,\n text.endsWith('\\n') ? text : `${text}\\n`,\n context\n );\n const work = await runtime.snapshot(ScoopWorkDoc, context);\n const entry = work?.feeds[String(task.id)] as Feed;\n const waiting = (wait: { scoops: string[]; done: Record<string, string> }) =>\n wait.scoops.includes(id) && !(id in wait.done);\n const folded = Object.values(work?.waits ?? {}).some(waiting);\n const route = { target: entry.target, channel: entry.channel };\n if (!folded && record && entry.report && failed !== 'aborted')\n await licks.deliver(\n {\n ...reportLick(record, id, { text, failed, path }, route),\n eventId: `report:${task.id}`,\n },\n context\n );\n await runtime.commit(async (tx) => {\n const work = await tx.doc(ScoopWorkDoc);\n for (const wait of Object.values(work.waits))\n if (waiting(wait))\n wait.done[id] = failed\n ? `stopped: ${failed}`\n : `${path ? `Output: ${path}\\n` : ''}${text || '(no text)'}`;\n delete work.feeds[String(task.id)];\n return {\n status: 'terminal',\n outcome: { status: 'completed', result: { text, failed, path } },\n };\n }, context);\n },\n },\n abort: (task, runtime, context) =>\n runtime.commit(async (tx) => {\n delete (await tx.doc(ScoopWorkDoc)).feeds[String(task.id)];\n return { status: 'terminal', outcome: { status: 'aborted' } };\n }, context),\n });\n\n return { anchor, reporter, wait: waitTask(lookup) };\n}\n\nasync function whose(input: PromptInput, context: Context) {\n const state = await input.read.snapshot(AgentsDoc, context);\n const scoop = Object.values(state?.scoops ?? {}).find(\n (record) => record.conversation === input.conversationId\n );\n return { scoop };\n}\n\nexport function scoopsExtension(\n tasks: ReturnType<typeof scoopTasks>,\n layout: () => Layout = () => LEGACY\n): Extension {\n return defineExtension({\n name: 'slicc-scoops',\n tasks: [tasks.anchor, tasks.reporter, tasks.wait],\n sections: [\n section('scoop', async (input, context) => {\n const { scoop } = await whose(input, context);\n return scoop ? scoopFacts(scoop, layout()) : undefined;\n }),\n ],\n });\n}\n\nconst WRITES = new Set(['write', 'edit']);\n\nexport function allowed(\n record: Pick<ScoopRecord, 'folder'> & { roots?: ScoopRecord['roots'] },\n coneCwd: string,\n path: string,\n writing: boolean,\n reads: readonly string[] = [],\n own = LEGACY.scoopRoot(record.folder)\n): boolean {\n const write = [own, '/tmp', ...(record.roots?.write ?? [])];\n const roots = writing ? write : [...write, coneCwd, ...(record.roots?.read ?? []), ...reads];\n return roots.some((root) => path === root || path.startsWith(`${root === '/' ? '' : root}/`));\n}\n\nfunction refusal(value: string) {\n return { content: [{ type: 'text' as const, text: value }], isError: true };\n}\n\nexport function guardExtension(lookup: Lookup, tools: readonly ToolRegistration[]): Extension {\n const wraps = tools\n .filter((tool) => tool.name === 'read' || WRITES.has(tool.name))\n .map((tool) =>\n wrapTool(tool, (inner) => ({\n ...inner,\n async execute(args, api, context) {\n const { agents, reads, layout = LEGACY, enforced } = await lookup();\n const record = Object.values(agents.state().scoops).find(\n (scoop) => scoop.conversation === api.conversationId\n );\n if (!record || enforced) return inner.execute(args, api, context);\n const own = layout.scoopRoot(record.folder);\n const cone = layout.users ? own : HOME;\n const path = String((args as { path: unknown }).path);\n const asked = normalize(resolve(layout.workspace(record.folder), path));\n const canonical = await api.env?.canonicalPath(asked, context);\n const target = canonical?.ok ? canonical.value : asked;\n const writing = WRITES.has(inner.name);\n const skills = reads?.() ?? [];\n if (\n allowed(record, cone, target, writing, skills, own) ||\n allowed(record, cone, asked, writing, skills, own)\n )\n return inner.execute(args, api, context);\n const reach = layout.users || WRITES.has(inner.name) ? '' : `, and reads ${HOME}`;\n return refusal(\n `${target} is outside this scoop's folders. ${inner.name} reaches ${own} and /tmp${reach}.`\n );\n },\n }))\n );\n return defineExtension({ name: 'slicc-scoop-guard', wraps });\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n AssistantEntry,\n type ConversationId,\n defineExtension,\n defineTask,\n type Extension,\n type PromptInput,\n type Submission,\n section,\n type ToolRegistration,\n type Tx,\n wrapTool,\n} from '@earendil-works/pi-durable';\nimport { answerText } from '../agent.ts';\nimport { AgentsDoc, live, type ScoopRecord } from '../agents.ts';\nimport { HOME } from '../kernel/env.ts';\nimport { normalize, resolve } from '../kernel/paths.ts';\nimport { type Layout, LEGACY } from '../layout.ts';\nimport type { LickEvent } from '../licks/licks.ts';\nimport { FROM_KIND } from './from.ts';\nimport { type Feed, type ScoopsHost, ScoopWorkDoc } from './service.ts';\n\nexport const PREVIEW = 1000;\n\ntype Lookup = () => Promise<ScoopsHost>;\n\nexport function scoopFacts(\n record: Pick<ScoopRecord, 'name' | 'folder' | 'role'> & { cone?: string },\n layout: Layout = LEGACY\n): string {\n const place = layout.workspace(record.folder);\n return [\n `You are the scoop \"${record.name}\"${record.role ? ` (role: ${record.role})` : ''}, a helper agent working for a cone. Messages come from the cone or from the user at a terminal; your final answer goes back to whoever asked, so end every task with a complete answer.`,\n layout.users\n ? `You run as the user ${layout.scoopRoot(record.folder).slice('/home/'.length)}, and your home is ${place}: work there. Your cone can read your home; you can't read your cone's home or another scoop's. /tmp is shared scratch.`\n : `Work in ${place}. Your read, write and edit tools only reach ${layout.scoopRoot(record.folder)} and /tmp, and read ${HOME}.`,\n layout.users\n ? 'Your bash runs as your user too, so the kernel decides what it may read and change. Stay in your home, and change nothing outside it unless the request says so.'\n : 'Your bash is not confined: it sees the whole file system and every process. Stay in your folder anyway, and change nothing outside it unless the request says so.',\n 'You may run `agent` synchronously for a bounded side task (calls nest at most three deep), but you cannot start asynchronous scoops (`agent --async`, `subagent spawn`).',\n ].join('\\n');\n}\n\nfunction lines(text: string): number {\n return text ? text.split('\\n').length : 0;\n}\n\nexport type ReportResult = { text: string; failed: string | null; path: string | null };\n\nexport function reportLick(\n record: ScoopRecord,\n id: string,\n report: ReportResult,\n route: { target?: string | null; channel?: 'scoop-notify' | 'bash' } = {}\n): LickEvent {\n const status = report.failed ? (report.failed === 'aborted' ? 'stopped' : 'failed') : 'completed';\n const label = `${record.name} (${record.role ?? 'scoop'})`;\n const preview = report.text.length > PREVIEW ? `${report.text.slice(0, PREVIEW)}\u2026` : report.text;\n const where = report.path ? `Output: ${report.path} (${lines(report.text)} lines)` : null;\n const reason = report.failed && report.failed !== 'aborted' ? `Reason: ${report.failed}` : null;\n return {\n channel: route.channel ?? 'scoop-notify',\n source: id,\n title: label,\n text: `[scoop ${record.name} (${record.role ?? 'scoop'}) ${status}]`,\n body: [where, reason, preview].filter(Boolean).join('\\n\\n'),\n target: (route.target ?? `cone:${record.cone}`) as LickEvent['target'],\n ...(report.failed && report.failed !== 'aborted' ? { severity: 'warn' as const } : {}),\n };\n}\n\ntype Noting = {\n commit: (\n write: (\n tx: Tx\n ) => Promise<{ status: 'running'; checkpoint: { phase: 'deliver'; noted: true } }>,\n context: Context\n ) => Promise<unknown>;\n};\n\nexport async function noteWhenPlaced(\n runtime: Noting,\n submission: Pick<Submission, 'status'>,\n conversation: ConversationId,\n note: { from: string | undefined; agent?: boolean; noted: boolean | undefined },\n context: Context\n): Promise<void> {\n const { from } = note;\n while (from && !note.noted && !context.abortSignal?.aborted) {\n const record = await submission.status(context);\n if (record.status !== 'queued') {\n if (!('entry' in record) || record.entry === undefined) return;\n const entry = record.entry;\n await runtime\n .commit(async (tx) => {\n await tx.appendEntry(conversation, {\n kind: FROM_KIND,\n data: { from, entry, ...(note.agent ? { agent: true } : {}) },\n });\n return { status: 'running', checkpoint: { phase: 'deliver', noted: true } };\n }, context)\n .catch(() => undefined);\n return;\n }\n await new Promise((done) => setTimeout(done, NOTE_POLL_MS));\n }\n}\n\nexport const NOTE_POLL_MS = 100;\n\nexport function waitTask(lookup: Lookup) {\n type WaitInput = { scoops: string[]; cone: string; deadline: number };\n\n return defineTask<WaitInput, { phase: 'start' } | { phase: 'watch' }, null>({\n name: 'slicc.scoop-wait',\n version: 1,\n initial: () => ({ phase: 'start' }),\n phases: {\n async start(task, runtime, context) {\n await runtime.commit(async (tx) => {\n (await tx.doc(ScoopWorkDoc)).waits[String(task.id)] = {\n scoops: task.input.scoops,\n done: {},\n cone: task.input.cone,\n };\n return { status: 'running', checkpoint: { phase: 'watch' } };\n }, context);\n },\n async watch(task, runtime, context) {\n const { agents, licks } = await lookup();\n const key = String(task.id);\n for (;;) {\n const work = await runtime.snapshot(ScoopWorkDoc, context);\n const state = work?.waits[key];\n if (!state) break;\n const feeding = new Set(Object.values(work?.feeds ?? {}).map((feed) => feed.scoop));\n const open = state.scoops.filter((id) => !(id in state.done) && feeding.has(id));\n const expired = runtime.now() >= task.input.deadline;\n if (open.length && !expired) {\n await runtime.sleep(Math.min(runtime.now() + 300, task.input.deadline), context);\n continue;\n }\n const names = (id: string) => agents.state().scoops[id]?.name ?? id;\n const body = state.scoops.map((id) =>\n open.includes(id)\n ? `## ${names(id)}\\n(still working when the wait timed out)`\n : `## ${names(id)}\\n${state.done[id] ?? '(nothing was running)'}`\n );\n await licks.deliver(\n {\n channel: 'scoop-wait',\n source: key,\n title: `Waited for ${state.scoops.map(names).join(', ')}`,\n text: open.length\n ? `${state.scoops.length - open.length} of ${state.scoops.length} done; timed out waiting for ${open.map(names).join(', ')}`\n : `All ${state.scoops.length} done`,\n body: body.join('\\n\\n'),\n target: `cone:${state.cone}`,\n coalesce: false,\n eventId: `wait:${key}`,\n },\n context\n );\n break;\n }\n await runtime.commit(async (tx) => {\n delete (await tx.doc(ScoopWorkDoc)).waits[key];\n return { status: 'terminal', outcome: { status: 'completed', result: null } };\n }, context);\n },\n },\n abort: (task, runtime, context) =>\n runtime.commit(async (tx) => {\n delete (await tx.doc(ScoopWorkDoc)).waits[String(task.id)];\n return { status: 'terminal', outcome: { status: 'aborted' } };\n }, context),\n });\n}\n\nexport function scoopTasks(lookup: Lookup) {\n const anchor = defineTask<null, { phase: 'done' }, null>({\n name: 'slicc.scoop-anchor',\n version: 1,\n initial: () => ({ phase: 'done' }),\n phases: {\n done: (_task, runtime, context) =>\n runtime.commit(\n () => ({ status: 'terminal', outcome: { status: 'completed', result: null } }),\n context\n ),\n },\n abort: (_task, runtime, context) =>\n runtime.commit(() => ({ status: 'terminal', outcome: { status: 'aborted' } }), context),\n });\n\n type ReporterInput = {\n scoop: string;\n prompt: string;\n followUp?: boolean;\n request?: string;\n };\n type ReporterState =\n | { phase: 'deliver'; noted?: boolean }\n | { phase: 'report'; text: string; failed: string | null };\n\n const reporter = defineTask<ReporterInput, ReporterState, ReportResult>({\n name: 'slicc.scoop-reporter',\n version: 1,\n initial: () => ({ phase: 'deliver' }),\n phases: {\n async deliver(task, runtime, context) {\n const { agents } = await lookup();\n const record = agents.state().scoops[task.input.scoop];\n const handle =\n record && live(record)\n ? await runtime.conversation(record.conversation as ConversationId, context)\n : undefined;\n if (!handle) {\n await runtime.commit(async (tx) => {\n delete (await tx.doc(ScoopWorkDoc)).feeds[String(task.id)];\n return {\n status: 'terminal',\n outcome: {\n status: 'completed',\n result: { text: '', failed: 'the scoop is gone', path: null },\n },\n };\n }, context);\n return;\n }\n const requestId = task.input.request ? `subagent:${task.input.request}` : `feed:${task.id}`;\n const feed = (await runtime.snapshot(ScoopWorkDoc, context))?.feeds[String(task.id)];\n const submission = await handle.submit(\n {\n type: 'input',\n content: task.input.prompt,\n whenBusy: task.input.followUp ? 'followUp' : 'steer',\n requestId,\n },\n context\n );\n const noting = noteWhenPlaced(\n runtime,\n submission,\n record?.conversation as ConversationId,\n { from: feed?.from, agent: feed?.agent === true, noted: task.state.checkpoint.noted },\n context\n );\n const settled = await submission.wait(context);\n await noting;\n await runtime.commit(async (tx) => {\n if (settled.status !== 'done' || settled.type !== 'input')\n return {\n status: 'running',\n checkpoint: {\n phase: 'report',\n text: '',\n failed: String(settled.reason),\n },\n };\n const answer = await tx.entry(AssistantEntry, settled.answer);\n return {\n status: 'running',\n checkpoint: {\n phase: 'report',\n text: answerText(answer?.model?.[0]?.content),\n failed: null,\n },\n };\n }, context);\n },\n async report(task, runtime, context) {\n const { agents, licks, layout = LEGACY } = await lookup();\n const id = task.input.scoop;\n const record = agents.state().scoops[id];\n const { text, failed } = task.state.checkpoint;\n const path =\n record && text ? `${layout.reports(record.folder, record.cone)}/${task.id}.md` : null;\n if (path)\n await (await runtime.env(context))?.writeFile(\n path,\n text.endsWith('\\n') ? text : `${text}\\n`,\n context\n );\n const work = await runtime.snapshot(ScoopWorkDoc, context);\n const entry = work?.feeds[String(task.id)] as Feed;\n const waiting = (wait: { scoops: string[]; done: Record<string, string> }) =>\n wait.scoops.includes(id) && !(id in wait.done);\n const folded = Object.values(work?.waits ?? {}).some(waiting);\n const route = { target: entry.target, channel: entry.channel };\n if (!folded && record && entry.report && failed !== 'aborted')\n await licks.deliver(\n {\n ...reportLick(record, id, { text, failed, path }, route),\n eventId: `report:${task.id}`,\n },\n context\n );\n await runtime.commit(async (tx) => {\n const work = await tx.doc(ScoopWorkDoc);\n for (const wait of Object.values(work.waits))\n if (waiting(wait))\n wait.done[id] = failed\n ? `stopped: ${failed}`\n : `${path ? `Output: ${path}\\n` : ''}${text || '(no text)'}`;\n delete work.feeds[String(task.id)];\n return {\n status: 'terminal',\n outcome: { status: 'completed', result: { text, failed, path } },\n };\n }, context);\n },\n },\n abort: (task, runtime, context) =>\n runtime.commit(async (tx) => {\n delete (await tx.doc(ScoopWorkDoc)).feeds[String(task.id)];\n return { status: 'terminal', outcome: { status: 'aborted' } };\n }, context),\n });\n\n return { anchor, reporter, wait: waitTask(lookup) };\n}\n\nasync function whose(input: PromptInput, context: Context) {\n const state = await input.read.snapshot(AgentsDoc, context);\n const scoop = Object.values(state?.scoops ?? {}).find(\n (record) => record.conversation === input.conversationId\n );\n return { scoop };\n}\n\nexport function scoopsExtension(\n tasks: ReturnType<typeof scoopTasks>,\n layout: () => Layout = () => LEGACY\n): Extension {\n return defineExtension({\n name: 'slicc-scoops',\n tasks: [tasks.anchor, tasks.reporter, tasks.wait],\n sections: [\n section('scoop', async (input, context) => {\n const { scoop } = await whose(input, context);\n return scoop ? scoopFacts(scoop, layout()) : undefined;\n }),\n ],\n });\n}\n\nconst WRITES = new Set(['write', 'edit']);\n\nexport function allowed(\n record: Pick<ScoopRecord, 'folder'> & { roots?: ScoopRecord['roots'] },\n coneCwd: string,\n path: string,\n writing: boolean,\n reads: readonly string[] = [],\n own = LEGACY.scoopRoot(record.folder)\n): boolean {\n const write = [own, '/tmp', ...(record.roots?.write ?? [])];\n const roots = writing ? write : [...write, coneCwd, ...(record.roots?.read ?? []), ...reads];\n return roots.some((root) => path === root || path.startsWith(`${root === '/' ? '' : root}/`));\n}\n\nfunction refusal(value: string) {\n return { output: [{ type: 'text' as const, text: value }], isError: true };\n}\n\nexport function guardExtension(lookup: Lookup, tools: readonly ToolRegistration[]): Extension {\n const wraps = tools\n .filter((tool) => tool.name === 'read' || WRITES.has(tool.name))\n .map((tool) =>\n wrapTool(tool, (inner) => ({\n ...inner,\n async execute(args, api, context) {\n const { agents, reads, layout = LEGACY, enforced } = await lookup();\n const record = Object.values(agents.state().scoops).find(\n (scoop) => scoop.conversation === api.conversationId\n );\n if (!record || enforced) return inner.execute(args, api, context);\n const own = layout.scoopRoot(record.folder);\n const cone = layout.users ? own : HOME;\n const path = String((args as { path: unknown }).path);\n const asked = normalize(resolve(layout.workspace(record.folder), path));\n const canonical = await api.env?.canonicalPath(asked, context);\n const target = canonical?.ok ? canonical.value : asked;\n const writing = WRITES.has(inner.name);\n const skills = reads?.() ?? [];\n if (\n allowed(record, cone, target, writing, skills, own) ||\n allowed(record, cone, asked, writing, skills, own)\n )\n return inner.execute(args, api, context);\n const reach = layout.users || WRITES.has(inner.name) ? '' : `, and reads ${HOME}`;\n return refusal(\n `${target} is outside this scoop's folders. ${inner.name} reaches ${own} and /tmp${reach}.`\n );\n },\n }))\n );\n return defineExtension({ name: 'slicc-scoop-guard', wraps });\n}\n"],
5
+ "mappings": "AACA;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EAIA;AAAA,EAGA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,WAAW,YAA8B;AAClD,SAAS,YAAY;AACrB,SAAS,WAAW,eAAe;AACnC,SAAsB,cAAc;AAEpC,SAAS,iBAAiB;AAC1B,SAAqC,oBAAoB;AAElD,MAAM,UAAU;AAIhB,SAAS,WACd,QACA,SAAiB,QACT;AACR,QAAM,QAAQ,OAAO,UAAU,OAAO,MAAM;AAC5C,SAAO;AAAA,IACL,sBAAsB,OAAO,IAAI,IAAI,OAAO,OAAO,WAAW,OAAO,IAAI,MAAM,EAAE;AAAA,IACjF,OAAO,QACH,uBAAuB,OAAO,UAAU,OAAO,MAAM,EAAE,MAAM,SAAS,MAAM,CAAC,sBAAsB,KAAK,4HACxG,WAAW,KAAK,gDAAgD,OAAO,UAAU,OAAO,MAAM,CAAC,uBAAuB,IAAI;AAAA,IAC9H,OAAO,QACH,qKACA;AAAA,IACJ;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,MAAM,MAAsB;AACnC,SAAO,OAAO,KAAK,MAAM,IAAI,EAAE,SAAS;AAC1C;AAIO,SAAS,WACd,QACA,IACA,QACA,QAAuE,CAAC,GAC7D;AACX,QAAM,SAAS,OAAO,SAAU,OAAO,WAAW,YAAY,YAAY,WAAY;AACtF,QAAM,QAAQ,GAAG,OAAO,IAAI,KAAK,OAAO,QAAQ,OAAO;AACvD,QAAM,UAAU,OAAO,KAAK,SAAS,UAAU,GAAG,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,WAAM,OAAO;AAC5F,QAAM,QAAQ,OAAO,OAAO,WAAW,OAAO,IAAI,KAAK,MAAM,OAAO,IAAI,CAAC,YAAY;AACrF,QAAM,SAAS,OAAO,UAAU,OAAO,WAAW,YAAY,WAAW,OAAO,MAAM,KAAK;AAC3F,SAAO;AAAA,IACL,SAAS,MAAM,WAAW;AAAA,IAC1B,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM,UAAU,OAAO,IAAI,KAAK,OAAO,QAAQ,OAAO,KAAK,MAAM;AAAA,IACjE,MAAM,CAAC,OAAO,QAAQ,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,MAAM;AAAA,IAC1D,QAAS,MAAM,UAAU,QAAQ,OAAO,IAAI;AAAA,IAC5C,GAAI,OAAO,UAAU,OAAO,WAAW,YAAY,EAAE,UAAU,OAAgB,IAAI,CAAC;AAAA,EACtF;AACF;AAWA,eAAsB,eACpB,SACA,YACA,cACA,MACA,SACe;AACf,QAAM,EAAE,KAAK,IAAI;AACjB,SAAO,QAAQ,CAAC,KAAK,SAAS,CAAC,QAAQ,aAAa,SAAS;AAC3D,UAAM,SAAS,MAAM,WAAW,OAAO,OAAO;AAC9C,QAAI,OAAO,WAAW,UAAU;AAC9B,UAAI,EAAE,WAAW,WAAW,OAAO,UAAU,OAAW;AACxD,YAAM,QAAQ,OAAO;AACrB,YAAM,QACH,OAAO,OAAO,OAAO;AACpB,cAAM,GAAG,YAAY,cAAc;AAAA,UACjC,MAAM;AAAA,UACN,MAAM,EAAE,MAAM,OAAO,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC,EAAG;AAAA,QAC9D,CAAC;AACD,eAAO,EAAE,QAAQ,WAAW,YAAY,EAAE,OAAO,WAAW,OAAO,KAAK,EAAE;AAAA,MAC5E,GAAG,OAAO,EACT,MAAM,MAAM,MAAS;AACxB;AAAA,IACF;AACA,UAAM,IAAI,QAAQ,CAAC,SAAS,WAAW,MAAM,YAAY,CAAC;AAAA,EAC5D;AACF;AAEO,MAAM,eAAe;AAErB,SAAS,SAAS,QAAgB;AAGvC,SAAO,WAAqE;AAAA,IAC1E,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,OAAO,EAAE,OAAO,QAAQ;AAAA,IACjC,QAAQ;AAAA,MACN,MAAM,MAAM,MAAM,SAAS,SAAS;AAClC,cAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,WAAC,MAAM,GAAG,IAAI,YAAY,GAAG,MAAM,OAAO,KAAK,EAAE,CAAC,IAAI;AAAA,YACpD,QAAQ,KAAK,MAAM;AAAA,YACnB,MAAM,CAAC;AAAA,YACP,MAAM,KAAK,MAAM;AAAA,UACnB;AACA,iBAAO,EAAE,QAAQ,WAAW,YAAY,EAAE,OAAO,QAAQ,EAAE;AAAA,QAC7D,GAAG,OAAO;AAAA,MACZ;AAAA,MACA,MAAM,MAAM,MAAM,SAAS,SAAS;AAClC,cAAM,EAAE,QAAQ,MAAM,IAAI,MAAM,OAAO;AACvC,cAAM,MAAM,OAAO,KAAK,EAAE;AAC1B,mBAAS;AACP,gBAAM,OAAO,MAAM,QAAQ,SAAS,cAAc,OAAO;AACzD,gBAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,cAAI,CAAC,MAAO;AACZ,gBAAM,UAAU,IAAI,IAAI,OAAO,OAAO,MAAM,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;AAClF,gBAAM,OAAO,MAAM,OAAO,OAAO,CAAC,OAAO,EAAE,MAAM,MAAM,SAAS,QAAQ,IAAI,EAAE,CAAC;AAC/E,gBAAM,UAAU,QAAQ,IAAI,KAAK,KAAK,MAAM;AAC5C,cAAI,KAAK,UAAU,CAAC,SAAS;AAC3B,kBAAM,QAAQ,MAAM,KAAK,IAAI,QAAQ,IAAI,IAAI,KAAK,KAAK,MAAM,QAAQ,GAAG,OAAO;AAC/E;AAAA,UACF;AACA,gBAAM,QAAQ,CAAC,OAAe,OAAO,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ;AACjE,gBAAM,OAAO,MAAM,OAAO;AAAA,YAAI,CAAC,OAC7B,KAAK,SAAS,EAAE,IACZ,MAAM,MAAM,EAAE,CAAC;AAAA,2CACf,MAAM,MAAM,EAAE,CAAC;AAAA,EAAK,MAAM,KAAK,EAAE,KAAK,uBAAuB;AAAA,UACnE;AACA,gBAAM,MAAM;AAAA,YACV;AAAA,cACE,SAAS;AAAA,cACT,QAAQ;AAAA,cACR,OAAO,cAAc,MAAM,OAAO,IAAI,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA,cACvD,MAAM,KAAK,SACP,GAAG,MAAM,OAAO,SAAS,KAAK,MAAM,OAAO,MAAM,OAAO,MAAM,gCAAgC,KAAK,IAAI,KAAK,EAAE,KAAK,IAAI,CAAC,KACxH,OAAO,MAAM,OAAO,MAAM;AAAA,cAC9B,MAAM,KAAK,KAAK,MAAM;AAAA,cACtB,QAAQ,QAAQ,MAAM,IAAI;AAAA,cAC1B,UAAU;AAAA,cACV,SAAS,QAAQ,GAAG;AAAA,YACtB;AAAA,YACA;AAAA,UACF;AACA;AAAA,QACF;AACA,cAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,kBAAQ,MAAM,GAAG,IAAI,YAAY,GAAG,MAAM,GAAG;AAC7C,iBAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,aAAa,QAAQ,KAAK,EAAE;AAAA,QAC9E,GAAG,OAAO;AAAA,MACZ;AAAA,IACF;AAAA,IACA,OAAO,CAAC,MAAM,SAAS,YACrB,QAAQ,OAAO,OAAO,OAAO;AAC3B,cAAQ,MAAM,GAAG,IAAI,YAAY,GAAG,MAAM,OAAO,KAAK,EAAE,CAAC;AACzD,aAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,UAAU,EAAE;AAAA,IAC9D,GAAG,OAAO;AAAA,EACd,CAAC;AACH;AAEO,SAAS,WAAW,QAAgB;AACzC,QAAM,SAAS,WAA0C;AAAA,IACvD,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,OAAO,EAAE,OAAO,OAAO;AAAA,IAChC,QAAQ;AAAA,MACN,MAAM,CAAC,OAAO,SAAS,YACrB,QAAQ;AAAA,QACN,OAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,aAAa,QAAQ,KAAK,EAAE;AAAA,QAC5E;AAAA,MACF;AAAA,IACJ;AAAA,IACA,OAAO,CAAC,OAAO,SAAS,YACtB,QAAQ,OAAO,OAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,UAAU,EAAE,IAAI,OAAO;AAAA,EAC1F,CAAC;AAYD,QAAM,WAAW,WAAuD;AAAA,IACtE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,OAAO,EAAE,OAAO,UAAU;AAAA,IACnC,QAAQ;AAAA,MACN,MAAM,QAAQ,MAAM,SAAS,SAAS;AACpC,cAAM,EAAE,OAAO,IAAI,MAAM,OAAO;AAChC,cAAM,SAAS,OAAO,MAAM,EAAE,OAAO,KAAK,MAAM,KAAK;AACrD,cAAM,SACJ,UAAU,KAAK,MAAM,IACjB,MAAM,QAAQ,aAAa,OAAO,cAAgC,OAAO,IACzE;AACN,YAAI,CAAC,QAAQ;AACX,gBAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,oBAAQ,MAAM,GAAG,IAAI,YAAY,GAAG,MAAM,OAAO,KAAK,EAAE,CAAC;AACzD,mBAAO;AAAA,cACL,QAAQ;AAAA,cACR,SAAS;AAAA,gBACP,QAAQ;AAAA,gBACR,QAAQ,EAAE,MAAM,IAAI,QAAQ,qBAAqB,MAAM,KAAK;AAAA,cAC9D;AAAA,YACF;AAAA,UACF,GAAG,OAAO;AACV;AAAA,QACF;AACA,cAAM,YAAY,KAAK,MAAM,UAAU,YAAY,KAAK,MAAM,OAAO,KAAK,QAAQ,KAAK,EAAE;AACzF,cAAM,QAAQ,MAAM,QAAQ,SAAS,cAAc,OAAO,IAAI,MAAM,OAAO,KAAK,EAAE,CAAC;AACnF,cAAM,aAAa,MAAM,OAAO;AAAA,UAC9B;AAAA,YACE,MAAM;AAAA,YACN,SAAS,KAAK,MAAM;AAAA,YACpB,UAAU,KAAK,MAAM,WAAW,aAAa;AAAA,YAC7C;AAAA,UACF;AAAA,UACA;AAAA,QACF;AACA,cAAM,SAAS;AAAA,UACb;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,EAAE,MAAM,MAAM,MAAM,OAAO,MAAM,UAAU,MAAM,OAAO,KAAK,MAAM,WAAW,MAAM;AAAA,UACpF;AAAA,QACF;AACA,cAAM,UAAU,MAAM,WAAW,KAAK,OAAO;AAC7C,cAAM;AACN,cAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,cAAI,QAAQ,WAAW,UAAU,QAAQ,SAAS;AAChD,mBAAO;AAAA,cACL,QAAQ;AAAA,cACR,YAAY;AAAA,gBACV,OAAO;AAAA,gBACP,MAAM;AAAA,gBACN,QAAQ,OAAO,QAAQ,MAAM;AAAA,cAC/B;AAAA,YACF;AACF,gBAAM,SAAS,MAAM,GAAG,MAAM,gBAAgB,QAAQ,MAAM;AAC5D,iBAAO;AAAA,YACL,QAAQ;AAAA,YACR,YAAY;AAAA,cACV,OAAO;AAAA,cACP,MAAM,WAAW,QAAQ,QAAQ,CAAC,GAAG,OAAO;AAAA,cAC5C,QAAQ;AAAA,YACV;AAAA,UACF;AAAA,QACF,GAAG,OAAO;AAAA,MACZ;AAAA,MACA,MAAM,OAAO,MAAM,SAAS,SAAS;AACnC,cAAM,EAAE,QAAQ,OAAO,SAAS,OAAO,IAAI,MAAM,OAAO;AACxD,cAAM,KAAK,KAAK,MAAM;AACtB,cAAM,SAAS,OAAO,MAAM,EAAE,OAAO,EAAE;AACvC,cAAM,EAAE,MAAM,OAAO,IAAI,KAAK,MAAM;AACpC,cAAM,OACJ,UAAU,OAAO,GAAG,OAAO,QAAQ,OAAO,QAAQ,OAAO,IAAI,CAAC,IAAI,KAAK,EAAE,QAAQ;AACnF,YAAI;AACF,iBAAO,MAAM,QAAQ,IAAI,OAAO,IAAI;AAAA,YAClC;AAAA,YACA,KAAK,SAAS,IAAI,IAAI,OAAO,GAAG,IAAI;AAAA;AAAA,YACpC;AAAA,UACF;AACF,cAAM,OAAO,MAAM,QAAQ,SAAS,cAAc,OAAO;AACzD,cAAM,QAAQ,MAAM,MAAM,OAAO,KAAK,EAAE,CAAC;AACzC,cAAM,UAAU,CAAC,SACf,KAAK,OAAO,SAAS,EAAE,KAAK,EAAE,MAAM,KAAK;AAC3C,cAAM,SAAS,OAAO,OAAO,MAAM,SAAS,CAAC,CAAC,EAAE,KAAK,OAAO;AAC5D,cAAM,QAAQ,EAAE,QAAQ,MAAM,QAAQ,SAAS,MAAM,QAAQ;AAC7D,YAAI,CAAC,UAAU,UAAU,MAAM,UAAU,WAAW;AAClD,gBAAM,MAAM;AAAA,YACV;AAAA,cACE,GAAG,WAAW,QAAQ,IAAI,EAAE,MAAM,QAAQ,KAAK,GAAG,KAAK;AAAA,cACvD,SAAS,UAAU,KAAK,EAAE;AAAA,YAC5B;AAAA,YACA;AAAA,UACF;AACF,cAAM,QAAQ,OAAO,OAAO,OAAO;AACjC,gBAAMA,QAAO,MAAM,GAAG,IAAI,YAAY;AACtC,qBAAW,QAAQ,OAAO,OAAOA,MAAK,KAAK;AACzC,gBAAI,QAAQ,IAAI;AACd,mBAAK,KAAK,EAAE,IAAI,SACZ,YAAY,MAAM,KAClB,GAAG,OAAO,WAAW,IAAI;AAAA,IAAO,EAAE,GAAG,QAAQ,WAAW;AAChE,iBAAOA,MAAK,MAAM,OAAO,KAAK,EAAE,CAAC;AACjC,iBAAO;AAAA,YACL,QAAQ;AAAA,YACR,SAAS,EAAE,QAAQ,aAAa,QAAQ,EAAE,MAAM,QAAQ,KAAK,EAAE;AAAA,UACjE;AAAA,QACF,GAAG,OAAO;AAAA,MACZ;AAAA,IACF;AAAA,IACA,OAAO,CAAC,MAAM,SAAS,YACrB,QAAQ,OAAO,OAAO,OAAO;AAC3B,cAAQ,MAAM,GAAG,IAAI,YAAY,GAAG,MAAM,OAAO,KAAK,EAAE,CAAC;AACzD,aAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,QAAQ,UAAU,EAAE;AAAA,IAC9D,GAAG,OAAO;AAAA,EACd,CAAC;AAED,SAAO,EAAE,QAAQ,UAAU,MAAM,SAAS,MAAM,EAAE;AACpD;AAEA,eAAe,MAAM,OAAoB,SAAkB;AACzD,QAAM,QAAQ,MAAM,MAAM,KAAK,SAAS,WAAW,OAAO;AAC1D,QAAM,QAAQ,OAAO,OAAO,OAAO,UAAU,CAAC,CAAC,EAAE;AAAA,IAC/C,CAAC,WAAW,OAAO,iBAAiB,MAAM;AAAA,EAC5C;AACA,SAAO,EAAE,MAAM;AACjB;AAEO,SAAS,gBACd,OACA,SAAuB,MAAM,QAClB;AACX,SAAO,gBAAgB;AAAA,IACrB,MAAM;AAAA,IACN,OAAO,CAAC,MAAM,QAAQ,MAAM,UAAU,MAAM,IAAI;AAAA,IAChD,UAAU;AAAA,MACR,QAAQ,SAAS,OAAO,OAAO,YAAY;AACzC,cAAM,EAAE,MAAM,IAAI,MAAM,MAAM,OAAO,OAAO;AAC5C,eAAO,QAAQ,WAAW,OAAO,OAAO,CAAC,IAAI;AAAA,MAC/C,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAEA,MAAM,SAAS,oBAAI,IAAI,CAAC,SAAS,MAAM,CAAC;AAEjC,SAAS,QACd,QACA,SACA,MACA,SACA,QAA2B,CAAC,GAC5B,MAAM,OAAO,UAAU,OAAO,MAAM,GAC3B;AACT,QAAM,QAAQ,CAAC,KAAK,QAAQ,GAAI,OAAO,OAAO,SAAS,CAAC,CAAE;AAC1D,QAAM,QAAQ,UAAU,QAAQ,CAAC,GAAG,OAAO,SAAS,GAAI,OAAO,OAAO,QAAQ,CAAC,GAAI,GAAG,KAAK;AAC3F,SAAO,MAAM,KAAK,CAAC,SAAS,SAAS,QAAQ,KAAK,WAAW,GAAG,SAAS,MAAM,KAAK,IAAI,GAAG,CAAC;AAC9F;AAEA,SAAS,QAAQ,OAAe;AAC9B,SAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,QAAiB,MAAM,MAAM,CAAC,GAAG,SAAS,KAAK;AAC3E;AAEO,SAAS,eAAe,QAAgB,OAA+C;AAC5F,QAAM,QAAQ,MACX,OAAO,CAAC,SAAS,KAAK,SAAS,UAAU,OAAO,IAAI,KAAK,IAAI,CAAC,EAC9D;AAAA,IAAI,CAAC,SACJ,SAAS,MAAM,CAAC,WAAW;AAAA,MACzB,GAAG;AAAA,MACH,MAAM,QAAQ,MAAM,KAAK,SAAS;AAChC,cAAM,EAAE,QAAQ,OAAO,SAAS,QAAQ,SAAS,IAAI,MAAM,OAAO;AAClE,cAAM,SAAS,OAAO,OAAO,OAAO,MAAM,EAAE,MAAM,EAAE;AAAA,UAClD,CAAC,UAAU,MAAM,iBAAiB,IAAI;AAAA,QACxC;AACA,YAAI,CAAC,UAAU,SAAU,QAAO,MAAM,QAAQ,MAAM,KAAK,OAAO;AAChE,cAAM,MAAM,OAAO,UAAU,OAAO,MAAM;AAC1C,cAAM,OAAO,OAAO,QAAQ,MAAM;AAClC,cAAM,OAAO,OAAQ,KAA2B,IAAI;AACpD,cAAM,QAAQ,UAAU,QAAQ,OAAO,UAAU,OAAO,MAAM,GAAG,IAAI,CAAC;AACtE,cAAM,YAAY,MAAM,IAAI,KAAK,cAAc,OAAO,OAAO;AAC7D,cAAM,SAAS,WAAW,KAAK,UAAU,QAAQ;AACjD,cAAM,UAAU,OAAO,IAAI,MAAM,IAAI;AACrC,cAAM,SAAS,QAAQ,KAAK,CAAC;AAC7B,YACE,QAAQ,QAAQ,MAAM,QAAQ,SAAS,QAAQ,GAAG,KAClD,QAAQ,QAAQ,MAAM,OAAO,SAAS,QAAQ,GAAG;AAEjD,iBAAO,MAAM,QAAQ,MAAM,KAAK,OAAO;AACzC,cAAM,QAAQ,OAAO,SAAS,OAAO,IAAI,MAAM,IAAI,IAAI,KAAK,eAAe,IAAI;AAC/E,eAAO;AAAA,UACL,GAAG,MAAM,qCAAqC,MAAM,IAAI,YAAY,GAAG,YAAY,KAAK;AAAA,QAC1F;AAAA,MACF;AAAA,IACF,EAAE;AAAA,EACJ;AACF,SAAO,gBAAgB,EAAE,MAAM,qBAAqB,MAAM,CAAC;AAC7D;",
6
6
  "names": ["work"]
7
7
  }
@@ -31,9 +31,11 @@ function leaves(value, out, set) {
31
31
  });
32
32
  }
33
33
  async function scrubResult(result, scrub) {
34
- const content = [...result.content ?? []];
34
+ const content = [...result.output ?? []];
35
35
  const diagnostics = [...result.diagnostics ?? []];
36
- const details = result.details === void 0 ? void 0 : structuredClone(result.details);
36
+ const copy = (value) => value === void 0 ? void 0 : structuredClone(value);
37
+ const details = copy(result.details);
38
+ const structured = copy(result.structuredOutput);
37
39
  const found = [];
38
40
  content.forEach((item, at) => {
39
41
  if (item.type === "text")
@@ -50,19 +52,25 @@ async function scrubResult(result, scrub) {
50
52
  leaves(details, found, (next) => {
51
53
  replaced = next;
52
54
  });
55
+ let output = structured;
56
+ if (structured !== void 0)
57
+ leaves(structured, found, (next) => {
58
+ output = next;
59
+ });
53
60
  const before = found.map((leaf) => leaf.get());
54
61
  const after = await scrub.texts(before);
55
62
  if (after.every((text, at) => text === before[at])) return void 0;
56
63
  found.forEach((leaf, at) => {
57
64
  leaf.set(after[at]);
58
65
  });
59
- const kept = result.content;
66
+ const kept = result.output;
60
67
  if (kept) kept.splice(0, kept.length, ...content);
61
68
  return {
62
69
  ...result,
63
- ...kept ? { content: kept } : {},
70
+ ...kept ? { output: kept } : {},
64
71
  ...result.diagnostics ? { diagnostics } : {},
65
- ...replaced === void 0 ? {} : { details: replaced }
72
+ ...replaced === void 0 ? {} : { details: replaced },
73
+ ...output === void 0 ? {} : { structuredOutput: output }
66
74
  };
67
75
  }
68
76
  function where(name) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/secrets/extension.ts"],
4
- "sourcesContent": ["import type { Context, JsonValue } from '@earendil-works/chord';\nimport {\n type AgentChange,\n type Cursor,\n defineExtension,\n type Extension,\n GenerationTask,\n type Harness,\n hook,\n type Storage,\n section,\n type ToolExecutionResult,\n ToolTask,\n} from '@earendil-works/pi-durable';\nimport { TITLES } from '../titles/extension.ts';\nimport type { Names, SecretName } from './names.ts';\nimport type { Scrub } from './scrub.ts';\nimport type { Vault } from './vault.ts';\nimport { listedLine, SECRETS_KIND } from './view.ts';\n\nexport { listedLine, SECRETS_KIND };\n\nexport const SECRETS = 'slicc-secrets';\nexport const SECRETS_COMMAND = '/secrets';\nexport const SHELL_GRANTS = ['fnox', 'exec', '--shell-grants', '--'] as const;\n\nexport const MODEL_LINE =\n \"Credentials are env vars (`$NAME`) holding placeholders; `secret list` shows them and where each works. The vault fills in the real value only in the listed header, for the listed domain, method and path, over HTTPS (curl, git, fetch). Adding a rule means editing `/etc/fnox/config.toml` with sudo, which the user approves. `fnox exec` gives real values only for secrets the user enabled for exec, and the secrets listed as 'in every bash' already hold real values in your commands. Everything is logged, and stored values are replaced by their placeholder in everything you see; edit around placeholders, since files show them too. Never ask the user to paste a credential into the chat.\";\n\ntype Leaf = { get(): string; set(text: string): void };\n\nfunction leaves(value: JsonValue, out: Leaf[], set: (next: JsonValue) => void): void {\n if (typeof value === 'string') {\n out.push({ get: () => value, set });\n return;\n }\n if (Array.isArray(value)) {\n value.forEach((item, at) => {\n leaves(item, out, (next) => {\n value[at] = next;\n });\n });\n return;\n }\n if (value && typeof value === 'object')\n for (const [key, item] of Object.entries(value))\n leaves(item as JsonValue, out, (next) => {\n (value as Record<string, JsonValue>)[key] = next;\n });\n}\n\nexport async function scrubResult(\n result: ToolExecutionResult,\n scrub: Pick<Scrub, 'texts'>\n): Promise<ToolExecutionResult | undefined> {\n const content = [...(result.content ?? [])];\n const diagnostics = [...(result.diagnostics ?? [])];\n const details =\n result.details === undefined ? undefined : (structuredClone(result.details) as JsonValue);\n const found: Leaf[] = [];\n content.forEach((item, at) => {\n if (item.type === 'text')\n found.push({ get: () => item.text, set: (text) => void (content[at] = { ...item, text }) });\n });\n diagnostics.forEach((item, at) => {\n found.push({\n get: () => item.message,\n set: (message) => void (diagnostics[at] = { ...item, message }),\n });\n });\n let replaced = details;\n if (details !== undefined)\n leaves(details, found, (next) => {\n replaced = next;\n });\n const before = found.map((leaf) => leaf.get());\n const after = await scrub.texts(before);\n if (after.every((text, at) => text === before[at])) return undefined;\n found.forEach((leaf, at) => {\n leaf.set(after[at] as string);\n });\n const kept = result.content as ToolExecutionResult['content'] & unknown[];\n if (kept) kept.splice(0, kept.length, ...content);\n return {\n ...result,\n ...(kept ? { content: kept } : {}),\n ...(result.diagnostics ? { diagnostics } : {}),\n ...(replaced === undefined ? {} : { details: replaced }),\n };\n}\n\nfunction where(name: SecretName): string {\n const rules = name.rules.map((rule) =>\n [\n rule.domain,\n rule.header ? `header ${rule.header}` : '',\n rule.methods?.length ? rule.methods.join(' ') : '',\n rule.paths?.length ? rule.paths.join(' ') : '',\n ]\n .filter(Boolean)\n .join(', ')\n );\n const shell = name.exec === 'shell' ? ['in every bash'] : [];\n const parts = [...rules, ...shell];\n return parts.length ? `: ${parts.join('; ')}` : '';\n}\n\nexport function renderSecrets(names: readonly SecretName[]): string | undefined {\n if (!names.length) return undefined;\n const lines = names.map((name) => `- ${name.name} = ${name.placeholder}${where(name)}`);\n return [MODEL_LINE, '', ...lines].join('\\n');\n}\n\nexport function secretsEntry(names: readonly SecretName[]) {\n return {\n kind: SECRETS_KIND,\n data: { names: structuredClone(names) as unknown as JsonValue },\n model: [{ role: 'user' as const, content: listedLine(names), timestamp: Date.now() }],\n };\n}\n\nexport function shellGrants(names: Pick<Names, 'state'>) {\n return (argv: readonly string[]): readonly string[] =>\n names.state.value.some((name) => name.exec === 'shell') ? [...SHELL_GRANTS, ...argv] : argv;\n}\n\nconst missing = (list: readonly { name: string }[]) =>\n [SECRETS, TITLES].filter((name) => !list.some((extension) => extension.name === name));\n\nexport function pinned<T extends AgentChange>(change: T): T {\n const list = change.extensions;\n if (!Array.isArray(list)) return change;\n const added = missing(list);\n return added.length\n ? { ...change, extensions: [...list, ...added.map((name) => ({ name }))] }\n : change;\n}\n\nexport async function repin(\n harness: Pick<Harness, 'conversation'>,\n storage: Pick<Storage, 'scanConversations'>,\n context: Context\n): Promise<number> {\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 const conversation = await harness.conversation(record.id, context);\n if (!conversation) continue;\n const agent = await conversation.agent(context);\n if (!missing(agent.extensions).length) continue;\n await conversation.configure(pinned({ extensions: agent.extensions }), context);\n fixed++;\n }\n cursor = page.next;\n } while (cursor);\n return fixed;\n}\n\nexport function secretsExtension(deps: {\n names: Pick<Names, 'state'>;\n vault: Pick<Vault, 'ready' | 'texts'>;\n}): Extension {\n return defineExtension({\n name: SECRETS,\n sections: [section('secrets', () => renderSecrets(deps.names.state.value))],\n hooks: [\n hook(ToolTask, { afterTool: (_call, result) => scrubResult(result, deps.vault) }),\n hook(GenerationTask, {\n beforeRequest: async () => {\n await deps.vault.ready();\n return undefined;\n },\n }),\n ],\n });\n}\n"],
5
- "mappings": "AACA;AAAA,EAGE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,OACK;AACP,SAAS,cAAc;AAIvB,SAAS,YAAY,oBAAoB;AAIlC,MAAM,UAAU;AAChB,MAAM,kBAAkB;AACxB,MAAM,eAAe,CAAC,QAAQ,QAAQ,kBAAkB,IAAI;AAE5D,MAAM,aACX;AAIF,SAAS,OAAO,OAAkB,KAAa,KAAsC;AACnF,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,KAAK,EAAE,KAAK,MAAM,OAAO,IAAI,CAAC;AAClC;AAAA,EACF;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAM,QAAQ,CAAC,MAAM,OAAO;AAC1B,aAAO,MAAM,KAAK,CAAC,SAAS;AAC1B,cAAM,EAAE,IAAI;AAAA,MACd,CAAC;AAAA,IACH,CAAC;AACD;AAAA,EACF;AACA,MAAI,SAAS,OAAO,UAAU;AAC5B,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,KAAK;AAC5C,aAAO,MAAmB,KAAK,CAAC,SAAS;AACvC,QAAC,MAAoC,GAAG,IAAI;AAAA,MAC9C,CAAC;AACP;AAEA,eAAsB,YACpB,QACA,OAC0C;AAC1C,QAAM,UAAU,CAAC,GAAI,OAAO,WAAW,CAAC,CAAE;AAC1C,QAAM,cAAc,CAAC,GAAI,OAAO,eAAe,CAAC,CAAE;AAClD,QAAM,UACJ,OAAO,YAAY,SAAY,SAAa,gBAAgB,OAAO,OAAO;AAC5E,QAAM,QAAgB,CAAC;AACvB,UAAQ,QAAQ,CAAC,MAAM,OAAO;AAC5B,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,EAAE,KAAK,MAAM,KAAK,MAAM,KAAK,CAAC,SAAS,MAAM,QAAQ,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,GAAG,CAAC;AAAA,EAC9F,CAAC;AACD,cAAY,QAAQ,CAAC,MAAM,OAAO;AAChC,UAAM,KAAK;AAAA,MACT,KAAK,MAAM,KAAK;AAAA,MAChB,KAAK,CAAC,YAAY,MAAM,YAAY,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ;AAAA,IAC/D,CAAC;AAAA,EACH,CAAC;AACD,MAAI,WAAW;AACf,MAAI,YAAY;AACd,WAAO,SAAS,OAAO,CAAC,SAAS;AAC/B,iBAAW;AAAA,IACb,CAAC;AACH,QAAM,SAAS,MAAM,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC;AAC7C,QAAM,QAAQ,MAAM,MAAM,MAAM,MAAM;AACtC,MAAI,MAAM,MAAM,CAAC,MAAM,OAAO,SAAS,OAAO,EAAE,CAAC,EAAG,QAAO;AAC3D,QAAM,QAAQ,CAAC,MAAM,OAAO;AAC1B,SAAK,IAAI,MAAM,EAAE,CAAW;AAAA,EAC9B,CAAC;AACD,QAAM,OAAO,OAAO;AACpB,MAAI,KAAM,MAAK,OAAO,GAAG,KAAK,QAAQ,GAAG,OAAO;AAChD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAI,OAAO,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,IAChC,GAAI,OAAO,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,IAC5C,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,SAAS,SAAS;AAAA,EACxD;AACF;AAEA,SAAS,MAAM,MAA0B;AACvC,QAAM,QAAQ,KAAK,MAAM;AAAA,IAAI,CAAC,SAC5B;AAAA,MACE,KAAK;AAAA,MACL,KAAK,SAAS,UAAU,KAAK,MAAM,KAAK;AAAA,MACxC,KAAK,SAAS,SAAS,KAAK,QAAQ,KAAK,GAAG,IAAI;AAAA,MAChD,KAAK,OAAO,SAAS,KAAK,MAAM,KAAK,GAAG,IAAI;AAAA,IAC9C,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AAAA,EACd;AACA,QAAM,QAAQ,KAAK,SAAS,UAAU,CAAC,eAAe,IAAI,CAAC;AAC3D,QAAM,QAAQ,CAAC,GAAG,OAAO,GAAG,KAAK;AACjC,SAAO,MAAM,SAAS,KAAK,MAAM,KAAK,IAAI,CAAC,KAAK;AAClD;AAEO,SAAS,cAAc,OAAkD;AAC9E,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,QAAM,QAAQ,MAAM,IAAI,CAAC,SAAS,KAAK,KAAK,IAAI,MAAM,KAAK,WAAW,GAAG,MAAM,IAAI,CAAC,EAAE;AACtF,SAAO,CAAC,YAAY,IAAI,GAAG,KAAK,EAAE,KAAK,IAAI;AAC7C;AAEO,SAAS,aAAa,OAA8B;AACzD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,EAAE,OAAO,gBAAgB,KAAK,EAA0B;AAAA,IAC9D,OAAO,CAAC,EAAE,MAAM,QAAiB,SAAS,WAAW,KAAK,GAAG,WAAW,KAAK,IAAI,EAAE,CAAC;AAAA,EACtF;AACF;AAEO,SAAS,YAAY,OAA6B;AACvD,SAAO,CAAC,SACN,MAAM,MAAM,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,OAAO,IAAI,CAAC,GAAG,cAAc,GAAG,IAAI,IAAI;AAC3F;AAEA,MAAM,UAAU,CAAC,SACf,CAAC,SAAS,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,KAAK,CAAC,cAAc,UAAU,SAAS,IAAI,CAAC;AAEhF,SAAS,OAA8B,QAAc;AAC1D,QAAM,OAAO,OAAO;AACpB,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO;AACjC,QAAM,QAAQ,QAAQ,IAAI;AAC1B,SAAO,MAAM,SACT,EAAE,GAAG,QAAQ,YAAY,CAAC,GAAG,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,IACvE;AACN;AAEA,eAAsB,MACpB,SACA,SACA,SACiB;AACjB,MAAI,QAAQ;AACZ,MAAI;AACJ,KAAG;AACD,UAAM,OAAO,MAAM,QAAQ,kBAAkB,CAAC,GAAG,KAAK,QAAQ,OAAO;AACrE,eAAW,UAAU,KAAK,OAAO;AAC/B,YAAM,eAAe,MAAM,QAAQ,aAAa,OAAO,IAAI,OAAO;AAClE,UAAI,CAAC,aAAc;AACnB,YAAM,QAAQ,MAAM,aAAa,MAAM,OAAO;AAC9C,UAAI,CAAC,QAAQ,MAAM,UAAU,EAAE,OAAQ;AACvC,YAAM,aAAa,UAAU,OAAO,EAAE,YAAY,MAAM,WAAW,CAAC,GAAG,OAAO;AAC9E;AAAA,IACF;AACA,aAAS,KAAK;AAAA,EAChB,SAAS;AACT,SAAO;AACT;AAEO,SAAS,iBAAiB,MAGnB;AACZ,SAAO,gBAAgB;AAAA,IACrB,MAAM;AAAA,IACN,UAAU,CAAC,QAAQ,WAAW,MAAM,cAAc,KAAK,MAAM,MAAM,KAAK,CAAC,CAAC;AAAA,IAC1E,OAAO;AAAA,MACL,KAAK,UAAU,EAAE,WAAW,CAAC,OAAO,WAAW,YAAY,QAAQ,KAAK,KAAK,EAAE,CAAC;AAAA,MAChF,KAAK,gBAAgB;AAAA,QACnB,eAAe,YAAY;AACzB,gBAAM,KAAK,MAAM,MAAM;AACvB,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;",
4
+ "sourcesContent": ["import type { Context, JsonValue } from '@earendil-works/chord';\nimport {\n type AgentChange,\n type Cursor,\n defineExtension,\n type Extension,\n GenerationTask,\n type Harness,\n hook,\n type Storage,\n section,\n type ToolExecutionResult,\n ToolTask,\n} from '@earendil-works/pi-durable';\nimport { TITLES } from '../titles/extension.ts';\nimport type { Names, SecretName } from './names.ts';\nimport type { Scrub } from './scrub.ts';\nimport type { Vault } from './vault.ts';\nimport { listedLine, SECRETS_KIND } from './view.ts';\n\nexport { listedLine, SECRETS_KIND };\n\nexport const SECRETS = 'slicc-secrets';\nexport const SECRETS_COMMAND = '/secrets';\nexport const SHELL_GRANTS = ['fnox', 'exec', '--shell-grants', '--'] as const;\n\nexport const MODEL_LINE =\n \"Credentials are env vars (`$NAME`) holding placeholders; `secret list` shows them and where each works. The vault fills in the real value only in the listed header, for the listed domain, method and path, over HTTPS (curl, git, fetch). Adding a rule means editing `/etc/fnox/config.toml` with sudo, which the user approves. `fnox exec` gives real values only for secrets the user enabled for exec, and the secrets listed as 'in every bash' already hold real values in your commands. Everything is logged, and stored values are replaced by their placeholder in everything you see; edit around placeholders, since files show them too. Never ask the user to paste a credential into the chat.\";\n\ntype Leaf = { get(): string; set(text: string): void };\n\nfunction leaves(value: JsonValue, out: Leaf[], set: (next: JsonValue) => void): void {\n if (typeof value === 'string') {\n out.push({ get: () => value, set });\n return;\n }\n if (Array.isArray(value)) {\n value.forEach((item, at) => {\n leaves(item, out, (next) => {\n value[at] = next;\n });\n });\n return;\n }\n if (value && typeof value === 'object')\n for (const [key, item] of Object.entries(value))\n leaves(item as JsonValue, out, (next) => {\n (value as Record<string, JsonValue>)[key] = next;\n });\n}\n\nexport async function scrubResult(\n result: ToolExecutionResult,\n scrub: Pick<Scrub, 'texts'>\n): Promise<ToolExecutionResult | undefined> {\n const content = [...(result.output ?? [])];\n const diagnostics = [...(result.diagnostics ?? [])];\n const copy = (value: JsonValue | undefined) =>\n value === undefined ? undefined : (structuredClone(value) as JsonValue);\n const details = copy(result.details);\n const structured = copy(result.structuredOutput);\n const found: Leaf[] = [];\n content.forEach((item, at) => {\n if (item.type === 'text')\n found.push({ get: () => item.text, set: (text) => void (content[at] = { ...item, text }) });\n });\n diagnostics.forEach((item, at) => {\n found.push({\n get: () => item.message,\n set: (message) => void (diagnostics[at] = { ...item, message }),\n });\n });\n let replaced = details;\n if (details !== undefined)\n leaves(details, found, (next) => {\n replaced = next;\n });\n let output = structured;\n if (structured !== undefined)\n leaves(structured, found, (next) => {\n output = next;\n });\n const before = found.map((leaf) => leaf.get());\n const after = await scrub.texts(before);\n if (after.every((text, at) => text === before[at])) return undefined;\n found.forEach((leaf, at) => {\n leaf.set(after[at] as string);\n });\n const kept = result.output as ToolExecutionResult['output'] & unknown[];\n if (kept) kept.splice(0, kept.length, ...content);\n return {\n ...result,\n ...(kept ? { output: kept } : {}),\n ...(result.diagnostics ? { diagnostics } : {}),\n ...(replaced === undefined ? {} : { details: replaced }),\n ...(output === undefined ? {} : { structuredOutput: output }),\n };\n}\n\nfunction where(name: SecretName): string {\n const rules = name.rules.map((rule) =>\n [\n rule.domain,\n rule.header ? `header ${rule.header}` : '',\n rule.methods?.length ? rule.methods.join(' ') : '',\n rule.paths?.length ? rule.paths.join(' ') : '',\n ]\n .filter(Boolean)\n .join(', ')\n );\n const shell = name.exec === 'shell' ? ['in every bash'] : [];\n const parts = [...rules, ...shell];\n return parts.length ? `: ${parts.join('; ')}` : '';\n}\n\nexport function renderSecrets(names: readonly SecretName[]): string | undefined {\n if (!names.length) return undefined;\n const lines = names.map((name) => `- ${name.name} = ${name.placeholder}${where(name)}`);\n return [MODEL_LINE, '', ...lines].join('\\n');\n}\n\nexport function secretsEntry(names: readonly SecretName[]) {\n return {\n kind: SECRETS_KIND,\n data: { names: structuredClone(names) as unknown as JsonValue },\n model: [{ role: 'user' as const, content: listedLine(names), timestamp: Date.now() }],\n };\n}\n\nexport function shellGrants(names: Pick<Names, 'state'>) {\n return (argv: readonly string[]): readonly string[] =>\n names.state.value.some((name) => name.exec === 'shell') ? [...SHELL_GRANTS, ...argv] : argv;\n}\n\nconst missing = (list: readonly { name: string }[]) =>\n [SECRETS, TITLES].filter((name) => !list.some((extension) => extension.name === name));\n\nexport function pinned<T extends AgentChange>(change: T): T {\n const list = change.extensions;\n if (!Array.isArray(list)) return change;\n const added = missing(list);\n return added.length\n ? { ...change, extensions: [...list, ...added.map((name) => ({ name }))] }\n : change;\n}\n\nexport async function repin(\n harness: Pick<Harness, 'conversation'>,\n storage: Pick<Storage, 'scanConversations'>,\n context: Context\n): Promise<number> {\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 const conversation = await harness.conversation(record.id, context);\n if (!conversation) continue;\n const agent = await conversation.agent(context);\n if (!missing(agent.extensions).length) continue;\n await conversation.configure(pinned({ extensions: agent.extensions }), context);\n fixed++;\n }\n cursor = page.next;\n } while (cursor);\n return fixed;\n}\n\nexport function secretsExtension(deps: {\n names: Pick<Names, 'state'>;\n vault: Pick<Vault, 'ready' | 'texts'>;\n}): Extension {\n return defineExtension({\n name: SECRETS,\n sections: [section('secrets', () => renderSecrets(deps.names.state.value))],\n hooks: [\n hook(ToolTask, { afterTool: (_call, result) => scrubResult(result, deps.vault) }),\n hook(GenerationTask, {\n beforeRequest: async () => {\n await deps.vault.ready();\n return undefined;\n },\n }),\n ],\n });\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/services.js CHANGED
@@ -1,20 +1,118 @@
1
- import {
2
- defineService
3
- } from "@earendil-works/chord";
4
- const AgentControl = defineService("slicc.agent.control");
5
- const AgentTranscript = defineService("slicc.agent.transcript");
6
- const AgentSessions = defineService("slicc.agent.sessions");
7
- const AgentRemoteService = defineService("slicc.agent.remote");
8
- const AgentPagesService = defineService("slicc.agent.pages");
9
- const AGENT_SESSION = "agent";
10
- const AgentSettings = defineService("slicc.agent.settings");
1
+ import { contract } from "./serving.js";
2
+ const keys = (record) => Object.keys(record);
3
+ const CONTROL_METHODS = keys({
4
+ send: true,
5
+ wait: true,
6
+ withdraw: true,
7
+ abort: true,
8
+ compact: true,
9
+ compactAgent: true,
10
+ reset: true,
11
+ configure: true,
12
+ rewind: true,
13
+ rewindAgent: true,
14
+ resolveLick: true,
15
+ resolveAgentLick: true,
16
+ webhook: true,
17
+ stopAgent: true,
18
+ selectCone: true,
19
+ createCone: true,
20
+ createScoop: true,
21
+ drop: true,
22
+ unqueue: true,
23
+ sprinkleSend: true,
24
+ sprinkleCall: true,
25
+ memorySave: true,
26
+ memoryRemove: true,
27
+ freeze: true,
28
+ newChat: true,
29
+ thaw: true,
30
+ discard: true,
31
+ changesOpen: true,
32
+ changeAccept: true,
33
+ changeRevert: true,
34
+ monitorOpen: true,
35
+ monitorClose: true,
36
+ image: true,
37
+ agentCommands: true,
38
+ toolLabel: true
39
+ });
40
+ const CONTROL_READS = [
41
+ "wait",
42
+ "changesOpen",
43
+ "monitorOpen",
44
+ "monitorClose",
45
+ "image",
46
+ "toolLabel"
47
+ ];
48
+ const AgentControl = contract("slicc.agent.control", {
49
+ control: { methods: CONTROL_METHODS },
50
+ view: { methods: CONTROL_READS }
51
+ });
52
+ const TRANSCRIPT_STATES = keys({
53
+ state: true,
54
+ deliveries: true,
55
+ agents: true,
56
+ views: true,
57
+ commands: true,
58
+ privateRevision: true,
59
+ sprinkles: true,
60
+ memories: true,
61
+ memoryScopes: true,
62
+ frozen: true,
63
+ changes: true,
64
+ monitor: true,
65
+ vault: true,
66
+ labels: true
67
+ });
68
+ const AgentTranscript = contract("slicc.agent.transcript", {
69
+ transcript: { state: TRANSCRIPT_STATES }
70
+ });
71
+ const REMOTE_METHODS = keys({
72
+ prompt: true,
73
+ watch: true,
74
+ next: true,
75
+ close: true,
76
+ abort: true
77
+ });
78
+ const REMOTE_READS = ["watch", "next", "close"];
79
+ const AgentRemoteService = contract("slicc.agent.remote", {
80
+ remote: { methods: REMOTE_METHODS },
81
+ watch: { methods: REMOTE_READS }
82
+ });
83
+ const PAGE_METHODS = keys({
84
+ open: true,
85
+ report: true,
86
+ next: true,
87
+ answer: true,
88
+ close: true
89
+ });
90
+ const AgentPagesService = contract("slicc.agent.pages", {
91
+ pages: { methods: PAGE_METHODS },
92
+ none: {}
93
+ });
94
+ const SETTINGS_METHODS = keys({
95
+ connect: true,
96
+ disconnect: true,
97
+ signIn: true,
98
+ dismissMoved: true
99
+ });
100
+ const AgentSettings = contract("slicc.agent.settings", {
101
+ settings: { state: ["state"], methods: SETTINGS_METHODS },
102
+ view: { state: ["state"] }
103
+ });
11
104
  export {
12
- AGENT_SESSION,
13
105
  AgentControl,
14
106
  AgentPagesService,
15
107
  AgentRemoteService,
16
- AgentSessions,
17
108
  AgentSettings,
18
- AgentTranscript
109
+ AgentTranscript,
110
+ CONTROL_METHODS,
111
+ CONTROL_READS,
112
+ PAGE_METHODS,
113
+ REMOTE_METHODS,
114
+ REMOTE_READS,
115
+ SETTINGS_METHODS,
116
+ TRANSCRIPT_STATES
19
117
  };
20
118
  //# sourceMappingURL=services.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/services.ts"],
4
- "sourcesContent": ["import {\n type Context,\n defineService,\n type JsonValue,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { SendAttachment, ShownAttachment } from './attachments.ts';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport type { VaultState } from './secrets/vault.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: string | null;\n attachments?: SendAttachment[];\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n compactAgent(\n agentId: string,\n instructions: string | null,\n context: Context\n ): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n rewindAgent(agentId: string, messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n resolveAgentLick(\n agentId: string,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n sprinkleSend(\n sprinkleId: string,\n payload: { action: string; data: JsonValue | null; target: string | null },\n context: Context\n ): Promise<{ delivered: boolean }>;\n sprinkleCall(\n sprinkleId: string,\n method: string,\n args: JsonValue[],\n context: Context\n ): Promise<JsonValue>;\n memorySave(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n memoryRemove(id: string, context: Context): Promise<{ removed: boolean }>;\n freeze(agentId: string, context: Context): Promise<Created>;\n newChat(agentId: string, context: Context): Promise<Created>;\n thaw(frozenId: string, context: Context): Promise<Created>;\n discard(frozenId: string, context: Context): Promise<Created>;\n changesOpen(context: Context): Promise<void>;\n changeAccept(path: string, context: Context): Promise<Created>;\n changeRevert(path: string, context: Context): Promise<Created>;\n monitorOpen(lease: string | null, resync: boolean, context: Context): Promise<MonitorLease>;\n monitorClose(lease: string, context: Context): Promise<void>;\n image(ref: ImageRef, context: Context): Promise<StoredImage>;\n agentCommands(agentId: string, context: Context): Promise<Command[]>;\n toolLabel(agentId: string, callIds: string[], context: Context): Promise<string | null>;\n}\n\nexport interface ImageRef {\n entry: number;\n message: number;\n part: number;\n}\n\nexport type StoredImage = { data: string; mimeType: string } | { data: null; mimeType: null };\n\nexport interface MonitorLease {\n lease: string;\n ttlMs: number;\n}\n\nexport interface MonitorAgent {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n model: string | null;\n running: boolean;\n queued: number;\n tool: string | null;\n retry: { attempt: number; since: number; error: string } | null;\n failed: string | null;\n context: number | null;\n cpu?: number | null;\n}\n\nexport interface MonitorTask {\n id: string;\n kind: string;\n detail: string | null;\n agentId: string | null;\n owner: string | null;\n status: 'pending' | 'running' | 'waiting' | 'completing';\n phase: string;\n background: boolean;\n aborting: number | null;\n since: number;\n waits: number;\n}\n\nexport interface MonitorProcess {\n pid: number;\n ppid: number;\n pgid: number;\n user: string | null;\n agentId: string | null;\n command: string;\n state: 'S' | 'Z';\n started: number;\n zombie: number | null;\n tty: boolean;\n rss: number | null;\n utime?: number;\n stime?: number;\n cpu?: number | null;\n}\n\nexport interface MonitorProblem {\n id: string;\n at: number;\n agentId: string | null;\n kind: 'task' | 'retry' | 'turn';\n message: string;\n}\n\nexport interface MonitorFeed {\n open: boolean;\n at: number;\n usage: { tokens: number; cost: number; free: boolean } | null;\n agents: MonitorAgent[];\n tasks: MonitorTask[];\n processes: MonitorProcess[] | null;\n kernel: 'checking' | 'ok' | 'unreachable';\n problems: MonitorProblem[];\n paused: boolean;\n}\n\nexport interface MemoryDraft {\n id: string | null;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null; attachments?: ShownAttachment[] }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n readonly commands: ReplicatedState<Command[]>;\n readonly privateRevision: ReplicatedState<PrivateRevision>;\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes: ReplicatedState<MemoryScope[]>;\n readonly frozen: ReplicatedState<FrozenCone[]>;\n readonly changes: ReplicatedState<ChangesView>;\n readonly monitor: ReplicatedState<MonitorFeed>;\n readonly vault: ReplicatedState<VaultState>;\n readonly labels: ReplicatedState<Record<string, string>>;\n}\n\nexport interface PrivateRevision {\n revision: number;\n}\n\nexport interface Command {\n name: string;\n description: string;\n kind: 'prompt' | 'skill' | 'action';\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AgentRemoteService = defineService<AgentRemote>('slicc.agent.remote');\n\nexport const AgentPagesService = defineService<AgentPages>('slicc.agent.pages');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n images?: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected' | 'expired';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n group?: 'providers' | 'mcp';\n secret?: 'api-key' | 'token';\n store?: 'vault';\n fields?: AccountField[];\n}\n\nexport interface AccountField {\n name: string;\n label: string;\n required?: boolean;\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface McpSignIn {\n url: string;\n state: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n moved?: { kind: 'model' | 'mcp'; label: string }[];\n labels?: Record<string, string>;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string | null,\n fields: Record<string, string> | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | McpSignIn | null>;\n dismissMoved(context: Context): Promise<void>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
5
- "mappings": "AAAA;AAAA,EAEE;AAAA,OAGK;AAoOA,MAAM,eAAe,cAA4B,qBAAqB;AA+BtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,qBAAqB,cAA2B,oBAAoB;AAE1E,MAAM,oBAAoB,cAA0B,mBAAmB;AAEvE,MAAM,gBAAgB;AAsEtB,MAAM,gBAAgB,cAA6B,sBAAsB;",
4
+ "sourcesContent": ["import type { Context, JsonValue, RemoteContract, ReplicatedState } from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { SendAttachment, ShownAttachment } from './attachments.ts';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { AgentPages } from './remote/pages.ts';\nimport type { VaultState } from './secrets/vault.ts';\nimport { contract } from './serving.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: string | null;\n attachments?: SendAttachment[];\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n compactAgent(\n agentId: string,\n instructions: string | null,\n context: Context\n ): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n rewindAgent(agentId: string, messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n resolveAgentLick(\n agentId: string,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n sprinkleSend(\n sprinkleId: string,\n payload: { action: string; data: JsonValue | null; target: string | null },\n context: Context\n ): Promise<{ delivered: boolean }>;\n sprinkleCall(\n sprinkleId: string,\n method: string,\n args: JsonValue[],\n context: Context\n ): Promise<JsonValue>;\n memorySave(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n memoryRemove(id: string, context: Context): Promise<{ removed: boolean }>;\n freeze(agentId: string, context: Context): Promise<Created>;\n newChat(agentId: string, context: Context): Promise<Created>;\n thaw(frozenId: string, context: Context): Promise<Created>;\n discard(frozenId: string, context: Context): Promise<Created>;\n changesOpen(context: Context): Promise<void>;\n changeAccept(path: string, context: Context): Promise<Created>;\n changeRevert(path: string, context: Context): Promise<Created>;\n monitorOpen(lease: string | null, resync: boolean, context: Context): Promise<MonitorLease>;\n monitorClose(lease: string, context: Context): Promise<void>;\n image(ref: ImageRef, context: Context): Promise<StoredImage>;\n agentCommands(agentId: string, context: Context): Promise<Command[]>;\n toolLabel(agentId: string, callIds: string[], context: Context): Promise<string | null>;\n}\n\nexport interface ImageRef {\n entry: number;\n message: number;\n part: number;\n}\n\nexport type StoredImage = { data: string; mimeType: string } | { data: null; mimeType: null };\n\nexport interface MonitorLease {\n lease: string;\n ttlMs: number;\n}\n\nexport interface MonitorAgent {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n model: string | null;\n running: boolean;\n queued: number;\n tool: string | null;\n retry: { attempt: number; since: number; error: string } | null;\n failed: string | null;\n context: number | null;\n cpu?: number | null;\n}\n\nexport interface MonitorTask {\n id: string;\n kind: string;\n detail: string | null;\n agentId: string | null;\n owner: string | null;\n status: 'pending' | 'running' | 'waiting' | 'completing';\n phase: string;\n background: boolean;\n aborting: number | null;\n since: number;\n waits: number;\n}\n\nexport interface MonitorProcess {\n pid: number;\n ppid: number;\n pgid: number;\n user: string | null;\n agentId: string | null;\n command: string;\n state: 'S' | 'Z';\n started: number;\n zombie: number | null;\n tty: boolean;\n rss: number | null;\n utime?: number;\n stime?: number;\n cpu?: number | null;\n}\n\nexport interface MonitorProblem {\n id: string;\n at: number;\n agentId: string | null;\n kind: 'task' | 'retry' | 'turn';\n message: string;\n}\n\nexport interface MonitorFeed {\n open: boolean;\n at: number;\n usage: { tokens: number; cost: number; free: boolean } | null;\n agents: MonitorAgent[];\n tasks: MonitorTask[];\n processes: MonitorProcess[] | null;\n kernel: 'checking' | 'ok' | 'unreachable';\n problems: MonitorProblem[];\n paused: boolean;\n}\n\nexport interface MemoryDraft {\n id: string | null;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null; attachments?: ShownAttachment[] }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nconst keys = <T>(record: Record<keyof T, true>) => Object.keys(record) as (keyof T & string)[];\n\nexport const CONTROL_METHODS = keys<AgentControl>({\n send: true,\n wait: true,\n withdraw: true,\n abort: true,\n compact: true,\n compactAgent: true,\n reset: true,\n configure: true,\n rewind: true,\n rewindAgent: true,\n resolveLick: true,\n resolveAgentLick: true,\n webhook: true,\n stopAgent: true,\n selectCone: true,\n createCone: true,\n createScoop: true,\n drop: true,\n unqueue: true,\n sprinkleSend: true,\n sprinkleCall: true,\n memorySave: true,\n memoryRemove: true,\n freeze: true,\n newChat: true,\n thaw: true,\n discard: true,\n changesOpen: true,\n changeAccept: true,\n changeRevert: true,\n monitorOpen: true,\n monitorClose: true,\n image: true,\n agentCommands: true,\n toolLabel: true,\n});\n\nexport const CONTROL_READS: readonly (keyof AgentControl & string)[] = [\n 'wait',\n 'changesOpen',\n 'monitorOpen',\n 'monitorClose',\n 'image',\n 'toolLabel',\n];\n\nexport const AgentControl: RemoteContract = contract('slicc.agent.control', {\n control: { methods: CONTROL_METHODS },\n view: { methods: CONTROL_READS },\n});\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n readonly commands: ReplicatedState<Command[]>;\n readonly privateRevision: ReplicatedState<PrivateRevision>;\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes: ReplicatedState<MemoryScope[]>;\n readonly frozen: ReplicatedState<FrozenCone[]>;\n readonly changes: ReplicatedState<ChangesView>;\n readonly monitor: ReplicatedState<MonitorFeed>;\n readonly vault: ReplicatedState<VaultState>;\n readonly labels: ReplicatedState<Record<string, string>>;\n}\n\nexport interface PrivateRevision {\n revision: number;\n}\n\nexport interface Command {\n name: string;\n description: string;\n kind: 'prompt' | 'skill' | 'action';\n}\n\nexport const TRANSCRIPT_STATES = keys<AgentTranscript>({\n state: true,\n deliveries: true,\n agents: true,\n views: true,\n commands: true,\n privateRevision: true,\n sprinkles: true,\n memories: true,\n memoryScopes: true,\n frozen: true,\n changes: true,\n monitor: true,\n vault: true,\n labels: true,\n});\n\nexport const AgentTranscript: RemoteContract = contract('slicc.agent.transcript', {\n transcript: { state: TRANSCRIPT_STATES },\n});\n\nexport const REMOTE_METHODS = keys<AgentRemote>({\n prompt: true,\n watch: true,\n next: true,\n close: true,\n abort: true,\n});\n\nexport const REMOTE_READS: readonly (keyof AgentRemote & string)[] = ['watch', 'next', 'close'];\n\nexport const AgentRemoteService: RemoteContract = contract('slicc.agent.remote', {\n remote: { methods: REMOTE_METHODS },\n watch: { methods: REMOTE_READS },\n});\n\nexport const PAGE_METHODS = keys<AgentPages>({\n open: true,\n report: true,\n next: true,\n answer: true,\n close: true,\n});\n\nexport const AgentPagesService: RemoteContract = contract('slicc.agent.pages', {\n pages: { methods: PAGE_METHODS },\n none: {},\n});\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n images?: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected' | 'expired';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n group?: 'providers' | 'mcp';\n secret?: 'api-key' | 'token';\n store?: 'vault';\n fields?: AccountField[];\n}\n\nexport interface AccountField {\n name: string;\n label: string;\n required?: boolean;\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface McpSignIn {\n url: string;\n state: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n moved?: { kind: 'model' | 'mcp'; label: string }[];\n labels?: Record<string, string>;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string | null,\n fields: Record<string, string> | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | McpSignIn | null>;\n dismissMoved(context: Context): Promise<void>;\n}\n\nexport const SETTINGS_METHODS = keys<Omit<AgentSettings, 'state'>>({\n connect: true,\n disconnect: true,\n signIn: true,\n dismissMoved: true,\n});\n\nexport const AgentSettings: RemoteContract = contract('slicc.agent.settings', {\n settings: { state: ['state'], methods: SETTINGS_METHODS },\n view: { state: ['state'] },\n});\n"],
5
+ "mappings": "AASA,SAAS,gBAAgB;AA4NzB,MAAM,OAAO,CAAI,WAAkC,OAAO,KAAK,MAAM;AAE9D,MAAM,kBAAkB,KAAmB;AAAA,EAChD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,UAAU;AAAA,EACV,OAAO;AAAA,EACP,SAAS;AAAA,EACT,cAAc;AAAA,EACd,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,MAAM;AAAA,EACN,SAAS;AAAA,EACT,cAAc;AAAA,EACd,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,EACT,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,OAAO;AAAA,EACP,eAAe;AAAA,EACf,WAAW;AACb,CAAC;AAEM,MAAM,gBAA0D;AAAA,EACrE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,MAAM,eAA+B,SAAS,uBAAuB;AAAA,EAC1E,SAAS,EAAE,SAAS,gBAAgB;AAAA,EACpC,MAAM,EAAE,SAAS,cAAc;AACjC,CAAC;AA+BM,MAAM,oBAAoB,KAAsB;AAAA,EACrD,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,UAAU;AAAA,EACV,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AACV,CAAC;AAEM,MAAM,kBAAkC,SAAS,0BAA0B;AAAA,EAChF,YAAY,EAAE,OAAO,kBAAkB;AACzC,CAAC;AAEM,MAAM,iBAAiB,KAAkB;AAAA,EAC9C,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AACT,CAAC;AAEM,MAAM,eAAwD,CAAC,SAAS,QAAQ,OAAO;AAEvF,MAAM,qBAAqC,SAAS,sBAAsB;AAAA,EAC/E,QAAQ,EAAE,SAAS,eAAe;AAAA,EAClC,OAAO,EAAE,SAAS,aAAa;AACjC,CAAC;AAEM,MAAM,eAAe,KAAiB;AAAA,EAC3C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AACT,CAAC;AAEM,MAAM,oBAAoC,SAAS,qBAAqB;AAAA,EAC7E,OAAO,EAAE,SAAS,aAAa;AAAA,EAC/B,MAAM,CAAC;AACT,CAAC;AAsEM,MAAM,mBAAmB,KAAmC;AAAA,EACjE,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,cAAc;AAChB,CAAC;AAEM,MAAM,gBAAgC,SAAS,wBAAwB;AAAA,EAC5E,UAAU,EAAE,OAAO,CAAC,OAAO,GAAG,SAAS,iBAAiB;AAAA,EACxD,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE;AAC3B,CAAC;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,174 @@
1
+ import {
2
+ isChordError,
3
+ method,
4
+ remote
5
+ } from "@earendil-works/chord";
6
+ import { BACKGROUND_CONTEXT, withAbortSignal } from "@earendil-works/chord/context";
7
+ import { diffRevisions } from "@earendil-works/chord/delta";
8
+ import { Type } from "typebox";
9
+ const AGENT_HOST = "agent";
10
+ const seatOf = (principal) => principal.seat;
11
+ const principalKey = (principal) => {
12
+ const { seat, link } = principal ?? {};
13
+ return seat ? `${seat}:${link}` : "host";
14
+ };
15
+ const Args = Type.Array(Type.Unknown());
16
+ const Value = Type.Unknown();
17
+ function contract(name, variants) {
18
+ const def = {
19
+ version: 1,
20
+ input: Type.Object({}),
21
+ variants: Object.fromEntries(
22
+ Object.entries(variants).map(([kind, members]) => [
23
+ kind,
24
+ {
25
+ state: Object.fromEntries((members.state ?? []).map((member) => [member, Value])),
26
+ methods: Object.fromEntries(
27
+ (members.methods ?? []).map((member) => [member, method(Args, Value)])
28
+ )
29
+ }
30
+ ])
31
+ )
32
+ };
33
+ return remote(name, def);
34
+ }
35
+ const contextOf = (ctx) => withAbortSignal(ctx.signal, BACKGROUND_CONTEXT);
36
+ function served(impl, names) {
37
+ return Object.fromEntries(
38
+ names.map((name) => [
39
+ name,
40
+ (args, ctx) => impl()[name](...args, contextOf(ctx))
41
+ ])
42
+ );
43
+ }
44
+ const readables = /* @__PURE__ */ new WeakMap();
45
+ function readable(state) {
46
+ const known = readables.get(state);
47
+ if (known) return known;
48
+ const values = /* @__PURE__ */ new Set();
49
+ const events = /* @__PURE__ */ new Set();
50
+ let value = state.value;
51
+ let off;
52
+ const start = () => {
53
+ if (off) return;
54
+ value = state.value;
55
+ off = state.subscribe((next) => {
56
+ const before = value;
57
+ value = next;
58
+ if (before === next) return;
59
+ const event = before === void 0 ? { type: "reset", value: next } : { type: "ops", ops: diffRevisions(before, next), value: next };
60
+ for (const listener of [...events]) listener(event);
61
+ for (const listener of [...values]) listener(next);
62
+ });
63
+ };
64
+ const stop = () => {
65
+ if (values.size + events.size > 0 || !off) return;
66
+ off();
67
+ off = void 0;
68
+ };
69
+ const made = {
70
+ get value() {
71
+ return off ? value : state.value;
72
+ },
73
+ subscribe(listener) {
74
+ values.add(listener);
75
+ start();
76
+ listener(value);
77
+ return () => {
78
+ values.delete(listener);
79
+ stop();
80
+ };
81
+ },
82
+ events(listener) {
83
+ events.add(listener);
84
+ start();
85
+ listener({ type: "snap", value });
86
+ return () => {
87
+ events.delete(listener);
88
+ stop();
89
+ };
90
+ }
91
+ };
92
+ readables.set(state, made);
93
+ return made;
94
+ }
95
+ function readablesOf(states) {
96
+ return Object.fromEntries(Object.entries(states).map(([name, state]) => [name, readable(state)]));
97
+ }
98
+ function replicated(member) {
99
+ return {
100
+ get value() {
101
+ return member.value;
102
+ },
103
+ subscribe(listener) {
104
+ let sequence = 0;
105
+ return member.subscribe((value) => {
106
+ if (value === void 0) return;
107
+ sequence += 1;
108
+ void listener(value, BACKGROUND_CONTEXT, {
109
+ kind: sequence === 1 ? "hydrate" : "update",
110
+ sequence
111
+ });
112
+ });
113
+ }
114
+ };
115
+ }
116
+ function kept(member) {
117
+ let resolve;
118
+ const ready = new Promise((done) => {
119
+ resolve = done;
120
+ });
121
+ const stop = member.subscribe((value) => {
122
+ if (value !== void 0) resolve();
123
+ });
124
+ return { ready, stop };
125
+ }
126
+ const CLOSED = "the connection to the agent is closed";
127
+ function plain(error) {
128
+ if (!isChordError(error)) return error;
129
+ if (error.code === "released") return Object.assign(new Error(CLOSED), { code: "released" });
130
+ const prefix = error.reason ? `${error.code}/${error.reason}: ` : `${error.code}: `;
131
+ const message = error.message.startsWith(prefix) ? error.message.slice(prefix.length) : error.message;
132
+ return Object.assign(new Error(message), { code: error.code });
133
+ }
134
+ const isContext = (value) => typeof value === "object" && value !== null && "abortSignal" in value && typeof value.value === "function";
135
+ function bound(handle, root, names, absent) {
136
+ const shape = handle.shape;
137
+ const members = new Set(shape?.members ?? []);
138
+ return Object.fromEntries(
139
+ names.map((name) => {
140
+ if (!members.has(name)) return [name, absent(name)];
141
+ const call = handle[name];
142
+ return [
143
+ name,
144
+ async (...given) => {
145
+ const last = given.at(-1);
146
+ const context = isContext(last) ? last : void 0;
147
+ const args = context ? given.slice(0, -1) : given;
148
+ const signal = context?.abortSignal;
149
+ try {
150
+ return await root((ctx) => call(args, ctx), signal ? { signal } : {});
151
+ } catch (error) {
152
+ throw plain(error);
153
+ }
154
+ }
155
+ ];
156
+ })
157
+ );
158
+ }
159
+ export {
160
+ AGENT_HOST,
161
+ CLOSED,
162
+ bound,
163
+ contextOf,
164
+ contract,
165
+ kept,
166
+ plain,
167
+ principalKey,
168
+ readable,
169
+ readablesOf,
170
+ replicated,
171
+ seatOf,
172
+ served
173
+ };
174
+ //# sourceMappingURL=serving.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/serving.ts"],
4
+ "sourcesContent": ["import {\n type Ctx,\n isChordError,\n method,\n type Principal,\n type Readable,\n type RemoteContract,\n type ReplicatedState,\n remote,\n type StateEvent,\n} from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT, type Context, withAbortSignal } from '@earendil-works/chord/context';\nimport { diffRevisions } from '@earendil-works/chord/delta';\nimport { Type } from 'typebox';\n\nexport const AGENT_HOST = 'agent';\n\nexport type Seat = 'owner' | 'control' | 'view';\n\nexport interface AgentPrincipal {\n seat: Seat;\n link: number;\n}\n\nexport const seatOf = (principal: Principal): Seat => (principal as AgentPrincipal).seat;\n\nexport const principalKey = (principal: Principal): string => {\n const { seat, link } = (principal ?? {}) as Partial<AgentPrincipal>;\n return seat ? `${seat}:${link}` : 'host';\n};\n\nexport interface Members {\n methods?: readonly string[];\n state?: readonly string[];\n}\n\nconst Args = Type.Array(Type.Unknown());\nconst Value = Type.Unknown();\n\nexport function contract(name: string, variants: Record<string, Members>): RemoteContract {\n const def: Parameters<typeof remote>[1] = {\n version: 1,\n input: Type.Object({}),\n variants: Object.fromEntries(\n Object.entries(variants).map(([kind, members]) => [\n kind,\n {\n state: Object.fromEntries((members.state ?? []).map((member) => [member, Value])),\n methods: Object.fromEntries(\n (members.methods ?? []).map((member) => [member, method(Args, Value)])\n ),\n },\n ])\n ),\n };\n return remote(name, def);\n}\n\nexport type Callable = (...args: unknown[]) => Promise<unknown>;\n\nexport const contextOf = (ctx: Ctx): Context => withAbortSignal(ctx.signal, BACKGROUND_CONTEXT);\n\nexport function served(\n impl: () => object,\n names: readonly string[]\n): Record<string, (args: unknown[], ctx: Ctx) => Promise<unknown>> {\n return Object.fromEntries(\n names.map((name) => [\n name,\n (args: unknown[], ctx: Ctx) =>\n ((impl() as Record<string, Callable>)[name] as Callable)(...args, contextOf(ctx)),\n ])\n );\n}\n\nconst readables = new WeakMap<object, Readable<unknown>>();\n\nexport function readable<T>(state: ReplicatedState<T>): Readable<T> {\n const known = readables.get(state);\n if (known) return known as Readable<T>;\n const values = new Set<(value: T) => void>();\n const events = new Set<(event: StateEvent<T>) => void>();\n let value = state.value as T;\n let off: (() => void) | undefined;\n const start = () => {\n if (off) return;\n value = state.value as T;\n off = state.subscribe((next) => {\n const before = value;\n value = next;\n if (before === next) return;\n const event: StateEvent<T> =\n before === undefined\n ? { type: 'reset', value: next }\n : { type: 'ops', ops: diffRevisions(before as never, next as never), value: next };\n for (const listener of [...events]) listener(event);\n for (const listener of [...values]) listener(next);\n });\n };\n const stop = () => {\n if (values.size + events.size > 0 || !off) return;\n off();\n off = undefined;\n };\n const made: Readable<T> = {\n get value() {\n return off ? value : (state.value as T);\n },\n subscribe(listener) {\n values.add(listener);\n start();\n listener(value);\n return () => {\n values.delete(listener);\n stop();\n };\n },\n events(listener) {\n events.add(listener);\n start();\n listener({ type: 'snap', value });\n return () => {\n events.delete(listener);\n stop();\n };\n },\n };\n readables.set(state, made);\n return made;\n}\n\nexport function readablesOf(\n states: Record<string, ReplicatedState<unknown>>\n): Record<string, Readable<unknown>> {\n return Object.fromEntries(Object.entries(states).map(([name, state]) => [name, readable(state)]));\n}\n\nexport interface Remote {\n readonly kind: string | undefined;\n readonly ready: Promise<void>;\n release(): void;\n [member: string]: unknown;\n}\n\nexport function replicated<T>(member: Readable<T | undefined>): ReplicatedState<T> {\n return {\n get value() {\n return member.value;\n },\n subscribe(listener: (value: T, context: Context, delivery: never) => unknown) {\n let sequence = 0;\n return member.subscribe((value) => {\n if (value === undefined) return;\n sequence += 1;\n void listener(value, BACKGROUND_CONTEXT, {\n kind: sequence === 1 ? 'hydrate' : 'update',\n sequence,\n } as never);\n });\n },\n } as ReplicatedState<T>;\n}\n\nexport function kept(member: Readable<unknown>): { ready: Promise<void>; stop(): void } {\n let resolve!: () => void;\n const ready = new Promise<void>((done) => {\n resolve = done;\n });\n const stop = member.subscribe((value) => {\n if (value !== undefined) resolve();\n });\n return { ready, stop };\n}\n\nexport const CLOSED = 'the connection to the agent is closed';\n\nexport function plain(error: unknown): unknown {\n if (!isChordError(error)) return error;\n if (error.code === 'released') return Object.assign(new Error(CLOSED), { code: 'released' });\n const prefix = error.reason ? `${error.code}/${error.reason}: ` : `${error.code}: `;\n const message = error.message.startsWith(prefix)\n ? error.message.slice(prefix.length)\n : error.message;\n return Object.assign(new Error(message), { code: error.code });\n}\n\nconst isContext = (value: unknown): value is Context =>\n typeof value === 'object' &&\n value !== null &&\n 'abortSignal' in value &&\n typeof (value as Context).value === 'function';\n\nexport type Root = <R>(\n run: (ctx: Ctx) => Promise<R>,\n options: { signal?: AbortSignal }\n) => Promise<R>;\n\nexport function bound<T extends object>(\n handle: Remote,\n root: Root,\n names: readonly string[],\n absent: (name: string) => Callable\n): T {\n const shape = (handle as { shape?: { members: readonly string[] } }).shape;\n const members = new Set(shape?.members ?? []);\n return Object.fromEntries(\n names.map((name) => {\n if (!members.has(name)) return [name, absent(name)];\n const call = handle[name] as (args: unknown[], ctx: Ctx) => Promise<unknown>;\n return [\n name,\n async (...given: unknown[]) => {\n const last = given.at(-1);\n const context = isContext(last) ? last : undefined;\n const args = context ? given.slice(0, -1) : given;\n const signal = context?.abortSignal;\n try {\n return await root((ctx) => call(args, ctx), signal ? { signal } : {});\n } catch (error) {\n throw plain(error);\n }\n },\n ];\n })\n ) as T;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }