@ai-ecoverse/slicc-agent 3.48.1 → 3.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +38 -6
  2. package/bin/agent +1 -0
  3. package/dist/control.js +1 -1
  4. package/dist/control.js.map +2 -2
  5. package/dist/freezer/index.js +29 -8
  6. package/dist/freezer/index.js.map +2 -2
  7. package/dist/freezer/title.js +2 -2
  8. package/dist/freezer/title.js.map +2 -2
  9. package/dist/gelatiere/catalog.js +1 -1
  10. package/dist/gelatiere/catalog.js.map +2 -2
  11. package/dist/gelatiere/index.js +44 -21
  12. package/dist/gelatiere/index.js.map +2 -2
  13. package/dist/gelatiere/store.js +9 -5
  14. package/dist/gelatiere/store.js.map +2 -2
  15. package/dist/index.js.map +2 -2
  16. package/dist/kernel/accounts.js +141 -0
  17. package/dist/kernel/accounts.js.map +7 -0
  18. package/dist/kernel/agent-users.js +180 -0
  19. package/dist/kernel/agent-users.js.map +7 -0
  20. package/dist/kernel/env.js +9 -3
  21. package/dist/kernel/env.js.map +2 -2
  22. package/dist/kernel/users.js +16 -1
  23. package/dist/kernel/users.js.map +2 -2
  24. package/dist/layout.js +62 -0
  25. package/dist/layout.js.map +7 -0
  26. package/dist/licks/index.js.map +2 -2
  27. package/dist/licks/sources.js +69 -20
  28. package/dist/licks/sources.js.map +2 -2
  29. package/dist/main.js +70 -36
  30. package/dist/main.js.map +2 -2
  31. package/dist/mcp/index.js +5 -3
  32. package/dist/mcp/index.js.map +2 -2
  33. package/dist/mcp/paths.js +15 -0
  34. package/dist/mcp/paths.js.map +7 -0
  35. package/dist/mcp/runtime.js +16 -14
  36. package/dist/mcp/runtime.js.map +3 -3
  37. package/dist/memory/extract.js +2 -1
  38. package/dist/memory/extract.js.map +2 -2
  39. package/dist/memory/prompt.js +5 -1
  40. package/dist/memory/prompt.js.map +2 -2
  41. package/dist/memory/runtime.js +9 -1
  42. package/dist/memory/runtime.js.map +2 -2
  43. package/dist/roles/roles.js +54 -15
  44. package/dist/roles/roles.js.map +2 -2
  45. package/dist/scoops/cli.js +40 -19
  46. package/dist/scoops/cli.js.map +2 -2
  47. package/dist/scoops/extension.js +19 -15
  48. package/dist/scoops/extension.js.map +2 -2
  49. package/dist/scoops/index.js +82 -11
  50. package/dist/scoops/index.js.map +2 -2
  51. package/dist/scoops/requests.js +2 -1
  52. package/dist/scoops/requests.js.map +2 -2
  53. package/dist/scoops/service.js +45 -38
  54. package/dist/scoops/service.js.map +2 -2
  55. package/dist/scoops/sync.js +63 -13
  56. package/dist/scoops/sync.js.map +2 -2
  57. package/dist/skills/index.js +76 -34
  58. package/dist/skills/index.js.map +3 -3
  59. package/dist/sprinkles/index.js +114 -25
  60. package/dist/sprinkles/index.js.map +2 -2
  61. package/dist/sprinkles/sprinkles.js +13 -4
  62. package/dist/sprinkles/sprinkles.js.map +2 -2
  63. package/dist/types/freezer/index.d.ts +3 -1
  64. package/dist/types/freezer/title.d.ts +1 -1
  65. package/dist/types/gelatiere/index.d.ts +8 -0
  66. package/dist/types/gelatiere/store.d.ts +3 -2
  67. package/dist/types/index.d.ts +1 -1
  68. package/dist/types/kernel/accounts.d.ts +29 -0
  69. package/dist/types/kernel/agent-users.d.ts +37 -0
  70. package/dist/types/kernel/env.d.ts +6 -0
  71. package/dist/types/kernel/users.d.ts +15 -0
  72. package/dist/types/layout.d.ts +26 -0
  73. package/dist/types/licks/index.d.ts +2 -1
  74. package/dist/types/licks/sources.d.ts +11 -1
  75. package/dist/types/mcp/paths.d.ts +3 -0
  76. package/dist/types/mcp/runtime.d.ts +3 -3
  77. package/dist/types/memory/prompt.d.ts +1 -0
  78. package/dist/types/memory/runtime.d.ts +1 -0
  79. package/dist/types/roles/roles.d.ts +14 -1
  80. package/dist/types/scoops/cli.d.ts +5 -0
  81. package/dist/types/scoops/extension.d.ts +6 -3
  82. package/dist/types/scoops/index.d.ts +9 -0
  83. package/dist/types/scoops/requests.d.ts +2 -0
  84. package/dist/types/scoops/service.d.ts +5 -3
  85. package/dist/types/scoops/sync.d.ts +9 -6
  86. package/dist/types/skills/index.d.ts +10 -2
  87. package/dist/types/sprinkles/index.d.ts +15 -0
  88. package/dist/types/sprinkles/sprinkles.d.ts +7 -1
  89. package/package.json +4 -4
@@ -3,7 +3,7 @@ import {
3
3
  defineExtension,
4
4
  section
5
5
  } from "@earendil-works/pi-durable";
6
- import { manifestDirs } from "../roles/roles.js";
6
+ import { PACKAGE_ROOTS, piPackages } from "../roles/roles.js";
7
7
  import {
8
8
  expandSkillCommand,
9
9
  formatSkillsForPrompt,
@@ -25,6 +25,12 @@ const SKILLS_DIR = "/var/lib/slicc/agent/skills";
25
25
  const PROMPTS_DIR = "/var/lib/slicc/agent/prompts";
26
26
  const GLOBAL_DIR = "/usr/share/skills";
27
27
  const RELOAD_MS = 300;
28
+ function userRoots(homes) {
29
+ return homes.flatMap((home) => [
30
+ { dir: `${home}/.pi/agent/skills`, source: "user" },
31
+ { dir: `${home}/.agents/skills`, source: "user" }
32
+ ]);
33
+ }
28
34
  function piResources(dir, text, kind) {
29
35
  let manifest;
30
36
  try {
@@ -36,13 +42,8 @@ function piResources(dir, text, kind) {
36
42
  const paths = Array.isArray(listed) ? listed : typeof listed === "string" ? [listed] : [];
37
43
  return paths.filter((path) => typeof path === "string").map((path) => `${dir}/${path.replace(/^\.\//, "").replace(/\/$/, "")}`);
38
44
  }
39
- async function packaged(env, kind, context) {
40
- const out = [];
41
- for (const dir of await manifestDirs(env, "/node_modules", context)) {
42
- const read = await env.readTextFile(`${dir}/package.json`, context);
43
- if (read.ok) out.push(...piResources(dir, read.value, kind));
44
- }
45
- return out;
45
+ function packaged(found, kind) {
46
+ return found.packages.flatMap(({ dir, manifest }) => piResources(dir, manifest, kind));
46
47
  }
47
48
  async function installBuiltins(env, assets, context) {
48
49
  await env.remove(SKILLS_DIR, { recursive: true, force: true }, context);
@@ -95,15 +96,28 @@ function commandsOf(skills, templates) {
95
96
  }
96
97
  function setupSkills(registry, licks) {
97
98
  let current = [];
99
+ let own = /* @__PURE__ */ new Map();
100
+ let people;
101
+ const mine = (conversationId) => {
102
+ const at = people?.homeOf(conversationId);
103
+ return (at ? own.get(at) : void 0) ?? current;
104
+ };
98
105
  const extension = defineExtension({
99
106
  name: "slicc-skills",
100
- sections: [section("skills", async () => formatSkillsForPrompt(current) || void 0)]
107
+ sections: [
108
+ section("skills", async (input) => {
109
+ return formatSkillsForPrompt(mine(input.conversationId)) || void 0;
110
+ })
111
+ ]
101
112
  });
102
113
  registry.install(extension);
103
114
  return {
104
115
  extension,
105
116
  async attach(options, context) {
106
117
  const { env, home } = options;
118
+ const dir = options.agentDir ?? `${home}/.pi/agent`;
119
+ people = options.people;
120
+ const homes = () => options.people?.homes() ?? [];
107
121
  if (options.assets)
108
122
  await installBuiltins(env, options.assets, context).catch(() => void 0);
109
123
  await env.exec(`mkdir -p ${GLOBAL_DIR} && chmod 755 ${GLOBAL_DIR}`, void 0, context);
@@ -111,26 +125,39 @@ function setupSkills(registry, licks) {
111
125
  const commands = replicatedState([]);
112
126
  const reported = /* @__PURE__ */ new Set();
113
127
  const reload = async (using) => {
128
+ const packages = await piPackages(env, PACKAGE_ROOTS, using);
114
129
  const roots = [
115
- { dir: `${home}/.pi/agent/skills`, source: "user" },
116
- { dir: `${home}/.agents/skills`, source: "user" },
117
- { dir: GLOBAL_DIR, source: "global" },
118
- ...(await packaged(env, "skills", using)).map((dir) => ({
119
- dir,
120
- source: "package"
121
- })),
122
- { dir: SKILLS_DIR, source: "builtin" }
130
+ ...options.people ? [{ dir: GLOBAL_DIR, source: "global" }] : [
131
+ { dir: `${dir}/skills`, source: "user" },
132
+ { dir: `${home}/.agents/skills`, source: "user" },
133
+ { dir: GLOBAL_DIR, source: "global" }
134
+ ],
135
+ { dir: SKILLS_DIR, source: "builtin" },
136
+ ...packaged(packages, "skills").map((dir2) => ({ dir: dir2, source: "package" }))
123
137
  ];
124
138
  const loaded = await loadSkills(env, roots, using);
139
+ const homesLoaded = /* @__PURE__ */ new Map();
140
+ for (const at of homes()) {
141
+ const files = await options.people?.envOf(at);
142
+ if (!files) continue;
143
+ const found = await loadSkills(files, [...userRoots([at]), ...roots], using);
144
+ homesLoaded.set(at, found.skills);
145
+ loaded.diagnostics.push(...found.diagnostics);
146
+ }
147
+ own = homesLoaded;
125
148
  const prompts = await loadTemplates(
126
149
  env,
127
- [`${home}/.pi/agent/prompts`, ...await packaged(env, "prompts", using), PROMPTS_DIR],
150
+ [`${dir}/prompts`, PROMPTS_DIR, ...packaged(packages, "prompts")],
128
151
  using
129
152
  );
130
153
  current = loaded.skills;
131
154
  templates = prompts.templates;
132
155
  commands.replace(using, commandsOf(current, templates));
133
- for (const problem of [...loaded.diagnostics, ...prompts.diagnostics]) {
156
+ for (const problem of [
157
+ ...packages.skipped,
158
+ ...loaded.diagnostics,
159
+ ...prompts.diagnostics
160
+ ]) {
134
161
  const key = `${problem.path}
135
162
  ${problem.message}`;
136
163
  if (reported.has(key)) continue;
@@ -144,27 +171,41 @@ ${problem.message}`;
144
171
  clearTimeout(timer);
145
172
  timer = setTimeout(() => void reload(context).catch(() => void 0), options.reloadMs);
146
173
  };
147
- const watched = await env.watch(
148
- [
149
- { path: `${home}/.pi/agent/skills`, recursive: true },
150
- { path: `${home}/.pi/agent/prompts` },
151
- { path: `${home}/.agents/skills`, recursive: true },
152
- { path: GLOBAL_DIR, recursive: true }
153
- ],
154
- schedule,
155
- context
156
- );
157
- const watcher = watched.ok ? watched.value : void 0;
174
+ let watcher;
175
+ let seen = "";
176
+ const watch = async (using) => {
177
+ const extra = options.people ? userRoots(homes()).map((root) => ({ path: root.dir, recursive: true })) : [
178
+ { path: `${dir}/skills`, recursive: true },
179
+ { path: `${home}/.agents/skills`, recursive: true }
180
+ ];
181
+ const key = JSON.stringify(extra);
182
+ if (key === seen) return;
183
+ seen = key;
184
+ await watcher?.close(using);
185
+ const watched = await env.watch(
186
+ [{ path: GLOBAL_DIR, recursive: true }, { path: `${dir}/prompts` }, ...extra],
187
+ schedule,
188
+ using
189
+ );
190
+ watcher = watched.ok ? watched.value : void 0;
191
+ };
192
+ await watch(context);
158
193
  return {
159
194
  commands,
160
195
  skills: () => current,
161
196
  dirs: () => current.map((skill) => skill.baseDir),
162
197
  templates: () => templates,
163
- async expand(text, using) {
164
- const expanded = await expandSkillCommand(text, current, env, using);
198
+ async expand(text, using, conversationId) {
199
+ const at = people?.homeOf(conversationId);
200
+ const files = at ? await people?.envOf(at) : env;
201
+ if (!files) return text;
202
+ const expanded = await expandSkillCommand(text, mine(conversationId), files, using);
165
203
  return expanded === text ? expandPromptTemplate(text, templates) : expanded;
166
204
  },
167
- reload,
205
+ async reload(using) {
206
+ await watch(using);
207
+ await reload(using);
208
+ },
168
209
  async close(using) {
169
210
  clearTimeout(timer);
170
211
  await watcher?.close(using);
@@ -184,6 +225,7 @@ export {
184
225
  commandsOf,
185
226
  piResources,
186
227
  problemLick,
187
- setupSkills
228
+ setupSkills,
229
+ userRoots
188
230
  };
189
231
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/skills/index.ts"],
4
- "sourcesContent": ["import { type Context, type ReplicatedState, replicatedState } from '@earendil-works/chord';\nimport {\n defineExtension,\n type Extension,\n type Registry,\n section,\n} from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport type { Licks } from '../licks/licks.ts';\nimport { manifestDirs } from '../roles/roles.ts';\nimport type { Command } from '../services.ts';\nimport {\n type Diagnostic,\n expandSkillCommand,\n formatSkillsForPrompt,\n loadSkills,\n type Skill,\n type SkillRoot,\n} from './skills.ts';\nimport { expandPromptTemplate, loadTemplates, type PromptTemplate } from './templates.ts';\n\nexport const BUILTIN_SKILLS = [\n 'agent',\n 'browser',\n 'gelatiere',\n 'licks',\n 'memory',\n 'skill-authoring',\n 'sprinkles',\n 'welcome',\n];\nexport const BUILTIN_PROMPTS = ['parallel-review', 'review-loop'];\nexport const SKILLS_DIR = '/var/lib/slicc/agent/skills';\nexport const PROMPTS_DIR = '/var/lib/slicc/agent/prompts';\nexport const GLOBAL_DIR = '/usr/share/skills';\nexport const RELOAD_MS = 300;\n\nexport interface SkillsRuntime {\n readonly commands: ReplicatedState<Command[]>;\n skills(): readonly Skill[];\n dirs(): string[];\n templates(): readonly PromptTemplate[];\n expand(text: string, context: Context): Promise<string>;\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport interface SkillsAttach {\n env: ExecutionEnv;\n home: string;\n assets?: (path: string) => Promise<string>;\n reloadMs: number;\n}\n\nexport interface SkillsSetup {\n extension: Extension;\n attach(options: SkillsAttach, context: Context): Promise<SkillsRuntime>;\n}\n\ntype Manifest = { pi?: { skills?: unknown; prompts?: unknown } };\n\nexport function piResources(dir: string, text: string, kind: 'skills' | 'prompts'): string[] {\n let manifest: Manifest;\n try {\n manifest = JSON.parse(text) as Manifest;\n } catch {\n return [];\n }\n const listed = manifest.pi?.[kind];\n const paths = Array.isArray(listed) ? listed : typeof listed === 'string' ? [listed] : [];\n return paths\n .filter((path): path is string => typeof path === 'string')\n .map((path) => `${dir}/${path.replace(/^\\.\\//, '').replace(/\\/$/, '')}`);\n}\n\nasync function packaged(env: ExecutionEnv, kind: 'skills' | 'prompts', context: Context) {\n const out: string[] = [];\n for (const dir of await manifestDirs(env, '/node_modules', context)) {\n const read = await env.readTextFile(`${dir}/package.json`, context);\n if (read.ok) out.push(...piResources(dir, read.value, kind));\n }\n return out;\n}\n\nasync function installBuiltins(\n env: ExecutionEnv,\n assets: (path: string) => Promise<string>,\n context: Context\n): Promise<void> {\n await env.remove(SKILLS_DIR, { recursive: true, force: true }, context);\n await env.remove(PROMPTS_DIR, { recursive: true, force: true }, context);\n for (const name of BUILTIN_SKILLS) {\n const text = await assets(`packages/vfs-root/skills/${name}/SKILL.md`).catch(() => undefined);\n if (text === undefined) continue;\n await env.createDir(`${SKILLS_DIR}/${name}`, { recursive: true }, context);\n await env.writeFile(`${SKILLS_DIR}/${name}/SKILL.md`, text, context);\n }\n await env.createDir(PROMPTS_DIR, { recursive: true }, context);\n for (const name of BUILTIN_PROMPTS) {\n const text = await assets(`packages/vfs-root/prompts/${name}.md`).catch(() => undefined);\n if (text !== undefined) await env.writeFile(`${PROMPTS_DIR}/${name}.md`, text, context);\n }\n}\n\nexport function problemLick(problem: Diagnostic, home: string) {\n const path = problem.path.startsWith(`${home}/`)\n ? `~${problem.path.slice(home.length)}`\n : problem.path;\n return {\n channel: 'fswatch' as const,\n source: `skills:${path}`,\n title: `Skill or prompt template problem: ${path}`,\n text: problem.message,\n body: 'The file is used with this problem, or skipped if it has no description.\\nThe rules are in the slicc-agent README.',\n target: 'cone' as const,\n severity: 'warn' as const,\n eventId: `${path}\\n${problem.message}`,\n };\n}\n\nexport const SECRETS_ACTION: Command = {\n name: 'secrets',\n description: 'List the secrets the agent can use, and where each works',\n kind: 'action',\n};\n\nexport function commandsOf(\n skills: readonly Skill[],\n templates: readonly PromptTemplate[]\n): Command[] {\n return [\n ...templates.map((template) => ({\n name: template.name,\n description: template.description,\n kind: 'prompt' as const,\n })),\n ...skills.map((skill) => ({\n name: `skill:${skill.name}`,\n description: skill.description,\n kind: 'skill' as const,\n })),\n SECRETS_ACTION,\n ];\n}\n\nexport function setupSkills(registry: Registry, licks: Licks): SkillsSetup {\n let current: Skill[] = [];\n const extension = defineExtension({\n name: 'slicc-skills',\n sections: [section('skills', async () => formatSkillsForPrompt(current) || undefined)],\n });\n registry.install(extension);\n return {\n extension,\n async attach(options, context) {\n const { env, home } = options;\n if (options.assets)\n await installBuiltins(env, options.assets, context).catch(() => undefined);\n await env.exec(`mkdir -p ${GLOBAL_DIR} && chmod 755 ${GLOBAL_DIR}`, undefined, context);\n let templates: PromptTemplate[] = [];\n const commands = replicatedState<Command[]>([]);\n const reported = new Set<string>();\n const reload = async (using: Context) => {\n const roots: SkillRoot[] = [\n { dir: `${home}/.pi/agent/skills`, source: 'user' },\n { dir: `${home}/.agents/skills`, source: 'user' },\n { dir: GLOBAL_DIR, source: 'global' },\n ...(await packaged(env, 'skills', using)).map((dir) => ({\n dir,\n source: 'package' as const,\n })),\n { dir: SKILLS_DIR, source: 'builtin' },\n ];\n const loaded = await loadSkills(env, roots, using);\n const prompts = await loadTemplates(\n env,\n [`${home}/.pi/agent/prompts`, ...(await packaged(env, 'prompts', using)), PROMPTS_DIR],\n using\n );\n current = loaded.skills;\n templates = prompts.templates;\n commands.replace(using, commandsOf(current, templates));\n for (const problem of [...loaded.diagnostics, ...prompts.diagnostics]) {\n const key = `${problem.path}\\n${problem.message}`;\n if (reported.has(key)) continue;\n reported.add(key);\n await licks.deliver(problemLick(problem, home), using).catch(() => undefined);\n }\n };\n await reload(context);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const watched = await env.watch(\n [\n { path: `${home}/.pi/agent/skills`, recursive: true },\n { path: `${home}/.pi/agent/prompts` },\n { path: `${home}/.agents/skills`, recursive: true },\n { path: GLOBAL_DIR, recursive: true },\n ],\n schedule,\n context\n );\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n return {\n commands,\n skills: () => current,\n dirs: () => current.map((skill) => skill.baseDir),\n templates: () => templates,\n async expand(text, using) {\n const expanded = await expandSkillCommand(text, current, env, using);\n return expanded === text ? expandPromptTemplate(text, templates) : expanded;\n },\n reload,\n async close(using) {\n clearTimeout(timer);\n await watcher?.close(using);\n },\n };\n },\n };\n}\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["import { type Context, type ReplicatedState, replicatedState } from '@earendil-works/chord';\nimport {\n defineExtension,\n type Extension,\n type Registry,\n section,\n} from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport type { Licks } from '../licks/licks.ts';\nimport { PACKAGE_ROOTS, type PiPackages, piPackages } from '../roles/roles.ts';\nimport type { Command } from '../services.ts';\nimport {\n type Diagnostic,\n expandSkillCommand,\n formatSkillsForPrompt,\n loadSkills,\n type Skill,\n type SkillRoot,\n} from './skills.ts';\nimport { expandPromptTemplate, loadTemplates, type PromptTemplate } from './templates.ts';\n\nexport const BUILTIN_SKILLS = [\n 'agent',\n 'browser',\n 'gelatiere',\n 'licks',\n 'memory',\n 'skill-authoring',\n 'sprinkles',\n 'welcome',\n];\nexport const BUILTIN_PROMPTS = ['parallel-review', 'review-loop'];\nexport const SKILLS_DIR = '/var/lib/slicc/agent/skills';\nexport const PROMPTS_DIR = '/var/lib/slicc/agent/prompts';\nexport const GLOBAL_DIR = '/usr/share/skills';\nexport const RELOAD_MS = 300;\n\nexport interface SkillsRuntime {\n readonly commands: ReplicatedState<Command[]>;\n skills(): readonly Skill[];\n dirs(): string[];\n templates(): readonly PromptTemplate[];\n expand(text: string, context: Context, conversationId?: number): Promise<string>;\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport interface SkillsAttach {\n env: ExecutionEnv;\n home: string;\n agentDir?: string;\n assets?: (path: string) => Promise<string>;\n reloadMs: number;\n people?: SkillPeople;\n}\n\nexport interface SkillPeople {\n homes(): readonly string[];\n homeOf(conversationId: number | undefined): string | undefined;\n envOf(home: string): Promise<ExecutionEnv | undefined>;\n}\n\nexport function userRoots(homes: readonly string[]): SkillRoot[] {\n return homes.flatMap((home) => [\n { dir: `${home}/.pi/agent/skills`, source: 'user' as const },\n { dir: `${home}/.agents/skills`, source: 'user' as const },\n ]);\n}\n\nexport interface SkillsSetup {\n extension: Extension;\n attach(options: SkillsAttach, context: Context): Promise<SkillsRuntime>;\n}\n\ntype Manifest = { pi?: { skills?: unknown; prompts?: unknown } };\n\nexport function piResources(dir: string, text: string, kind: 'skills' | 'prompts'): string[] {\n let manifest: Manifest;\n try {\n manifest = JSON.parse(text) as Manifest;\n } catch {\n return [];\n }\n const listed = manifest.pi?.[kind];\n const paths = Array.isArray(listed) ? listed : typeof listed === 'string' ? [listed] : [];\n return paths\n .filter((path): path is string => typeof path === 'string')\n .map((path) => `${dir}/${path.replace(/^\\.\\//, '').replace(/\\/$/, '')}`);\n}\n\nfunction packaged(found: PiPackages, kind: 'skills' | 'prompts') {\n return found.packages.flatMap(({ dir, manifest }) => piResources(dir, manifest, kind));\n}\n\nasync function installBuiltins(\n env: ExecutionEnv,\n assets: (path: string) => Promise<string>,\n context: Context\n): Promise<void> {\n await env.remove(SKILLS_DIR, { recursive: true, force: true }, context);\n await env.remove(PROMPTS_DIR, { recursive: true, force: true }, context);\n for (const name of BUILTIN_SKILLS) {\n const text = await assets(`packages/vfs-root/skills/${name}/SKILL.md`).catch(() => undefined);\n if (text === undefined) continue;\n await env.createDir(`${SKILLS_DIR}/${name}`, { recursive: true }, context);\n await env.writeFile(`${SKILLS_DIR}/${name}/SKILL.md`, text, context);\n }\n await env.createDir(PROMPTS_DIR, { recursive: true }, context);\n for (const name of BUILTIN_PROMPTS) {\n const text = await assets(`packages/vfs-root/prompts/${name}.md`).catch(() => undefined);\n if (text !== undefined) await env.writeFile(`${PROMPTS_DIR}/${name}.md`, text, context);\n }\n}\n\nexport function problemLick(problem: Diagnostic, home: string) {\n const path = problem.path.startsWith(`${home}/`)\n ? `~${problem.path.slice(home.length)}`\n : problem.path;\n return {\n channel: 'fswatch' as const,\n source: `skills:${path}`,\n title: `Skill or prompt template problem: ${path}`,\n text: problem.message,\n body: 'The file is used with this problem, or skipped if it has no description.\\nThe rules are in the slicc-agent README.',\n target: 'cone' as const,\n severity: 'warn' as const,\n eventId: `${path}\\n${problem.message}`,\n };\n}\n\nexport const SECRETS_ACTION: Command = {\n name: 'secrets',\n description: 'List the secrets the agent can use, and where each works',\n kind: 'action',\n};\n\nexport function commandsOf(\n skills: readonly Skill[],\n templates: readonly PromptTemplate[]\n): Command[] {\n return [\n ...templates.map((template) => ({\n name: template.name,\n description: template.description,\n kind: 'prompt' as const,\n })),\n ...skills.map((skill) => ({\n name: `skill:${skill.name}`,\n description: skill.description,\n kind: 'skill' as const,\n })),\n SECRETS_ACTION,\n ];\n}\n\nexport function setupSkills(registry: Registry, licks: Licks): SkillsSetup {\n let current: Skill[] = [];\n let own = new Map<string, Skill[]>();\n let people: SkillPeople | undefined;\n const mine = (conversationId: number | undefined) => {\n const at = people?.homeOf(conversationId);\n return (at ? own.get(at) : undefined) ?? current;\n };\n const extension = defineExtension({\n name: 'slicc-skills',\n sections: [\n section('skills', async (input) => {\n return formatSkillsForPrompt(mine(input.conversationId)) || undefined;\n }),\n ],\n });\n registry.install(extension);\n return {\n extension,\n async attach(options, context) {\n const { env, home } = options;\n const dir = options.agentDir ?? `${home}/.pi/agent`;\n people = options.people;\n const homes = () => options.people?.homes() ?? [];\n if (options.assets)\n await installBuiltins(env, options.assets, context).catch(() => undefined);\n await env.exec(`mkdir -p ${GLOBAL_DIR} && chmod 755 ${GLOBAL_DIR}`, undefined, context);\n let templates: PromptTemplate[] = [];\n const commands = replicatedState<Command[]>([]);\n const reported = new Set<string>();\n const reload = async (using: Context) => {\n const packages = await piPackages(env, PACKAGE_ROOTS, using);\n const roots: SkillRoot[] = [\n ...(options.people\n ? [{ dir: GLOBAL_DIR, source: 'global' as const }]\n : [\n { dir: `${dir}/skills`, source: 'user' as const },\n { dir: `${home}/.agents/skills`, source: 'user' as const },\n { dir: GLOBAL_DIR, source: 'global' as const },\n ]),\n { dir: SKILLS_DIR, source: 'builtin' },\n ...packaged(packages, 'skills').map((dir) => ({ dir, source: 'package' as const })),\n ];\n const loaded = await loadSkills(env, roots, using);\n const homesLoaded = new Map<string, Skill[]>();\n for (const at of homes()) {\n const files = await options.people?.envOf(at);\n if (!files) continue;\n const found = await loadSkills(files, [...userRoots([at]), ...roots], using);\n homesLoaded.set(at, found.skills);\n loaded.diagnostics.push(...found.diagnostics);\n }\n own = homesLoaded;\n const prompts = await loadTemplates(\n env,\n [`${dir}/prompts`, PROMPTS_DIR, ...packaged(packages, 'prompts')],\n using\n );\n current = loaded.skills;\n templates = prompts.templates;\n commands.replace(using, commandsOf(current, templates));\n for (const problem of [\n ...packages.skipped,\n ...loaded.diagnostics,\n ...prompts.diagnostics,\n ]) {\n const key = `${problem.path}\\n${problem.message}`;\n if (reported.has(key)) continue;\n reported.add(key);\n await licks.deliver(problemLick(problem, home), using).catch(() => undefined);\n }\n };\n await reload(context);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n let watcher: FileWatcher | undefined;\n let seen = '';\n const watch = async (using: Context) => {\n const extra = options.people\n ? userRoots(homes()).map((root) => ({ path: root.dir, recursive: true }))\n : [\n { path: `${dir}/skills`, recursive: true },\n { path: `${home}/.agents/skills`, recursive: true },\n ];\n const key = JSON.stringify(extra);\n if (key === seen) return;\n seen = key;\n await watcher?.close(using);\n const watched = await env.watch(\n [{ path: GLOBAL_DIR, recursive: true }, { path: `${dir}/prompts` }, ...extra],\n schedule,\n using\n );\n watcher = watched.ok ? watched.value : undefined;\n };\n await watch(context);\n return {\n commands,\n skills: () => current,\n dirs: () => current.map((skill) => skill.baseDir),\n templates: () => templates,\n async expand(text, using, conversationId) {\n const at = people?.homeOf(conversationId);\n const files = at ? await people?.envOf(at) : env;\n if (!files) return text;\n const expanded = await expandSkillCommand(text, mine(conversationId), files, using);\n return expanded === text ? expandPromptTemplate(text, templates) : expanded;\n },\n async reload(using) {\n await watch(using);\n await reload(using);\n },\n async close(using) {\n clearTimeout(timer);\n await watcher?.close(using);\n },\n };\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["dir"]
7
7
  }
@@ -12,37 +12,65 @@ import {
12
12
  SUGGESTIONS,
13
13
  SUGGESTIONS_DIR,
14
14
  SUGGESTIONS_STORE,
15
+ sharedPaths,
15
16
  sprinkleFiles,
16
17
  WELCOME,
17
18
  WELCOME_PROFILE,
18
19
  WELCOMED
19
20
  } from "./sprinkles.js";
21
+ const BUILTIN = /* @__PURE__ */ new Set([WELCOME, SUGGESTIONS]);
20
22
  function targetOf(agentId) {
21
23
  return agentId.startsWith("scoop:") ? agentId : `cone:${agentId}`;
22
24
  }
25
+ function liveOwner(agents, owner) {
26
+ if (!owner) return void 0;
27
+ const state = agents.state();
28
+ if (state.cones[owner]) return owner;
29
+ const scoop = state.scoops[owner];
30
+ return scoop && live(scoop) ? owner : void 0;
31
+ }
23
32
  function ownerOf(agents, owner) {
24
33
  const state = agents.state();
25
34
  if (owner && state.cones[owner]) return owner;
26
35
  const scoop = owner ? state.scoops[owner] : void 0;
27
36
  return scoop && live(scoop) ? owner : agents.activeCone();
28
37
  }
29
- function entry(name, html, agentId) {
30
- return { id: name, name, ...describe(name, html), agentId, html };
38
+ function entry(name, html, agentId, id = name) {
39
+ return { id, name, ...describe(name, html), agentId, html };
40
+ }
41
+ function placesOf(options, dir) {
42
+ return options.places?.() ?? [{ dir, cone: "" }];
43
+ }
44
+ async function filesIn(options, dir, reserved, owned, using, found = {}) {
45
+ const { env, agents } = options;
46
+ const out = [];
47
+ for (const place of placesOf(options, dir))
48
+ for (const { name, path } of await sprinkleFiles(env, place.dir, using)) {
49
+ const id = place.cone ? `${place.cone}/${name}` : name;
50
+ if (reserved.has(name) || out.some((item) => item.id === id)) continue;
51
+ const read = await env.readTextFile(path, using);
52
+ if (place.cone && !owned[id]) found[id] = place.cone;
53
+ const owner = owned[id] ?? (place.cone || void 0);
54
+ if (read.ok) out.push(entry(name, read.value, ownerOf(agents, owner), id));
55
+ }
56
+ return out;
31
57
  }
32
58
  function reloader(options, dir, builtins, sprinkles, owners) {
33
59
  const { env, agents } = options;
34
60
  const { welcome, suggestions } = builtins;
35
61
  return async (using) => {
36
62
  const owned = await owners(using);
37
- const stored = suggestions === void 0 ? void 0 : await env.exists(SUGGESTIONS_STORE, using);
63
+ const store = options.shared ? sharedPaths(options.shared).suggestions : SUGGESTIONS_STORE;
64
+ const stored = suggestions === void 0 ? void 0 : await env.exists(store, using);
38
65
  const offered = stored?.ok === true && stored.value;
39
66
  const reserved = /* @__PURE__ */ new Set([WELCOME, ...offered ? [SUGGESTIONS] : []]);
40
- const out = [];
41
- for (const { name, path } of await sprinkleFiles(env, dir, using)) {
42
- if (reserved.has(name)) continue;
43
- const read = await env.readTextFile(path, using);
44
- if (read.ok) out.push(entry(name, read.value, ownerOf(agents, owned[name])));
45
- }
67
+ const found = {};
68
+ const out = await filesIn(options, dir, reserved, owned, using, found);
69
+ if (Object.keys(found).length)
70
+ await options.harness.commit(async (tx) => {
71
+ const doc = await tx.doc(SprinklesDoc);
72
+ for (const [id, cone] of Object.entries(found)) doc.owners[id] ??= cone;
73
+ }, using);
46
74
  if (offered)
47
75
  out.push(entry(SUGGESTIONS, suggestions, ownerOf(agents, owned[SUGGESTIONS])));
48
76
  if (welcome !== void 0)
@@ -53,6 +81,36 @@ function reloader(options, dir, builtins, sprinkles, owners) {
53
81
  sprinkles.replace(using, out);
54
82
  };
55
83
  }
84
+ const NO_OWNER = "this sprinkle has no owner, so it can only be shown";
85
+ async function ownedRead(files, asked, home, using) {
86
+ const real = await files.canonicalPath(asked, using);
87
+ const path = real.ok ? homePath(real.value, home, home) : asked;
88
+ return {
89
+ files,
90
+ path,
91
+ async check() {
92
+ const again = await files.canonicalPath(path, using);
93
+ if (again.ok && again.value !== path)
94
+ throw new Error(`${asked} changed while it was read; read it again`);
95
+ }
96
+ };
97
+ }
98
+ function readerFor(options, owners, inside) {
99
+ const { env, agents } = options;
100
+ const none = async () => void 0;
101
+ return async (id, path, using) => {
102
+ if (options.shared && BUILTIN.has(id))
103
+ return { files: env, path: homePath(path, options.shared, options.shared), check: none };
104
+ if (!options.places) return { files: env, path: await inside(path, using), check: none };
105
+ const owner = liveOwner(agents, (await owners(using))[id]);
106
+ if (!owner) throw new Error(NO_OWNER);
107
+ const agentId = owner.startsWith("scoop:") ? owner : `cone:${owner}`;
108
+ const home = options.homeOf?.(agentId);
109
+ const files = await options.envOf?.(agentId, using);
110
+ if (!home || !files) throw new Error(NO_OWNER);
111
+ return ownedRead(files, homePath(path, home, home), home, using);
112
+ };
113
+ }
56
114
  function setupSprinkles() {
57
115
  return {
58
116
  async attach(options, context) {
@@ -69,6 +127,8 @@ function setupSprinkles() {
69
127
  const real = await env.canonicalPath(asked, using);
70
128
  return real.ok ? homePath(real.value) : asked;
71
129
  };
130
+ const paths = options.shared ? sharedPaths(options.shared) : void 0;
131
+ const reader = readerFor(options, owners, inside);
72
132
  const offCone = agents.onCone(() => reload(context).catch(() => void 0));
73
133
  const find = (id) => sprinkles.value.find((item) => item.id === id);
74
134
  const post = async (id, agentId, using) => {
@@ -88,15 +148,28 @@ function setupSprinkles() {
88
148
  }, context);
89
149
  }
90
150
  let timer;
91
- const watched = await env.watch(
92
- [{ path: dir, recursive: true }, { path: SUGGESTIONS_DIR }],
93
- () => {
94
- clearTimeout(timer);
95
- timer = setTimeout(() => void reload(context).catch(() => void 0), options.reloadMs);
96
- },
97
- context
98
- );
99
- const watcher = watched.ok ? watched.value : void 0;
151
+ let watcher;
152
+ let seen = "";
153
+ const watch = async (using) => {
154
+ const dirs = placesOf(options, dir).map((place) => place.dir);
155
+ const key = dirs.join("\n");
156
+ if (key === seen) return;
157
+ seen = key;
158
+ await watcher?.close(using);
159
+ const watched = await env.watch(
160
+ [
161
+ ...dirs.map((path) => ({ path, recursive: true })),
162
+ { path: paths?.suggestionsDir ?? SUGGESTIONS_DIR }
163
+ ],
164
+ () => {
165
+ clearTimeout(timer);
166
+ timer = setTimeout(() => void reload(context).catch(() => void 0), options.reloadMs);
167
+ },
168
+ using
169
+ );
170
+ watcher = watched.ok ? watched.value : void 0;
171
+ };
172
+ await watch(context);
100
173
  return {
101
174
  sprinkles,
102
175
  async send(id, payload, using) {
@@ -104,16 +177,23 @@ function setupSprinkles() {
104
177
  if (!sprinkle) return { delivered: false };
105
178
  if (await options.intercept?.(id, payload, using)) return { delivered: true };
106
179
  if (id === WELCOME && payload.action === "onboarding-complete") {
107
- await env.writeFile(WELCOMED, `${(/* @__PURE__ */ new Date()).toISOString()}
108
- `, using);
180
+ await env.writeFile(
181
+ paths?.welcomed ?? WELCOMED,
182
+ `${(/* @__PURE__ */ new Date()).toISOString()}
183
+ `,
184
+ using
185
+ );
109
186
  if (payload.data && typeof payload.data === "object" && !Array.isArray(payload.data))
110
187
  await env.writeFile(
111
- WELCOME_PROFILE,
188
+ paths?.profile ?? WELCOME_PROFILE,
112
189
  `${JSON.stringify(payload.data, null, 2)}
113
190
  `,
114
191
  using
115
192
  );
116
193
  }
194
+ const owner = (await owners(using))[id];
195
+ if (options.places && !BUILTIN.has(id) && !liveOwner(agents, owner))
196
+ return { delivered: false };
117
197
  await licks.deliver(
118
198
  {
119
199
  channel: "sprinkle",
@@ -121,7 +201,7 @@ function setupSprinkles() {
121
201
  title: sprinkle.title,
122
202
  text: payload.action || "(no action)",
123
203
  ...payload.data === null ? {} : { body: JSON.stringify(payload.data, null, 2) },
124
- target: targetOf(ownerOf(agents, (await owners(using))[id]))
204
+ target: targetOf(ownerOf(agents, owner))
125
205
  },
126
206
  using
127
207
  );
@@ -131,12 +211,15 @@ function setupSprinkles() {
131
211
  if (!find(id)) throw new Error(`there is no sprinkle ${id}`);
132
212
  const handlers = {
133
213
  async readFile() {
134
- const read = await env.readTextFile(await inside(args[0], using), using);
214
+ const { files, path, check } = await reader(id, args[0], using);
215
+ const read = await files.readTextFile(path, using);
135
216
  if (!read.ok) throw new Error(read.error.message);
217
+ await check();
136
218
  return read.value;
137
219
  },
138
220
  async exists() {
139
- const found = await env.exists(await inside(args[0], using), using);
221
+ const { files, path } = await reader(id, args[0], using);
222
+ const found = await files.exists(path, using);
140
223
  return found.ok && found.value;
141
224
  },
142
225
  async getState() {
@@ -162,7 +245,10 @@ function setupSprinkles() {
162
245
  }, using);
163
246
  await reload(using);
164
247
  },
165
- reload,
248
+ async reload(using) {
249
+ await watch(using);
250
+ await reload(using);
251
+ },
166
252
  async close(using) {
167
253
  offCone();
168
254
  clearTimeout(timer);
@@ -173,6 +259,9 @@ function setupSprinkles() {
173
259
  };
174
260
  }
175
261
  export {
262
+ NO_OWNER,
263
+ liveOwner,
264
+ ownedRead,
176
265
  ownerOf,
177
266
  setupSprinkles
178
267
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/sprinkles/index.ts"],
4
- "sourcesContent": ["import {\n type Context,\n type JsonValue,\n type ReplicatedState,\n replicatedState,\n} from '@earendil-works/chord';\nimport type { Harness } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { type Agents, live } from '../agents.ts';\nimport type { LickTarget } from '../licks/lick.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport {\n describe,\n homePath,\n NO_EXEC,\n SPRINKLE_KIND,\n SPRINKLES_DIR,\n type Sprinkle,\n type SprinkleMethod,\n SprinklesDoc,\n SUGGESTIONS,\n SUGGESTIONS_DIR,\n SUGGESTIONS_STORE,\n sprinkleFiles,\n WELCOME,\n WELCOME_PROFILE,\n WELCOMED,\n} from './sprinkles.ts';\n\nexport interface SprinklePayload {\n action: string;\n data: JsonValue | null;\n target: string | null;\n}\n\nexport interface SprinklesRuntime {\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n send(id: string, payload: SprinklePayload, context: Context): Promise<{ delivered: boolean }>;\n call(id: string, method: string, args: readonly unknown[], context: Context): Promise<JsonValue>;\n claim(id: string, cone: string, context: Context): Promise<void>;\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport interface SprinklesAttach {\n harness: Harness;\n agents: Agents;\n licks: Licks;\n env: ExecutionEnv;\n assets?: (path: string) => Promise<string>;\n reloadMs: number;\n dir?: string;\n intercept?: (id: string, payload: SprinklePayload, context: Context) => Promise<boolean>;\n}\n\nfunction targetOf(agentId: string): LickTarget {\n return agentId.startsWith('scoop:') ? (agentId as LickTarget) : `cone:${agentId}`;\n}\n\nexport function ownerOf(agents: Agents, owner: string | undefined): string {\n const state = agents.state();\n if (owner && state.cones[owner]) return owner;\n const scoop = owner ? state.scoops[owner] : undefined;\n return scoop && live(scoop) ? (owner as string) : agents.activeCone();\n}\n\nfunction entry(name: string, html: string, agentId: string): Sprinkle {\n return { id: name, name, ...describe(name, html), agentId, html };\n}\n\nfunction reloader(\n options: SprinklesAttach,\n dir: string,\n builtins: { welcome: string | undefined; suggestions: string | undefined },\n sprinkles: ReplicatedState<Sprinkle[]> & { replace(context: Context, value: Sprinkle[]): void },\n owners: (context: Context) => Promise<Record<string, string>>\n) {\n const { env, agents } = options;\n const { welcome, suggestions } = builtins;\n return async (using: Context) => {\n const owned = await owners(using);\n const stored =\n suggestions === undefined ? undefined : await env.exists(SUGGESTIONS_STORE, using);\n const offered = stored?.ok === true && stored.value;\n const reserved = new Set([WELCOME, ...(offered ? [SUGGESTIONS] : [])]);\n const out: Sprinkle[] = [];\n for (const { name, path } of await sprinkleFiles(env, dir, using)) {\n if (reserved.has(name)) continue;\n const read = await env.readTextFile(path, using);\n if (read.ok) out.push(entry(name, read.value, ownerOf(agents, owned[name])));\n }\n if (offered)\n out.push(entry(SUGGESTIONS, suggestions as string, ownerOf(agents, owned[SUGGESTIONS])));\n if (welcome !== undefined)\n out.push({\n ...entry(WELCOME, welcome, ownerOf(agents, owned[WELCOME] ?? 'cone')),\n inline: true,\n });\n sprinkles.replace(using, out);\n };\n}\n\nexport function setupSprinkles() {\n return {\n async attach(options: SprinklesAttach, context: Context): Promise<SprinklesRuntime> {\n const { harness, agents, licks, env } = options;\n const dir = options.dir ?? SPRINKLES_DIR;\n const sprinkles = replicatedState<Sprinkle[]>([]);\n const builtin = (name: string) =>\n options.assets\n ? options.assets(`packages/vfs-root/sprinkles/${name}.shtml`).catch(() => undefined)\n : Promise.resolve(undefined);\n const welcome = await builtin(WELCOME);\n const suggestions = await builtin(SUGGESTIONS);\n const owners = async (using: Context) =>\n (await harness.snapshot(SprinklesDoc, using))?.owners ?? {};\n const reload = reloader(options, dir, { welcome, suggestions }, sprinkles, owners);\n const inside = async (path: unknown, using: Context) => {\n const asked = homePath(path);\n const real = await env.canonicalPath(asked, using);\n return real.ok ? homePath(real.value) : asked;\n };\n const offCone = agents.onCone(() => reload(context).catch(() => undefined));\n const find = (id: string) => sprinkles.value.find((item) => item.id === id);\n const post = async (id: string, agentId: string, using: Context) => {\n const conversation = await agents.conversation(agentId, using);\n if (!conversation) throw new Error(`there is no agent ${agentId}`);\n await conversation.submit(\n { type: 'write', entry: { kind: SPRINKLE_KIND, data: { sprinkle: id, at: Date.now() } } },\n using\n );\n };\n await reload(context);\n const state = await harness.snapshot(SprinklesDoc, context);\n if (welcome !== undefined && !state?.welcomed) {\n await post(WELCOME, 'cone', context);\n await harness.commit(async (tx) => {\n (await tx.doc(SprinklesDoc)).welcomed = true;\n }, context);\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n const watched = await env.watch(\n [{ path: dir, recursive: true }, { path: SUGGESTIONS_DIR }],\n () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n },\n context\n );\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n return {\n sprinkles,\n async send(id, payload, using) {\n const sprinkle = find(id);\n if (!sprinkle) return { delivered: false };\n if (await options.intercept?.(id, payload, using)) return { delivered: true };\n if (id === WELCOME && payload.action === 'onboarding-complete') {\n await env.writeFile(WELCOMED, `${new Date().toISOString()}\\n`, using);\n if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data))\n await env.writeFile(\n WELCOME_PROFILE,\n `${JSON.stringify(payload.data, null, 2)}\\n`,\n using\n );\n }\n await licks.deliver(\n {\n channel: 'sprinkle',\n source: `sprinkle:${id}`,\n title: sprinkle.title,\n text: payload.action || '(no action)',\n ...(payload.data === null ? {} : { body: JSON.stringify(payload.data, null, 2) }),\n target: targetOf(ownerOf(agents, (await owners(using))[id])),\n },\n using\n );\n return { delivered: true };\n },\n async call(id, method, args, using) {\n if (!find(id)) throw new Error(`there is no sprinkle ${id}`);\n const handlers: Record<SprinkleMethod, () => Promise<JsonValue>> = {\n async readFile() {\n const read = await env.readTextFile(await inside(args[0], using), using);\n if (!read.ok) throw new Error(read.error.message);\n return read.value;\n },\n async exists() {\n const found = await env.exists(await inside(args[0], using), using);\n return found.ok && found.value;\n },\n async getState() {\n return (await harness.snapshot(SprinklesDoc, using))?.state[id] ?? null;\n },\n async setState() {\n await harness.commit(async (tx) => {\n (await tx.doc(SprinklesDoc)).state[id] = (args[0] ?? null) as JsonValue;\n }, using);\n return null;\n },\n };\n const handler = handlers[method as SprinkleMethod];\n if (!handler) throw new Error(NO_EXEC);\n return handler();\n },\n async claim(id, cone, using) {\n await reload(using);\n if (id === SUGGESTIONS || !find(id) || (await owners(using))[id]) return;\n await harness.commit(async (tx) => {\n const doc = await tx.doc(SprinklesDoc);\n doc.owners[id] ??= cone;\n }, using);\n await reload(using);\n },\n reload,\n async close(using) {\n offCone();\n clearTimeout(timer);\n await watcher?.close(using);\n },\n };\n },\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n type Context,\n type JsonValue,\n type ReplicatedState,\n replicatedState,\n} from '@earendil-works/chord';\nimport type { Harness } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { type Agents, live } from '../agents.ts';\nimport type { LickTarget } from '../licks/lick.ts';\nimport type { Licks } from '../licks/licks.ts';\nimport {\n describe,\n homePath,\n NO_EXEC,\n SPRINKLE_KIND,\n SPRINKLES_DIR,\n type Sprinkle,\n type SprinkleMethod,\n SprinklesDoc,\n SUGGESTIONS,\n SUGGESTIONS_DIR,\n SUGGESTIONS_STORE,\n sharedPaths,\n sprinkleFiles,\n WELCOME,\n WELCOME_PROFILE,\n WELCOMED,\n} from './sprinkles.ts';\n\nexport interface SprinklePayload {\n action: string;\n data: JsonValue | null;\n target: string | null;\n}\n\nexport interface SprinklesRuntime {\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n send(id: string, payload: SprinklePayload, context: Context): Promise<{ delivered: boolean }>;\n call(id: string, method: string, args: readonly unknown[], context: Context): Promise<JsonValue>;\n claim(id: string, cone: string, context: Context): Promise<void>;\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport interface SprinklesAttach {\n harness: Harness;\n agents: Agents;\n licks: Licks;\n env: ExecutionEnv;\n assets?: (path: string) => Promise<string>;\n reloadMs: number;\n dir?: string;\n intercept?: (id: string, payload: SprinklePayload, context: Context) => Promise<boolean>;\n places?: () => readonly SprinklePlace[];\n envOf?: (agentId: string, context: Context) => Promise<ExecutionEnv | undefined>;\n homeOf?: (agentId: string) => string | undefined;\n shared?: string;\n}\n\nconst BUILTIN = new Set([WELCOME, SUGGESTIONS]);\n\nexport interface SprinklePlace {\n dir: string;\n cone: string;\n}\n\nfunction targetOf(agentId: string): LickTarget {\n return agentId.startsWith('scoop:') ? (agentId as LickTarget) : `cone:${agentId}`;\n}\n\nexport function liveOwner(agents: Agents, owner: string | undefined): string | undefined {\n if (!owner) return undefined;\n const state = agents.state();\n if (state.cones[owner]) return owner;\n const scoop = state.scoops[owner];\n return scoop && live(scoop) ? owner : undefined;\n}\n\nexport function ownerOf(agents: Agents, owner: string | undefined): string {\n const state = agents.state();\n if (owner && state.cones[owner]) return owner;\n const scoop = owner ? state.scoops[owner] : undefined;\n return scoop && live(scoop) ? (owner as string) : agents.activeCone();\n}\n\nfunction entry(name: string, html: string, agentId: string, id = name): Sprinkle {\n return { id, name, ...describe(name, html), agentId, html };\n}\n\nfunction placesOf(options: SprinklesAttach, dir: string): readonly SprinklePlace[] {\n return options.places?.() ?? [{ dir, cone: '' }];\n}\n\nasync function filesIn(\n options: SprinklesAttach,\n dir: string,\n reserved: ReadonlySet<string>,\n owned: Record<string, string>,\n using: Context,\n found: Record<string, string> = {}\n): Promise<Sprinkle[]> {\n const { env, agents } = options;\n const out: Sprinkle[] = [];\n for (const place of placesOf(options, dir))\n for (const { name, path } of await sprinkleFiles(env, place.dir, using)) {\n const id = place.cone ? `${place.cone}/${name}` : name;\n if (reserved.has(name) || out.some((item) => item.id === id)) continue;\n const read = await env.readTextFile(path, using);\n if (place.cone && !owned[id]) found[id] = place.cone;\n const owner = owned[id] ?? (place.cone || undefined);\n if (read.ok) out.push(entry(name, read.value, ownerOf(agents, owner), id));\n }\n return out;\n}\n\nfunction reloader(\n options: SprinklesAttach,\n dir: string,\n builtins: { welcome: string | undefined; suggestions: string | undefined },\n sprinkles: ReplicatedState<Sprinkle[]> & { replace(context: Context, value: Sprinkle[]): void },\n owners: (context: Context) => Promise<Record<string, string>>\n) {\n const { env, agents } = options;\n const { welcome, suggestions } = builtins;\n return async (using: Context) => {\n const owned = await owners(using);\n const store = options.shared ? sharedPaths(options.shared).suggestions : SUGGESTIONS_STORE;\n const stored = suggestions === undefined ? undefined : await env.exists(store, using);\n const offered = stored?.ok === true && stored.value;\n const reserved = new Set([WELCOME, ...(offered ? [SUGGESTIONS] : [])]);\n const found: Record<string, string> = {};\n const out = await filesIn(options, dir, reserved, owned, using, found);\n if (Object.keys(found).length)\n await options.harness.commit(async (tx) => {\n const doc = await tx.doc(SprinklesDoc);\n for (const [id, cone] of Object.entries(found)) doc.owners[id] ??= cone;\n }, using);\n if (offered)\n out.push(entry(SUGGESTIONS, suggestions as string, ownerOf(agents, owned[SUGGESTIONS])));\n if (welcome !== undefined)\n out.push({\n ...entry(WELCOME, welcome, ownerOf(agents, owned[WELCOME] ?? 'cone')),\n inline: true,\n });\n sprinkles.replace(using, out);\n };\n}\n\nexport const NO_OWNER = 'this sprinkle has no owner, so it can only be shown';\n\nexport async function ownedRead(\n files: ExecutionEnv,\n asked: string,\n home: string,\n using: Context\n): Promise<{ files: ExecutionEnv; path: string; check: () => Promise<void> }> {\n const real = await files.canonicalPath(asked, using);\n const path = real.ok ? homePath(real.value, home, home) : asked;\n return {\n files,\n path,\n async check() {\n const again = await files.canonicalPath(path, using);\n if (again.ok && again.value !== path)\n throw new Error(`${asked} changed while it was read; read it again`);\n },\n };\n}\n\nfunction readerFor(\n options: SprinklesAttach,\n owners: (context: Context) => Promise<Record<string, string>>,\n inside: (path: unknown, context: Context) => Promise<string>\n) {\n const { env, agents } = options;\n const none = async () => undefined;\n return async (id: string, path: unknown, using: Context) => {\n if (options.shared && BUILTIN.has(id))\n return { files: env, path: homePath(path, options.shared, options.shared), check: none };\n if (!options.places) return { files: env, path: await inside(path, using), check: none };\n const owner = liveOwner(agents, (await owners(using))[id]);\n if (!owner) throw new Error(NO_OWNER);\n const agentId = owner.startsWith('scoop:') ? owner : `cone:${owner}`;\n const home = options.homeOf?.(agentId);\n const files = await options.envOf?.(agentId, using);\n if (!home || !files) throw new Error(NO_OWNER);\n return ownedRead(files, homePath(path, home, home), home, using);\n };\n}\n\nexport function setupSprinkles() {\n return {\n async attach(options: SprinklesAttach, context: Context): Promise<SprinklesRuntime> {\n const { harness, agents, licks, env } = options;\n const dir = options.dir ?? SPRINKLES_DIR;\n const sprinkles = replicatedState<Sprinkle[]>([]);\n const builtin = (name: string) =>\n options.assets\n ? options.assets(`packages/vfs-root/sprinkles/${name}.shtml`).catch(() => undefined)\n : Promise.resolve(undefined);\n const welcome = await builtin(WELCOME);\n const suggestions = await builtin(SUGGESTIONS);\n const owners = async (using: Context) =>\n (await harness.snapshot(SprinklesDoc, using))?.owners ?? {};\n const reload = reloader(options, dir, { welcome, suggestions }, sprinkles, owners);\n const inside = async (path: unknown, using: Context) => {\n const asked = homePath(path);\n const real = await env.canonicalPath(asked, using);\n return real.ok ? homePath(real.value) : asked;\n };\n const paths = options.shared ? sharedPaths(options.shared) : undefined;\n const reader = readerFor(options, owners, inside);\n const offCone = agents.onCone(() => reload(context).catch(() => undefined));\n const find = (id: string) => sprinkles.value.find((item) => item.id === id);\n const post = async (id: string, agentId: string, using: Context) => {\n const conversation = await agents.conversation(agentId, using);\n if (!conversation) throw new Error(`there is no agent ${agentId}`);\n await conversation.submit(\n { type: 'write', entry: { kind: SPRINKLE_KIND, data: { sprinkle: id, at: Date.now() } } },\n using\n );\n };\n await reload(context);\n const state = await harness.snapshot(SprinklesDoc, context);\n if (welcome !== undefined && !state?.welcomed) {\n await post(WELCOME, 'cone', context);\n await harness.commit(async (tx) => {\n (await tx.doc(SprinklesDoc)).welcomed = true;\n }, context);\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n let watcher: FileWatcher | undefined;\n let seen = '';\n const watch = async (using: Context) => {\n const dirs = placesOf(options, dir).map((place) => place.dir);\n const key = dirs.join('\\n');\n if (key === seen) return;\n seen = key;\n await watcher?.close(using);\n const watched = await env.watch(\n [\n ...dirs.map((path) => ({ path, recursive: true })),\n { path: paths?.suggestionsDir ?? SUGGESTIONS_DIR },\n ],\n () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n },\n using\n );\n watcher = watched.ok ? watched.value : undefined;\n };\n await watch(context);\n return {\n sprinkles,\n async send(id, payload, using) {\n const sprinkle = find(id);\n if (!sprinkle) return { delivered: false };\n if (await options.intercept?.(id, payload, using)) return { delivered: true };\n if (id === WELCOME && payload.action === 'onboarding-complete') {\n await env.writeFile(\n paths?.welcomed ?? WELCOMED,\n `${new Date().toISOString()}\\n`,\n using\n );\n if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data))\n await env.writeFile(\n paths?.profile ?? WELCOME_PROFILE,\n `${JSON.stringify(payload.data, null, 2)}\\n`,\n using\n );\n }\n const owner = (await owners(using))[id];\n if (options.places && !BUILTIN.has(id) && !liveOwner(agents, owner))\n return { delivered: false };\n await licks.deliver(\n {\n channel: 'sprinkle',\n source: `sprinkle:${id}`,\n title: sprinkle.title,\n text: payload.action || '(no action)',\n ...(payload.data === null ? {} : { body: JSON.stringify(payload.data, null, 2) }),\n target: targetOf(ownerOf(agents, owner)),\n },\n using\n );\n return { delivered: true };\n },\n async call(id, method, args, using) {\n if (!find(id)) throw new Error(`there is no sprinkle ${id}`);\n const handlers: Record<SprinkleMethod, () => Promise<JsonValue>> = {\n async readFile() {\n const { files, path, check } = await reader(id, args[0], using);\n const read = await files.readTextFile(path, using);\n if (!read.ok) throw new Error(read.error.message);\n await check();\n return read.value;\n },\n async exists() {\n const { files, path } = await reader(id, args[0], using);\n const found = await files.exists(path, using);\n return found.ok && found.value;\n },\n async getState() {\n return (await harness.snapshot(SprinklesDoc, using))?.state[id] ?? null;\n },\n async setState() {\n await harness.commit(async (tx) => {\n (await tx.doc(SprinklesDoc)).state[id] = (args[0] ?? null) as JsonValue;\n }, using);\n return null;\n },\n };\n const handler = handlers[method as SprinkleMethod];\n if (!handler) throw new Error(NO_EXEC);\n return handler();\n },\n async claim(id, cone, using) {\n await reload(using);\n if (id === SUGGESTIONS || !find(id) || (await owners(using))[id]) return;\n await harness.commit(async (tx) => {\n const doc = await tx.doc(SprinklesDoc);\n doc.owners[id] ??= cone;\n }, using);\n await reload(using);\n },\n async reload(using) {\n await watch(using);\n await reload(using);\n },\n async close(using) {\n offCone();\n clearTimeout(timer);\n await watcher?.close(using);\n },\n };\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -41,17 +41,25 @@ async function sprinkleFiles(env, dir, context) {
41
41
  }
42
42
  return [...out].map(([name, path]) => ({ name, path }));
43
43
  }
44
- function homePath(path) {
44
+ function homePath(path, shared = "/home", root = "/home") {
45
45
  if (typeof path !== "string" || !path) throw new Error("a sprinkle reads files by absolute path");
46
- const mapped = path.startsWith("/shared/") ? `/home/${path.slice("/shared/".length)}` : path;
46
+ const mapped = path.startsWith("/shared/") ? `${shared}/${path.slice("/shared/".length)}` : path;
47
47
  const segments = mapped.split("/");
48
48
  if (segments.includes(".") || segments.includes(".."))
49
49
  throw new Error(`a sprinkle reads paths without . or .. segments, not ${path}`);
50
50
  const resolved = normalize(mapped);
51
- if (resolved !== "/home" && !resolved.startsWith("/home/"))
52
- throw new Error(`a sprinkle can only read files in /home, not ${path}`);
51
+ if (resolved !== root && !resolved.startsWith(`${root}/`))
52
+ throw new Error(`a sprinkle can only read files in ${root}, not ${path}`);
53
53
  return resolved;
54
54
  }
55
+ function sharedPaths(shared) {
56
+ return {
57
+ suggestionsDir: `${shared}/.gelatiere`,
58
+ suggestions: `${shared}/.gelatiere/suggestions.json`,
59
+ welcomed: `${shared}/.welcomed`,
60
+ profile: `${shared}/.welcome.json`
61
+ };
62
+ }
55
63
  const NO_EXEC = "sprinkles can't run commands in SLICC; send the agent a message with slicc.lick() and let it run them";
56
64
  export {
57
65
  DEFAULT_ICON,
@@ -68,6 +76,7 @@ export {
68
76
  WELCOME_PROFILE,
69
77
  describe,
70
78
  homePath,
79
+ sharedPaths,
71
80
  sprinkleFiles
72
81
  };
73
82
  //# sourceMappingURL=sprinkles.js.map