@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.
- package/README.md +38 -6
- package/bin/agent +1 -0
- package/dist/control.js +1 -1
- package/dist/control.js.map +2 -2
- package/dist/freezer/index.js +29 -8
- package/dist/freezer/index.js.map +2 -2
- package/dist/freezer/title.js +2 -2
- package/dist/freezer/title.js.map +2 -2
- package/dist/gelatiere/catalog.js +1 -1
- package/dist/gelatiere/catalog.js.map +2 -2
- package/dist/gelatiere/index.js +44 -21
- package/dist/gelatiere/index.js.map +2 -2
- package/dist/gelatiere/store.js +9 -5
- package/dist/gelatiere/store.js.map +2 -2
- package/dist/index.js.map +2 -2
- package/dist/kernel/accounts.js +141 -0
- package/dist/kernel/accounts.js.map +7 -0
- package/dist/kernel/agent-users.js +180 -0
- package/dist/kernel/agent-users.js.map +7 -0
- package/dist/kernel/env.js +9 -3
- package/dist/kernel/env.js.map +2 -2
- package/dist/kernel/users.js +16 -1
- package/dist/kernel/users.js.map +2 -2
- package/dist/layout.js +62 -0
- package/dist/layout.js.map +7 -0
- package/dist/licks/index.js.map +2 -2
- package/dist/licks/sources.js +69 -20
- package/dist/licks/sources.js.map +2 -2
- package/dist/main.js +70 -36
- package/dist/main.js.map +2 -2
- package/dist/mcp/index.js +5 -3
- package/dist/mcp/index.js.map +2 -2
- package/dist/mcp/paths.js +15 -0
- package/dist/mcp/paths.js.map +7 -0
- package/dist/mcp/runtime.js +16 -14
- package/dist/mcp/runtime.js.map +3 -3
- package/dist/memory/extract.js +2 -1
- package/dist/memory/extract.js.map +2 -2
- package/dist/memory/prompt.js +5 -1
- package/dist/memory/prompt.js.map +2 -2
- package/dist/memory/runtime.js +9 -1
- package/dist/memory/runtime.js.map +2 -2
- package/dist/roles/roles.js +54 -15
- package/dist/roles/roles.js.map +2 -2
- package/dist/scoops/cli.js +40 -19
- package/dist/scoops/cli.js.map +2 -2
- package/dist/scoops/extension.js +19 -15
- package/dist/scoops/extension.js.map +2 -2
- package/dist/scoops/index.js +82 -11
- package/dist/scoops/index.js.map +2 -2
- package/dist/scoops/requests.js +2 -1
- package/dist/scoops/requests.js.map +2 -2
- package/dist/scoops/service.js +45 -38
- package/dist/scoops/service.js.map +2 -2
- package/dist/scoops/sync.js +63 -13
- package/dist/scoops/sync.js.map +2 -2
- package/dist/skills/index.js +76 -34
- package/dist/skills/index.js.map +3 -3
- package/dist/sprinkles/index.js +114 -25
- package/dist/sprinkles/index.js.map +2 -2
- package/dist/sprinkles/sprinkles.js +13 -4
- package/dist/sprinkles/sprinkles.js.map +2 -2
- package/dist/types/freezer/index.d.ts +3 -1
- package/dist/types/freezer/title.d.ts +1 -1
- package/dist/types/gelatiere/index.d.ts +8 -0
- package/dist/types/gelatiere/store.d.ts +3 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/kernel/accounts.d.ts +29 -0
- package/dist/types/kernel/agent-users.d.ts +37 -0
- package/dist/types/kernel/env.d.ts +6 -0
- package/dist/types/kernel/users.d.ts +15 -0
- package/dist/types/layout.d.ts +26 -0
- package/dist/types/licks/index.d.ts +2 -1
- package/dist/types/licks/sources.d.ts +11 -1
- package/dist/types/mcp/paths.d.ts +3 -0
- package/dist/types/mcp/runtime.d.ts +3 -3
- package/dist/types/memory/prompt.d.ts +1 -0
- package/dist/types/memory/runtime.d.ts +1 -0
- package/dist/types/roles/roles.d.ts +14 -1
- package/dist/types/scoops/cli.d.ts +5 -0
- package/dist/types/scoops/extension.d.ts +6 -3
- package/dist/types/scoops/index.d.ts +9 -0
- package/dist/types/scoops/requests.d.ts +2 -0
- package/dist/types/scoops/service.d.ts +5 -3
- package/dist/types/scoops/sync.d.ts +9 -6
- package/dist/types/skills/index.d.ts +10 -2
- package/dist/types/sprinkles/index.d.ts +15 -0
- package/dist/types/sprinkles/sprinkles.d.ts +7 -1
- package/package.json +4 -4
package/dist/skills/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
defineExtension,
|
|
4
4
|
section
|
|
5
5
|
} from "@earendil-works/pi-durable";
|
|
6
|
-
import {
|
|
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
|
-
|
|
40
|
-
|
|
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: [
|
|
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:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
[`${
|
|
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 [
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
{ path: `${
|
|
152
|
-
{ path:
|
|
153
|
-
]
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
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
|
package/dist/skills/index.js.map
CHANGED
|
@@ -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": "AAAA,SAA6C,uBAAuB;AACpE;AAAA,EACE;AAAA,EAGA;AAAA,OACK;AAGP,SAAS,
|
|
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
|
}
|
package/dist/sprinkles/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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(
|
|
108
|
-
|
|
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,
|
|
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
|
|
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
|
|
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": "AAAA;AAAA,EAIE;AAAA,OACK;AAGP,SAAsB,YAAY;AAGlC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;
|
|
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/") ?
|
|
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 !==
|
|
52
|
-
throw new Error(`a sprinkle can only read files in
|
|
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
|