@ai-ecoverse/slicc-agent 3.8.0 → 3.10.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 (48) hide show
  1. package/README.md +69 -2
  2. package/dist/client.js +2 -1
  3. package/dist/client.js.map +2 -2
  4. package/dist/codemode/index.js +288 -0
  5. package/dist/codemode/index.js.map +7 -0
  6. package/dist/control.js +3 -2
  7. package/dist/control.js.map +2 -2
  8. package/dist/host.js +11 -2
  9. package/dist/host.js.map +2 -2
  10. package/dist/main.js +35 -4
  11. package/dist/main.js.map +2 -2
  12. package/dist/roles/frontmatter.js +61 -35
  13. package/dist/roles/frontmatter.js.map +3 -3
  14. package/dist/roles/roles.js +1 -0
  15. package/dist/roles/roles.js.map +2 -2
  16. package/dist/scoops/extension.js +11 -29
  17. package/dist/scoops/extension.js.map +2 -2
  18. package/dist/scoops/index.js +9 -4
  19. package/dist/scoops/index.js.map +2 -2
  20. package/dist/scoops/service.js.map +2 -2
  21. package/dist/services.js.map +2 -2
  22. package/dist/skills/index.js +172 -0
  23. package/dist/skills/index.js.map +7 -0
  24. package/dist/skills/skills.js +162 -0
  25. package/dist/skills/skills.js.map +7 -0
  26. package/dist/skills/templates.js +97 -0
  27. package/dist/skills/templates.js.map +7 -0
  28. package/dist/spectrum/agent-port.js +1 -1
  29. package/dist/spectrum/agent-port.js.map +2 -2
  30. package/dist/types/client.d.ts +2 -1
  31. package/dist/types/codemode/index.d.ts +37 -0
  32. package/dist/types/control.d.ts +2 -1
  33. package/dist/types/host.d.ts +2 -0
  34. package/dist/types/main.d.ts +2 -0
  35. package/dist/types/roles/roles.d.ts +1 -0
  36. package/dist/types/scoops/extension.d.ts +2 -8
  37. package/dist/types/scoops/index.d.ts +2 -1
  38. package/dist/types/scoops/service.d.ts +1 -0
  39. package/dist/types/services.d.ts +6 -0
  40. package/dist/types/skills/index.d.ts +45 -0
  41. package/dist/types/skills/skills.d.ts +32 -0
  42. package/dist/types/skills/templates.d.ts +18 -0
  43. package/package.json +4 -3
  44. package/packages/vfs-root/prompts/parallel-review.md +59 -0
  45. package/packages/vfs-root/prompts/review-loop.md +48 -0
  46. package/packages/vfs-root/skills/agent/SKILL.md +1 -1
  47. package/packages/vfs-root/skills/licks/SKILL.md +49 -0
  48. package/packages/vfs-root/skills/skill-authoring/SKILL.md +76 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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 = ['agent', 'licks', 'skill-authoring'];\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 COMPAT_DIR = '/workspace/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 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 ];\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 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: COMPAT_DIR, source: 'compat' },\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: COMPAT_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": []
7
+ }
@@ -0,0 +1,162 @@
1
+ import { parseFrontmatter } from "../roles/frontmatter.js";
2
+ const MAX_NAME_LENGTH = 64;
3
+ const MAX_DESCRIPTION_LENGTH = 1024;
4
+ function dirname(path) {
5
+ return path.slice(0, path.lastIndexOf("/")) || "/";
6
+ }
7
+ function basename(path) {
8
+ return path.slice(path.lastIndexOf("/") + 1);
9
+ }
10
+ function validateName(name) {
11
+ const errors = [];
12
+ if (name.length > MAX_NAME_LENGTH)
13
+ errors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);
14
+ if (!/^[a-z0-9-]+$/.test(name))
15
+ errors.push("name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)");
16
+ if (name.startsWith("-") || name.endsWith("-"))
17
+ errors.push("name must not start or end with a hyphen");
18
+ if (name.includes("--")) errors.push("name must not contain consecutive hyphens");
19
+ return errors;
20
+ }
21
+ function validateDescription(description) {
22
+ if (typeof description !== "string" || description.trim() === "")
23
+ return ["description is required"];
24
+ if (description.length > MAX_DESCRIPTION_LENGTH)
25
+ return [`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`];
26
+ return [];
27
+ }
28
+ function skillFromText(text, filePath, source) {
29
+ const declared = basename(filePath) === "SKILL.md";
30
+ const { fields, problems } = parseFrontmatter(text);
31
+ const diagnostics = [];
32
+ const description = typeof fields.description === "string" ? fields.description.trim() : "";
33
+ if (!declared && !description) return { skills: [], diagnostics };
34
+ for (const problem of problems.filter((item) => !item.includes("nested values")))
35
+ diagnostics.push({ path: filePath, message: problem });
36
+ for (const message of validateDescription(description))
37
+ diagnostics.push({ path: filePath, message });
38
+ const baseDir = dirname(filePath);
39
+ const name = typeof fields.name === "string" && fields.name || basename(baseDir);
40
+ for (const message of validateName(name)) diagnostics.push({ path: filePath, message });
41
+ if (!description) return { skills: [], diagnostics };
42
+ const skill = {
43
+ name,
44
+ description,
45
+ filePath,
46
+ baseDir,
47
+ source,
48
+ disableModelInvocation: fields["disable-model-invocation"] === true
49
+ };
50
+ return { skills: [skill], diagnostics };
51
+ }
52
+ async function loadFile(files, filePath, source, context) {
53
+ const read = await files.readTextFile(filePath, context);
54
+ if (!read.ok)
55
+ return { skills: [], diagnostics: [{ path: filePath, message: read.error.message }] };
56
+ return skillFromText(read.value, filePath, source);
57
+ }
58
+ async function loadSkillsFromDir(files, dir, source, context, root = true, visited = /* @__PURE__ */ new Set()) {
59
+ const out = { skills: [], diagnostics: [] };
60
+ const real = await files.canonicalPath(dir, context);
61
+ const key = real.ok ? real.value : dir;
62
+ if (visited.has(key)) return out;
63
+ visited.add(key);
64
+ const listed = await files.listDir(dir, context);
65
+ if (!listed.ok) return out;
66
+ const merge = (found) => {
67
+ out.skills.push(...found.skills);
68
+ out.diagnostics.push(...found.diagnostics);
69
+ };
70
+ if (listed.value.some((entry) => entry.name === "SKILL.md" && entry.kind !== "directory")) {
71
+ merge(await loadFile(files, `${dir}/SKILL.md`, source, context));
72
+ return out;
73
+ }
74
+ const entries = [...listed.value].sort((a, b) => a.name.localeCompare(b.name));
75
+ for (const entry of entries) {
76
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
77
+ const path = `${dir}/${entry.name}`;
78
+ if (entry.kind === "directory" || entry.kind === "symlink")
79
+ merge(await loadSkillsFromDir(files, path, source, context, false, visited));
80
+ if (entry.kind !== "directory" && root && entry.name.endsWith(".md"))
81
+ merge(await loadFile(files, path, source, context));
82
+ }
83
+ return out;
84
+ }
85
+ async function loadSkills(files, roots, context) {
86
+ const byName = /* @__PURE__ */ new Map();
87
+ const seen = /* @__PURE__ */ new Set();
88
+ const diagnostics = [];
89
+ const collisions = [];
90
+ for (const root of roots) {
91
+ const found = await loadSkillsFromDir(files, root.dir, root.source, context);
92
+ diagnostics.push(...found.diagnostics);
93
+ for (const skill of found.skills) {
94
+ if (seen.has(skill.filePath)) continue;
95
+ const existing = byName.get(skill.name);
96
+ if (existing)
97
+ collisions.push({
98
+ path: skill.filePath,
99
+ message: `name "${skill.name}" collision; ${existing.filePath} is used`
100
+ });
101
+ else {
102
+ byName.set(skill.name, skill);
103
+ seen.add(skill.filePath);
104
+ }
105
+ }
106
+ }
107
+ return { skills: [...byName.values()], diagnostics: [...diagnostics, ...collisions] };
108
+ }
109
+ function escapeXml(text) {
110
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
111
+ }
112
+ function formatSkillsForPrompt(skills) {
113
+ const visible = skills.filter((skill) => !skill.disableModelInvocation);
114
+ if (!visible.length) return "";
115
+ const lines = [
116
+ "The following skills provide specialized instructions for specific tasks.",
117
+ "Use the read tool to load a skill's file when the task matches its description.",
118
+ "When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
119
+ "",
120
+ "<available_skills>"
121
+ ];
122
+ for (const skill of visible)
123
+ lines.push(
124
+ " <skill>",
125
+ ` <name>${escapeXml(skill.name)}</name>`,
126
+ ` <description>${escapeXml(skill.description)}</description>`,
127
+ ` <location>${escapeXml(skill.filePath)}</location>`,
128
+ " </skill>"
129
+ );
130
+ lines.push("</available_skills>");
131
+ return lines.join("\n");
132
+ }
133
+ async function expandSkillCommand(text, skills, files, context) {
134
+ if (!text.startsWith("/skill:")) return text;
135
+ const space = text.indexOf(" ");
136
+ const name = space === -1 ? text.slice(7) : text.slice(7, space);
137
+ const args = space === -1 ? "" : text.slice(space + 1).trim();
138
+ const skill = skills.find((candidate) => candidate.name === name);
139
+ if (!skill) return text;
140
+ const read = await files.readTextFile(skill.filePath, context);
141
+ if (!read.ok) return text;
142
+ const body = parseFrontmatter(read.value).body.trim();
143
+ const block = `<skill name="${skill.name}" location="${skill.filePath}">
144
+ References are relative to ${skill.baseDir}.
145
+
146
+ ${body}
147
+ </skill>`;
148
+ return args ? `${block}
149
+
150
+ ${args}` : block;
151
+ }
152
+ export {
153
+ MAX_DESCRIPTION_LENGTH,
154
+ MAX_NAME_LENGTH,
155
+ expandSkillCommand,
156
+ formatSkillsForPrompt,
157
+ loadSkills,
158
+ loadSkillsFromDir,
159
+ skillFromText,
160
+ validateName
161
+ };
162
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/skills/skills.ts"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { ExecutionEnv } from '@earendil-works/pi-durable/env';\nimport { parseFrontmatter } from '../roles/frontmatter.ts';\n\nexport const MAX_NAME_LENGTH = 64;\nexport const MAX_DESCRIPTION_LENGTH = 1024;\n\nexport type SkillFiles = Pick<ExecutionEnv, 'readTextFile' | 'listDir' | 'canonicalPath'>;\n\nexport type SkillSource = 'user' | 'compat' | 'package' | 'builtin';\n\nexport interface Skill {\n name: string;\n description: string;\n filePath: string;\n baseDir: string;\n source: SkillSource;\n disableModelInvocation: boolean;\n}\n\nexport interface Diagnostic {\n path: string;\n message: string;\n}\n\nexport interface Loaded {\n skills: Skill[];\n diagnostics: Diagnostic[];\n}\n\nexport interface SkillRoot {\n dir: string;\n source: SkillSource;\n}\n\nfunction dirname(path: string): string {\n return path.slice(0, path.lastIndexOf('/')) || '/';\n}\n\nfunction basename(path: string): string {\n return path.slice(path.lastIndexOf('/') + 1);\n}\n\nexport function validateName(name: string): string[] {\n const errors: string[] = [];\n if (name.length > MAX_NAME_LENGTH)\n errors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);\n if (!/^[a-z0-9-]+$/.test(name))\n errors.push('name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)');\n if (name.startsWith('-') || name.endsWith('-'))\n errors.push('name must not start or end with a hyphen');\n if (name.includes('--')) errors.push('name must not contain consecutive hyphens');\n return errors;\n}\n\nfunction validateDescription(description: unknown): string[] {\n if (typeof description !== 'string' || description.trim() === '')\n return ['description is required'];\n if (description.length > MAX_DESCRIPTION_LENGTH)\n return [`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`];\n return [];\n}\n\nexport function skillFromText(text: string, filePath: string, source: SkillSource): Loaded {\n const declared = basename(filePath) === 'SKILL.md';\n const { fields, problems } = parseFrontmatter(text);\n const diagnostics: Diagnostic[] = [];\n const description = typeof fields.description === 'string' ? fields.description.trim() : '';\n if (!declared && !description) return { skills: [], diagnostics };\n for (const problem of problems.filter((item) => !item.includes('nested values')))\n diagnostics.push({ path: filePath, message: problem });\n for (const message of validateDescription(description))\n diagnostics.push({ path: filePath, message });\n const baseDir = dirname(filePath);\n const name = (typeof fields.name === 'string' && fields.name) || basename(baseDir);\n for (const message of validateName(name)) diagnostics.push({ path: filePath, message });\n if (!description) return { skills: [], diagnostics };\n const skill: Skill = {\n name,\n description,\n filePath,\n baseDir,\n source,\n disableModelInvocation: fields['disable-model-invocation'] === true,\n };\n return { skills: [skill], diagnostics };\n}\n\nasync function loadFile(\n files: SkillFiles,\n filePath: string,\n source: SkillSource,\n context: Context\n): Promise<Loaded> {\n const read = await files.readTextFile(filePath, context);\n if (!read.ok)\n return { skills: [], diagnostics: [{ path: filePath, message: read.error.message }] };\n return skillFromText(read.value, filePath, source);\n}\n\nexport async function loadSkillsFromDir(\n files: SkillFiles,\n dir: string,\n source: SkillSource,\n context: Context,\n root = true,\n visited = new Set<string>()\n): Promise<Loaded> {\n const out: Loaded = { skills: [], diagnostics: [] };\n const real = await files.canonicalPath(dir, context);\n const key = real.ok ? real.value : dir;\n if (visited.has(key)) return out;\n visited.add(key);\n const listed = await files.listDir(dir, context);\n if (!listed.ok) return out;\n const merge = (found: Loaded) => {\n out.skills.push(...found.skills);\n out.diagnostics.push(...found.diagnostics);\n };\n if (listed.value.some((entry) => entry.name === 'SKILL.md' && entry.kind !== 'directory')) {\n merge(await loadFile(files, `${dir}/SKILL.md`, source, context));\n return out;\n }\n const entries = [...listed.value].sort((a, b) => a.name.localeCompare(b.name));\n for (const entry of entries) {\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n const path = `${dir}/${entry.name}`;\n if (entry.kind === 'directory' || entry.kind === 'symlink')\n merge(await loadSkillsFromDir(files, path, source, context, false, visited));\n if (entry.kind !== 'directory' && root && entry.name.endsWith('.md'))\n merge(await loadFile(files, path, source, context));\n }\n return out;\n}\n\nexport async function loadSkills(\n files: SkillFiles,\n roots: readonly SkillRoot[],\n context: Context\n): Promise<Loaded> {\n const byName = new Map<string, Skill>();\n const seen = new Set<string>();\n const diagnostics: Diagnostic[] = [];\n const collisions: Diagnostic[] = [];\n for (const root of roots) {\n const found = await loadSkillsFromDir(files, root.dir, root.source, context);\n diagnostics.push(...found.diagnostics);\n for (const skill of found.skills) {\n if (seen.has(skill.filePath)) continue;\n const existing = byName.get(skill.name);\n if (existing)\n collisions.push({\n path: skill.filePath,\n message: `name \"${skill.name}\" collision; ${existing.filePath} is used`,\n });\n else {\n byName.set(skill.name, skill);\n seen.add(skill.filePath);\n }\n }\n }\n return { skills: [...byName.values()], diagnostics: [...diagnostics, ...collisions] };\n}\n\nfunction escapeXml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\nexport function formatSkillsForPrompt(skills: readonly Skill[]): string {\n const visible = skills.filter((skill) => !skill.disableModelInvocation);\n if (!visible.length) return '';\n const lines = [\n 'The following skills provide specialized instructions for specific tasks.',\n \"Use the read tool to load a skill's file when the task matches its description.\",\n 'When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.',\n '',\n '<available_skills>',\n ];\n for (const skill of visible)\n lines.push(\n ' <skill>',\n ` <name>${escapeXml(skill.name)}</name>`,\n ` <description>${escapeXml(skill.description)}</description>`,\n ` <location>${escapeXml(skill.filePath)}</location>`,\n ' </skill>'\n );\n lines.push('</available_skills>');\n return lines.join('\\n');\n}\n\nexport async function expandSkillCommand(\n text: string,\n skills: readonly Skill[],\n files: SkillFiles,\n context: Context\n): Promise<string> {\n if (!text.startsWith('/skill:')) return text;\n const space = text.indexOf(' ');\n const name = space === -1 ? text.slice(7) : text.slice(7, space);\n const args = space === -1 ? '' : text.slice(space + 1).trim();\n const skill = skills.find((candidate) => candidate.name === name);\n if (!skill) return text;\n const read = await files.readTextFile(skill.filePath, context);\n if (!read.ok) return text;\n const body = parseFrontmatter(read.value).body.trim();\n const block = `<skill name=\"${skill.name}\" location=\"${skill.filePath}\">\\nReferences are relative to ${skill.baseDir}.\\n\\n${body}\\n</skill>`;\n return args ? `${block}\\n\\n${args}` : block;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,97 @@
1
+ import { parseFrontmatter } from "../roles/frontmatter.js";
2
+ function parseCommandArgs(text) {
3
+ const args = [];
4
+ let current = "";
5
+ let quote = null;
6
+ for (const char of text) {
7
+ if (quote) {
8
+ if (char === quote) quote = null;
9
+ else current += char;
10
+ } else if (char === '"' || char === "'") quote = char;
11
+ else if (/\s/.test(char)) {
12
+ if (current) args.push(current);
13
+ current = "";
14
+ } else current += char;
15
+ }
16
+ if (current) args.push(current);
17
+ return args;
18
+ }
19
+ function all(args, target) {
20
+ return target === "@" || target === "ARGUMENTS" ? args.join(" ") : args[Number.parseInt(target, 10) - 1];
21
+ }
22
+ function substituteArgs(content, args) {
23
+ return content.replace(
24
+ /\$\{(\d+|ARGUMENTS|@):-([^}]*)\}|\$\{@:(\d+)(?::(\d+))?\}|\$(ARGUMENTS|@|\d+)/g,
25
+ (_match, fallbackTarget, fallback, sliceStart, sliceLength, simple) => {
26
+ if (fallbackTarget) return all(args, fallbackTarget) || fallback;
27
+ if (sliceStart) {
28
+ const start = Math.max(0, Number.parseInt(sliceStart, 10) - 1);
29
+ const end = sliceLength ? start + Number.parseInt(sliceLength, 10) : void 0;
30
+ return args.slice(start, end).join(" ");
31
+ }
32
+ return all(args, simple) ?? "";
33
+ }
34
+ );
35
+ }
36
+ function templateFromText(text, filePath) {
37
+ const { fields, body } = parseFrontmatter(text);
38
+ const name = filePath.slice(filePath.lastIndexOf("/") + 1).replace(/\.md$/, "");
39
+ let description = typeof fields.description === "string" ? fields.description : "";
40
+ if (!description) {
41
+ const first = body.split("\n").find((line) => line.trim()) ?? "";
42
+ description = first.length > 60 ? `${first.slice(0, 60)}...` : first;
43
+ }
44
+ const hint = fields["argument-hint"];
45
+ return {
46
+ name,
47
+ description,
48
+ ...typeof hint === "string" && hint ? { argumentHint: hint } : {},
49
+ content: body,
50
+ filePath
51
+ };
52
+ }
53
+ async function loadTemplates(files, sources, context) {
54
+ const out = { templates: [], diagnostics: [] };
55
+ const add = async (path) => {
56
+ if (out.templates.some((template) => template.filePath === path)) return;
57
+ const read = await files.readTextFile(path, context);
58
+ if (!read.ok) out.diagnostics.push({ path, message: read.error.message });
59
+ else {
60
+ const template = templateFromText(read.value, path);
61
+ const existing = out.templates.find((item) => item.name === template.name);
62
+ if (existing)
63
+ out.diagnostics.push({
64
+ path,
65
+ message: `name "${template.name}" collision; ${existing.filePath} is used`
66
+ });
67
+ else out.templates.push(template);
68
+ }
69
+ };
70
+ for (const source of sources) {
71
+ if (source.endsWith(".md") && !source.endsWith("*.md")) {
72
+ await add(source);
73
+ continue;
74
+ }
75
+ const dir = source.replace(/\/\*\.md$/, "");
76
+ const listed = await files.listDir(dir, context);
77
+ if (!listed.ok) continue;
78
+ const names = listed.value.filter((entry) => entry.kind !== "directory" && entry.name.endsWith(".md")).map((entry) => entry.name).sort();
79
+ for (const name of names) await add(`${dir}/${name}`);
80
+ }
81
+ return out;
82
+ }
83
+ function expandPromptTemplate(text, templates) {
84
+ const match = /^\/([^\s]+)(?:\s+([\s\S]*))?$/.exec(text);
85
+ if (!match) return text;
86
+ const template = templates.find((item) => item.name === match[1]);
87
+ if (!template) return text;
88
+ return substituteArgs(template.content, parseCommandArgs(match[2] ?? ""));
89
+ }
90
+ export {
91
+ expandPromptTemplate,
92
+ loadTemplates,
93
+ parseCommandArgs,
94
+ substituteArgs,
95
+ templateFromText
96
+ };
97
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/skills/templates.ts"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport { parseFrontmatter } from '../roles/frontmatter.ts';\nimport type { Diagnostic, SkillFiles } from './skills.ts';\n\nexport interface PromptTemplate {\n name: string;\n description: string;\n argumentHint?: string;\n content: string;\n filePath: string;\n}\n\nexport interface LoadedTemplates {\n templates: PromptTemplate[];\n diagnostics: Diagnostic[];\n}\n\nexport function parseCommandArgs(text: string): string[] {\n const args: string[] = [];\n let current = '';\n let quote: string | null = null;\n for (const char of text) {\n if (quote) {\n if (char === quote) quote = null;\n else current += char;\n } else if (char === '\"' || char === \"'\") quote = char;\n else if (/\\s/.test(char)) {\n if (current) args.push(current);\n current = '';\n } else current += char;\n }\n if (current) args.push(current);\n return args;\n}\n\nfunction all(args: readonly string[], target: string): string | undefined {\n return target === '@' || target === 'ARGUMENTS'\n ? args.join(' ')\n : args[Number.parseInt(target, 10) - 1];\n}\n\nexport function substituteArgs(content: string, args: readonly string[]): string {\n return content.replace(\n /\\$\\{(\\d+|ARGUMENTS|@):-([^}]*)\\}|\\$\\{@:(\\d+)(?::(\\d+))?\\}|\\$(ARGUMENTS|@|\\d+)/g,\n (_match, fallbackTarget, fallback, sliceStart, sliceLength, simple) => {\n if (fallbackTarget) return all(args, fallbackTarget) || fallback;\n if (sliceStart) {\n const start = Math.max(0, Number.parseInt(sliceStart, 10) - 1);\n const end = sliceLength ? start + Number.parseInt(sliceLength, 10) : undefined;\n return args.slice(start, end).join(' ');\n }\n return all(args, simple) ?? '';\n }\n );\n}\n\nexport function templateFromText(text: string, filePath: string): PromptTemplate {\n const { fields, body } = parseFrontmatter(text);\n const name = filePath.slice(filePath.lastIndexOf('/') + 1).replace(/\\.md$/, '');\n let description = typeof fields.description === 'string' ? fields.description : '';\n if (!description) {\n const first = body.split('\\n').find((line) => line.trim()) ?? '';\n description = first.length > 60 ? `${first.slice(0, 60)}...` : first;\n }\n const hint = fields['argument-hint'];\n return {\n name,\n description,\n ...(typeof hint === 'string' && hint ? { argumentHint: hint } : {}),\n content: body,\n filePath,\n };\n}\n\nexport async function loadTemplates(\n files: SkillFiles,\n sources: readonly string[],\n context: Context\n): Promise<LoadedTemplates> {\n const out: LoadedTemplates = { templates: [], diagnostics: [] };\n const add = async (path: string) => {\n if (out.templates.some((template) => template.filePath === path)) return;\n const read = await files.readTextFile(path, context);\n if (!read.ok) out.diagnostics.push({ path, message: read.error.message });\n else {\n const template = templateFromText(read.value, path);\n const existing = out.templates.find((item) => item.name === template.name);\n if (existing)\n out.diagnostics.push({\n path,\n message: `name \"${template.name}\" collision; ${existing.filePath} is used`,\n });\n else out.templates.push(template);\n }\n };\n for (const source of sources) {\n if (source.endsWith('.md') && !source.endsWith('*.md')) {\n await add(source);\n continue;\n }\n const dir = source.replace(/\\/\\*\\.md$/, '');\n const listed = await files.listDir(dir, context);\n if (!listed.ok) continue;\n const names = listed.value\n .filter((entry) => entry.kind !== 'directory' && entry.name.endsWith('.md'))\n .map((entry) => entry.name)\n .sort();\n for (const name of names) await add(`${dir}/${name}`);\n }\n return out;\n}\n\nexport function expandPromptTemplate(text: string, templates: readonly PromptTemplate[]): string {\n const match = /^\\/([^\\s]+)(?:\\s+([\\s\\S]*))?$/.exec(text);\n if (!match) return text;\n const template = templates.find((item) => item.name === match[1]);\n if (!template) return text;\n return substituteArgs(template.content, parseCommandArgs(match[2] ?? ''));\n}\n"],
5
+ "mappings": "AACA,SAAS,wBAAwB;AAgB1B,SAAS,iBAAiB,MAAwB;AACvD,QAAM,OAAiB,CAAC;AACxB,MAAI,UAAU;AACd,MAAI,QAAuB;AAC3B,aAAW,QAAQ,MAAM;AACvB,QAAI,OAAO;AACT,UAAI,SAAS,MAAO,SAAQ;AAAA,UACvB,YAAW;AAAA,IAClB,WAAW,SAAS,OAAO,SAAS,IAAK,SAAQ;AAAA,aACxC,KAAK,KAAK,IAAI,GAAG;AACxB,UAAI,QAAS,MAAK,KAAK,OAAO;AAC9B,gBAAU;AAAA,IACZ,MAAO,YAAW;AAAA,EACpB;AACA,MAAI,QAAS,MAAK,KAAK,OAAO;AAC9B,SAAO;AACT;AAEA,SAAS,IAAI,MAAyB,QAAoC;AACxE,SAAO,WAAW,OAAO,WAAW,cAChC,KAAK,KAAK,GAAG,IACb,KAAK,OAAO,SAAS,QAAQ,EAAE,IAAI,CAAC;AAC1C;AAEO,SAAS,eAAe,SAAiB,MAAiC;AAC/E,SAAO,QAAQ;AAAA,IACb;AAAA,IACA,CAAC,QAAQ,gBAAgB,UAAU,YAAY,aAAa,WAAW;AACrE,UAAI,eAAgB,QAAO,IAAI,MAAM,cAAc,KAAK;AACxD,UAAI,YAAY;AACd,cAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,SAAS,YAAY,EAAE,IAAI,CAAC;AAC7D,cAAM,MAAM,cAAc,QAAQ,OAAO,SAAS,aAAa,EAAE,IAAI;AACrE,eAAO,KAAK,MAAM,OAAO,GAAG,EAAE,KAAK,GAAG;AAAA,MACxC;AACA,aAAO,IAAI,MAAM,MAAM,KAAK;AAAA,IAC9B;AAAA,EACF;AACF;AAEO,SAAS,iBAAiB,MAAc,UAAkC;AAC/E,QAAM,EAAE,QAAQ,KAAK,IAAI,iBAAiB,IAAI;AAC9C,QAAM,OAAO,SAAS,MAAM,SAAS,YAAY,GAAG,IAAI,CAAC,EAAE,QAAQ,SAAS,EAAE;AAC9E,MAAI,cAAc,OAAO,OAAO,gBAAgB,WAAW,OAAO,cAAc;AAChF,MAAI,CAAC,aAAa;AAChB,UAAM,QAAQ,KAAK,MAAM,IAAI,EAAE,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,KAAK;AAC9D,kBAAc,MAAM,SAAS,KAAK,GAAG,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ;AAAA,EACjE;AACA,QAAM,OAAO,OAAO,eAAe;AACnC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,GAAI,OAAO,SAAS,YAAY,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;AAAA,IACjE,SAAS;AAAA,IACT;AAAA,EACF;AACF;AAEA,eAAsB,cACpB,OACA,SACA,SAC0B;AAC1B,QAAM,MAAuB,EAAE,WAAW,CAAC,GAAG,aAAa,CAAC,EAAE;AAC9D,QAAM,MAAM,OAAO,SAAiB;AAClC,QAAI,IAAI,UAAU,KAAK,CAAC,aAAa,SAAS,aAAa,IAAI,EAAG;AAClE,UAAM,OAAO,MAAM,MAAM,aAAa,MAAM,OAAO;AACnD,QAAI,CAAC,KAAK,GAAI,KAAI,YAAY,KAAK,EAAE,MAAM,SAAS,KAAK,MAAM,QAAQ,CAAC;AAAA,SACnE;AACH,YAAM,WAAW,iBAAiB,KAAK,OAAO,IAAI;AAClD,YAAM,WAAW,IAAI,UAAU,KAAK,CAAC,SAAS,KAAK,SAAS,SAAS,IAAI;AACzE,UAAI;AACF,YAAI,YAAY,KAAK;AAAA,UACnB;AAAA,UACA,SAAS,SAAS,SAAS,IAAI,gBAAgB,SAAS,QAAQ;AAAA,QAClE,CAAC;AAAA,UACE,KAAI,UAAU,KAAK,QAAQ;AAAA,IAClC;AAAA,EACF;AACA,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,SAAS,MAAM,GAAG;AACtD,YAAM,IAAI,MAAM;AAChB;AAAA,IACF;AACA,UAAM,MAAM,OAAO,QAAQ,aAAa,EAAE;AAC1C,UAAM,SAAS,MAAM,MAAM,QAAQ,KAAK,OAAO;AAC/C,QAAI,CAAC,OAAO,GAAI;AAChB,UAAM,QAAQ,OAAO,MAClB,OAAO,CAAC,UAAU,MAAM,SAAS,eAAe,MAAM,KAAK,SAAS,KAAK,CAAC,EAC1E,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,KAAK;AACR,eAAW,QAAQ,MAAO,OAAM,IAAI,GAAG,GAAG,IAAI,IAAI,EAAE;AAAA,EACtD;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,MAAc,WAA8C;AAC/F,QAAM,QAAQ,gCAAgC,KAAK,IAAI;AACvD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,WAAW,UAAU,KAAK,CAAC,SAAS,KAAK,SAAS,MAAM,CAAC,CAAC;AAChE,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,eAAe,SAAS,SAAS,iBAAiB,MAAM,CAAC,KAAK,EAAE,CAAC;AAC1E;",
6
+ "names": []
7
+ }
@@ -188,7 +188,7 @@ class AgentAdapter extends Emitter {
188
188
  return [];
189
189
  }
190
190
  commands() {
191
- return [];
191
+ return (this.#connection.commands?.value ?? []).map((command) => ({ ...command }));
192
192
  }
193
193
  createScoop(parentId, name) {
194
194
  const provisional = `scoop:creating-${crypto.randomUUID()}`;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/spectrum/agent-port.ts"],
4
- "sourcesContent": ["import type {\n Agent,\n AgentEvents,\n AgentPort,\n AgentStatus,\n FrozenCone,\n Message,\n Outgoing,\n SlashCommand,\n Thinking,\n UserMessage,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { AgentConnection } from '../client.ts';\nimport { entryLick } from '../licks/state.ts';\nimport type { AgentSettingsChange, AgentSummary } from '../services.ts';\nimport { Emitter } from './emitter.ts';\nimport { isBusy, queued, toMessages } from './messages.ts';\n\nexport const CONE = 'cone';\n\nconst thinking: Record<string, Thinking> = {\n off: 'off',\n minimal: 'low',\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'high',\n max: 'high',\n};\n\ninterface AgentDoc {\n model?: { provider: string; modelId: string };\n thinkingLevel?: string;\n}\n\nfunction status(view: ConversationView | undefined, messages: readonly Message[]): AgentStatus {\n if (isBusy(view)) {\n const live = view?.docs['pi.live'] as { tools?: { status: string }[] } | undefined;\n return live?.tools?.some((slot) => slot.status === 'running') ? 'working' : 'thinking';\n }\n const last = messages.at(-1);\n return last?.role === 'assistant' && last.status === 'error' ? 'error' : 'idle';\n}\n\nexport class AgentAdapter extends Emitter<AgentEvents> implements AgentPort {\n readonly #connection: AgentConnection;\n readonly #ready: Promise<void>;\n readonly #messages = new Map<string, Message[]>();\n #active: string | null = null;\n #creating = new Map<string, Promise<string | null>>();\n\n constructor(connection: AgentConnection) {\n super();\n this.#connection = connection;\n this.#ready = new Promise((resolve) => {\n connection.transcript.subscribe(() => {\n this.#refresh();\n resolve();\n });\n connection.deliveries.subscribe(() => this.#refresh());\n connection.views?.subscribe(() => this.#refresh());\n connection.agents?.subscribe(() => this.#refresh());\n });\n }\n\n #summary(): readonly AgentSummary[] {\n const agents = this.#connection.agents?.value?.agents;\n return agents?.length\n ? agents\n : [{ id: CONE, name: 'sliccy', kind: 'cone', parentId: null, role: null }];\n }\n\n #view(agentId: string = this.active()): ConversationView | undefined {\n const views = this.#connection.views?.value;\n const view = views?.[agentId];\n if (view) return view;\n const cone = this.#connection.agents?.value?.active ?? CONE;\n return agentId === cone ? this.#connection.transcript.value : undefined;\n }\n\n #refresh(): void {\n this.emit('agents', this.list());\n for (const summary of this.#summary()) this.#update(summary.id);\n for (const id of [...this.#messages.keys()])\n if (!this.#summary().some((summary) => summary.id === id)) this.#messages.delete(id);\n }\n\n #update(agentId: string): void {\n const view = this.#view(agentId);\n if (!view) return;\n const before = this.#messages.get(agentId) ?? [];\n const after = toMessages(view, this.#connection.deliveries.value);\n this.#messages.set(agentId, after);\n if (before.length !== after.length) {\n this.emit('messages', agentId);\n return;\n }\n const changed = after.filter(\n (message, index) => JSON.stringify(message) !== JSON.stringify(before[index])\n );\n if (changed.length === 1) this.emit('message', { agentId, message: changed[0] as Message });\n else if (changed.length > 1) this.emit('messages', agentId);\n }\n\n ready(): Promise<void> {\n return this.#ready;\n }\n\n list(): readonly Agent[] {\n return this.#summary().map((summary) => {\n const view = this.#view(summary.id);\n const doc = view?.docs['pi.agent'] as AgentDoc | undefined;\n return {\n id: summary.id,\n name: summary.role ? `${summary.name} \u00B7 ${summary.role}` : summary.name,\n kind: summary.kind,\n parentId: summary.parentId,\n status: status(view, this.#messages.get(summary.id) ?? []),\n model: doc?.model ? `${doc.model.provider}/${doc.model.modelId}` : '',\n contextFill: 0,\n unread: 0,\n thinking: thinking[doc?.thinkingLevel ?? 'off'] ?? 'off',\n };\n });\n }\n\n active(): string {\n const known = this.#summary();\n if (this.#active && this.#creating.has(this.#active)) return this.#active;\n if (this.#active && known.some((summary) => summary.id === this.#active)) return this.#active;\n return this.#connection.agents?.value?.active ?? CONE;\n }\n\n select(id: string): void {\n if (this.#creating.has(id)) {\n this.#active = id;\n this.emit('active', id);\n return;\n }\n const found = this.#summary().find((summary) => summary.id === id);\n if (!found) return;\n this.#active = id;\n if (found.kind === 'cone') void this.#connection.control.selectCone(id, BACKGROUND_CONTEXT);\n this.emit('active', id);\n this.emit('messages', id);\n }\n\n messages(agentId: string = this.active()): readonly Message[] {\n return this.#messages.get(agentId) ?? [];\n }\n\n async send(agentId: string, input: string | Outgoing): Promise<void> {\n const outgoing = typeof input === 'string' ? { text: input } : input;\n const target = this.#creating.has(agentId) ? await this.#creating.get(agentId) : agentId;\n if (target === null) throw new Error('The scoop could not be created.');\n const sent = await this.#connection.control.send(\n {\n text: outgoing.text,\n whenBusy: outgoing.mode === 'queue' ? 'followUp' : 'steer',\n requestId: crypto.randomUUID(),\n agentId: target || null,\n },\n BACKGROUND_CONTEXT\n );\n if (!sent.accepted) throw new Error(sent.error.message);\n }\n\n async rewind(_agentId: string, messageId: string): Promise<Outgoing | null> {\n const rewound = await this.#connection.control.rewind(messageId, BACKGROUND_CONTEXT);\n return rewound.done ? { text: rewound.text } : null;\n }\n\n stop(agentId: string = this.active()): void {\n void this.#connection.control.stopAgent(agentId, BACKGROUND_CONTEXT);\n }\n\n busy(agentId: string = this.active()): boolean {\n return isBusy(this.#view(agentId));\n }\n\n queue(agentId: string = this.active()): readonly UserMessage[] {\n return queued(this.#view(agentId));\n }\n\n unqueue(agentId: string, messageId: string): void {\n void this.#connection.control.unqueue(agentId, messageId.replace(/^q/, ''), BACKGROUND_CONTEXT);\n }\n\n suggestion(): string | null {\n return null;\n }\n\n answer(): void {}\n\n resolveLick(agentId: string, messageId: string, state: 'confirmed' | 'dismissed'): void {\n const message = this.messages(agentId).find((candidate) => candidate.id === messageId);\n const entry = this.#view(agentId)?.entries.find(\n (candidate) => `e${candidate.id}` === messageId\n );\n const lick = message?.role === 'lick' && entry ? entryLick(entry) : undefined;\n if (lick) void this.#connection.control.resolveLick(lick.id, state, BACKGROUND_CONTEXT);\n }\n\n compact(): void {\n void this.#connection.control.compact(null, BACKGROUND_CONTEXT);\n }\n\n clear(): void {\n void this.#connection.control.reset(null, BACKGROUND_CONTEXT);\n }\n\n #configure(agentId: string, change: Partial<AgentSettingsChange>): void {\n void this.#connection.control.configure(\n {\n model: change.model ?? null,\n thinkingLevel: change.thinkingLevel ?? null,\n agentId,\n },\n BACKGROUND_CONTEXT\n );\n }\n\n setModel(agentId: string, model: string): void {\n const at = model.indexOf('/');\n if (at <= 0) return;\n this.#configure(agentId, {\n model: { provider: model.slice(0, at), modelId: model.slice(at + 1) },\n });\n }\n\n setThinking(agentId: string, level: Thinking): void {\n this.#configure(agentId, { thinkingLevel: level });\n }\n\n async older(): Promise<readonly Message[]> {\n return [];\n }\n\n commands(): readonly SlashCommand[] {\n return [];\n }\n\n createScoop(parentId: string, name: string): Agent {\n const provisional = `scoop:creating-${crypto.randomUUID()}`;\n const created = this.#connection.control.createScoop(parentId, name, BACKGROUND_CONTEXT).then(\n (result) => result.id,\n () => null\n );\n this.#creating.set(provisional, created);\n void created.then((id) => {\n if (this.#active !== provisional) return;\n this.#active = id ?? parentId;\n this.emit('active', this.#active);\n this.emit('messages', this.#active);\n });\n return {\n id: provisional,\n name,\n kind: 'scoop',\n parentId,\n status: 'idle',\n model: '',\n contextFill: 0,\n unread: 0,\n };\n }\n\n async drop(agentId: string): Promise<void> {\n const parent = this.#summary().find((summary) => summary.id === agentId)?.parentId ?? CONE;\n const dropped = await this.#connection.control.drop(agentId, BACKGROUND_CONTEXT);\n if (dropped.error !== null) throw new Error(dropped.error);\n if (this.#active === agentId) this.select(parent);\n }\n\n async createCone(name: string): Promise<Agent> {\n const created = await this.#connection.control.createCone(name, BACKGROUND_CONTEXT);\n if (created.error !== null) throw new Error(created.error);\n return {\n id: created.id,\n name: name.trim(),\n kind: 'cone',\n parentId: null,\n status: 'idle',\n model: '',\n contextFill: 0,\n unread: 0,\n };\n }\n\n frozen(): readonly FrozenCone[] {\n return [];\n }\n\n freeze(): void {}\n\n thaw(): Agent | null {\n return null;\n }\n\n discard(): void {}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n Agent,\n AgentEvents,\n AgentPort,\n AgentStatus,\n FrozenCone,\n Message,\n Outgoing,\n SlashCommand,\n Thinking,\n UserMessage,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { AgentConnection } from '../client.ts';\nimport { entryLick } from '../licks/state.ts';\nimport type { AgentSettingsChange, AgentSummary } from '../services.ts';\nimport { Emitter } from './emitter.ts';\nimport { isBusy, queued, toMessages } from './messages.ts';\n\nexport const CONE = 'cone';\n\nconst thinking: Record<string, Thinking> = {\n off: 'off',\n minimal: 'low',\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'high',\n max: 'high',\n};\n\ninterface AgentDoc {\n model?: { provider: string; modelId: string };\n thinkingLevel?: string;\n}\n\nfunction status(view: ConversationView | undefined, messages: readonly Message[]): AgentStatus {\n if (isBusy(view)) {\n const live = view?.docs['pi.live'] as { tools?: { status: string }[] } | undefined;\n return live?.tools?.some((slot) => slot.status === 'running') ? 'working' : 'thinking';\n }\n const last = messages.at(-1);\n return last?.role === 'assistant' && last.status === 'error' ? 'error' : 'idle';\n}\n\nexport class AgentAdapter extends Emitter<AgentEvents> implements AgentPort {\n readonly #connection: AgentConnection;\n readonly #ready: Promise<void>;\n readonly #messages = new Map<string, Message[]>();\n #active: string | null = null;\n #creating = new Map<string, Promise<string | null>>();\n\n constructor(connection: AgentConnection) {\n super();\n this.#connection = connection;\n this.#ready = new Promise((resolve) => {\n connection.transcript.subscribe(() => {\n this.#refresh();\n resolve();\n });\n connection.deliveries.subscribe(() => this.#refresh());\n connection.views?.subscribe(() => this.#refresh());\n connection.agents?.subscribe(() => this.#refresh());\n });\n }\n\n #summary(): readonly AgentSummary[] {\n const agents = this.#connection.agents?.value?.agents;\n return agents?.length\n ? agents\n : [{ id: CONE, name: 'sliccy', kind: 'cone', parentId: null, role: null }];\n }\n\n #view(agentId: string = this.active()): ConversationView | undefined {\n const views = this.#connection.views?.value;\n const view = views?.[agentId];\n if (view) return view;\n const cone = this.#connection.agents?.value?.active ?? CONE;\n return agentId === cone ? this.#connection.transcript.value : undefined;\n }\n\n #refresh(): void {\n this.emit('agents', this.list());\n for (const summary of this.#summary()) this.#update(summary.id);\n for (const id of [...this.#messages.keys()])\n if (!this.#summary().some((summary) => summary.id === id)) this.#messages.delete(id);\n }\n\n #update(agentId: string): void {\n const view = this.#view(agentId);\n if (!view) return;\n const before = this.#messages.get(agentId) ?? [];\n const after = toMessages(view, this.#connection.deliveries.value);\n this.#messages.set(agentId, after);\n if (before.length !== after.length) {\n this.emit('messages', agentId);\n return;\n }\n const changed = after.filter(\n (message, index) => JSON.stringify(message) !== JSON.stringify(before[index])\n );\n if (changed.length === 1) this.emit('message', { agentId, message: changed[0] as Message });\n else if (changed.length > 1) this.emit('messages', agentId);\n }\n\n ready(): Promise<void> {\n return this.#ready;\n }\n\n list(): readonly Agent[] {\n return this.#summary().map((summary) => {\n const view = this.#view(summary.id);\n const doc = view?.docs['pi.agent'] as AgentDoc | undefined;\n return {\n id: summary.id,\n name: summary.role ? `${summary.name} \u00B7 ${summary.role}` : summary.name,\n kind: summary.kind,\n parentId: summary.parentId,\n status: status(view, this.#messages.get(summary.id) ?? []),\n model: doc?.model ? `${doc.model.provider}/${doc.model.modelId}` : '',\n contextFill: 0,\n unread: 0,\n thinking: thinking[doc?.thinkingLevel ?? 'off'] ?? 'off',\n };\n });\n }\n\n active(): string {\n const known = this.#summary();\n if (this.#active && this.#creating.has(this.#active)) return this.#active;\n if (this.#active && known.some((summary) => summary.id === this.#active)) return this.#active;\n return this.#connection.agents?.value?.active ?? CONE;\n }\n\n select(id: string): void {\n if (this.#creating.has(id)) {\n this.#active = id;\n this.emit('active', id);\n return;\n }\n const found = this.#summary().find((summary) => summary.id === id);\n if (!found) return;\n this.#active = id;\n if (found.kind === 'cone') void this.#connection.control.selectCone(id, BACKGROUND_CONTEXT);\n this.emit('active', id);\n this.emit('messages', id);\n }\n\n messages(agentId: string = this.active()): readonly Message[] {\n return this.#messages.get(agentId) ?? [];\n }\n\n async send(agentId: string, input: string | Outgoing): Promise<void> {\n const outgoing = typeof input === 'string' ? { text: input } : input;\n const target = this.#creating.has(agentId) ? await this.#creating.get(agentId) : agentId;\n if (target === null) throw new Error('The scoop could not be created.');\n const sent = await this.#connection.control.send(\n {\n text: outgoing.text,\n whenBusy: outgoing.mode === 'queue' ? 'followUp' : 'steer',\n requestId: crypto.randomUUID(),\n agentId: target || null,\n },\n BACKGROUND_CONTEXT\n );\n if (!sent.accepted) throw new Error(sent.error.message);\n }\n\n async rewind(_agentId: string, messageId: string): Promise<Outgoing | null> {\n const rewound = await this.#connection.control.rewind(messageId, BACKGROUND_CONTEXT);\n return rewound.done ? { text: rewound.text } : null;\n }\n\n stop(agentId: string = this.active()): void {\n void this.#connection.control.stopAgent(agentId, BACKGROUND_CONTEXT);\n }\n\n busy(agentId: string = this.active()): boolean {\n return isBusy(this.#view(agentId));\n }\n\n queue(agentId: string = this.active()): readonly UserMessage[] {\n return queued(this.#view(agentId));\n }\n\n unqueue(agentId: string, messageId: string): void {\n void this.#connection.control.unqueue(agentId, messageId.replace(/^q/, ''), BACKGROUND_CONTEXT);\n }\n\n suggestion(): string | null {\n return null;\n }\n\n answer(): void {}\n\n resolveLick(agentId: string, messageId: string, state: 'confirmed' | 'dismissed'): void {\n const message = this.messages(agentId).find((candidate) => candidate.id === messageId);\n const entry = this.#view(agentId)?.entries.find(\n (candidate) => `e${candidate.id}` === messageId\n );\n const lick = message?.role === 'lick' && entry ? entryLick(entry) : undefined;\n if (lick) void this.#connection.control.resolveLick(lick.id, state, BACKGROUND_CONTEXT);\n }\n\n compact(): void {\n void this.#connection.control.compact(null, BACKGROUND_CONTEXT);\n }\n\n clear(): void {\n void this.#connection.control.reset(null, BACKGROUND_CONTEXT);\n }\n\n #configure(agentId: string, change: Partial<AgentSettingsChange>): void {\n void this.#connection.control.configure(\n {\n model: change.model ?? null,\n thinkingLevel: change.thinkingLevel ?? null,\n agentId,\n },\n BACKGROUND_CONTEXT\n );\n }\n\n setModel(agentId: string, model: string): void {\n const at = model.indexOf('/');\n if (at <= 0) return;\n this.#configure(agentId, {\n model: { provider: model.slice(0, at), modelId: model.slice(at + 1) },\n });\n }\n\n setThinking(agentId: string, level: Thinking): void {\n this.#configure(agentId, { thinkingLevel: level });\n }\n\n async older(): Promise<readonly Message[]> {\n return [];\n }\n\n commands(): readonly SlashCommand[] {\n return (this.#connection.commands?.value ?? []).map((command) => ({ ...command }));\n }\n\n createScoop(parentId: string, name: string): Agent {\n const provisional = `scoop:creating-${crypto.randomUUID()}`;\n const created = this.#connection.control.createScoop(parentId, name, BACKGROUND_CONTEXT).then(\n (result) => result.id,\n () => null\n );\n this.#creating.set(provisional, created);\n void created.then((id) => {\n if (this.#active !== provisional) return;\n this.#active = id ?? parentId;\n this.emit('active', this.#active);\n this.emit('messages', this.#active);\n });\n return {\n id: provisional,\n name,\n kind: 'scoop',\n parentId,\n status: 'idle',\n model: '',\n contextFill: 0,\n unread: 0,\n };\n }\n\n async drop(agentId: string): Promise<void> {\n const parent = this.#summary().find((summary) => summary.id === agentId)?.parentId ?? CONE;\n const dropped = await this.#connection.control.drop(agentId, BACKGROUND_CONTEXT);\n if (dropped.error !== null) throw new Error(dropped.error);\n if (this.#active === agentId) this.select(parent);\n }\n\n async createCone(name: string): Promise<Agent> {\n const created = await this.#connection.control.createCone(name, BACKGROUND_CONTEXT);\n if (created.error !== null) throw new Error(created.error);\n return {\n id: created.id,\n name: name.trim(),\n kind: 'cone',\n parentId: null,\n status: 'idle',\n model: '',\n contextFill: 0,\n unread: 0,\n };\n }\n\n frozen(): readonly FrozenCone[] {\n return [];\n }\n\n freeze(): void {}\n\n thaw(): Agent | null {\n return null;\n }\n\n discard(): void {}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,6 @@
1
1
  import { type Context, type ReplicatedState } from '@earendil-works/chord';
2
2
  import type { ConversationView } from '@earendil-works/pi-durable';
3
- import { AgentControl, AgentSettings, type AgentsSummary, type Delivered, type SendMode } from './services.ts';
3
+ import { AgentControl, AgentSettings, type AgentsSummary, type Command, type Delivered, type SendMode } from './services.ts';
4
4
  import { type PortEndpoint } from './wire.ts';
5
5
  export interface AgentConnection {
6
6
  readonly serverId: string;
@@ -9,6 +9,7 @@ export interface AgentConnection {
9
9
  readonly deliveries: ReplicatedState<Record<string, Delivered>>;
10
10
  readonly agents?: ReplicatedState<AgentsSummary>;
11
11
  readonly views?: ReplicatedState<Record<string, ConversationView>>;
12
+ readonly commands?: ReplicatedState<Command[]>;
12
13
  readonly settings: AgentSettings;
13
14
  prompt(text: string, whenBusy?: SendMode): Promise<string>;
14
15
  close(): Promise<void>;
@@ -0,0 +1,37 @@
1
+ import type { Context, JsonValue } from '@earendil-works/chord';
2
+ import { type CodemodeError, type CodemodeOutputItem, type CodemodeResult, type CodemodeSandboxOptions } from '@earendil-works/pi-codemode';
3
+ import { type Extension, type ToolExecutionApi, type ToolRegistration } from '@earendil-works/pi-durable';
4
+ export declare const CODEMODE = "codemode";
5
+ export declare const DEFAULT_MAX_OUTPUT_TOKENS = 10000;
6
+ export declare const MEMORY_LIMIT_BYTES: number;
7
+ export declare const CodemodeDoc: import("@earendil-works/pi-durable").RewindableConversationDocToken<{
8
+ store: Record<string, JsonValue>;
9
+ }>;
10
+ export interface Sandbox {
11
+ execute(code: string, options: {
12
+ signal?: AbortSignal;
13
+ store?: Record<string, unknown>;
14
+ }): Promise<CodemodeResult>;
15
+ close(): Promise<void>;
16
+ }
17
+ export type SandboxFactory = (options: CodemodeSandboxOptions) => Sandbox;
18
+ export interface CodemodeOptions {
19
+ sandbox: SandboxFactory;
20
+ declared: readonly ToolRegistration[];
21
+ }
22
+ export declare function codemodeDescription(declared: readonly ToolRegistration[]): string;
23
+ export declare function callNested(tool: ToolRegistration, args: unknown, api: ToolExecutionApi, context: Context, id: string): Promise<unknown>;
24
+ export declare function formatOutput(output: readonly CodemodeOutputItem[]): CodemodeOutputItem[];
25
+ export declare function joinText(items: readonly CodemodeOutputItem[]): CodemodeOutputItem[];
26
+ export declare function formatError(error: CodemodeError, calls: CodemodeResult['calls']): string;
27
+ export declare function truncate(items: readonly CodemodeOutputItem[], maxTokens: number): {
28
+ items: CodemodeOutputItem[];
29
+ full: string | null;
30
+ };
31
+ export declare function codemodeTool(options: CodemodeOptions): ToolRegistration;
32
+ export declare function codemodeExtension(tools: readonly ToolRegistration[]): Extension;
33
+ export declare function disabledBySettings(text: string | undefined): boolean;
34
+ export type Fetcher = (url: string) => Promise<Response>;
35
+ export declare function wasmCandidates(from: URL): string[];
36
+ export declare function locateWasm(fetcher: Fetcher, from: URL): Promise<WebAssembly.Module | undefined>;
37
+ export declare function sandboxFactory(wasm: () => Promise<WebAssembly.Module | undefined>, workerUrl?: string | URL): SandboxFactory;
@@ -4,6 +4,7 @@ import type { Deliveries } from './deliveries.ts';
4
4
  import type { LickSources, Licks } from './licks/index.ts';
5
5
  import type { Scoops, ScoopsRuntime } from './scoops/index.ts';
6
6
  import type { AgentControl } from './services.ts';
7
+ import type { SkillsRuntime } from './skills/index.ts';
7
8
  export interface HostLicks {
8
9
  licks: Licks;
9
10
  sources: LickSources;
@@ -14,4 +15,4 @@ export interface HostScoops {
14
15
  }
15
16
  export declare function readable(out: string): string;
16
17
  export declare function submissionId(value: string): SubmissionId | undefined;
17
- export declare function createAgentControl(harness: Harness, target: Conversation | Cones, deliveries?: Deliveries, licks?: HostLicks, scoops?: HostScoops): AgentControl;
18
+ export declare function createAgentControl(harness: Harness, target: Conversation | Cones, deliveries?: Deliveries, licks?: HostLicks, scoops?: HostScoops, skills?: Pick<SkillsRuntime, 'expand'>): AgentControl;
@@ -2,6 +2,7 @@ import { type Context } from '@earendil-works/chord';
2
2
  import type { Agent } from './agent.ts';
3
3
  import { type HostLicks, type HostScoops } from './control.ts';
4
4
  import { AgentSettings } from './services.ts';
5
+ import type { SkillsRuntime } from './skills/index.ts';
5
6
  import type { PortEndpoint } from './wire.ts';
6
7
  export interface AgentHost {
7
8
  readonly serverId: string;
@@ -15,4 +16,5 @@ export declare function hostAgent(agent: Agent, options?: {
15
16
  settings?: AgentSettings;
16
17
  licks?: HostLicks;
17
18
  scoops?: HostScoops;
19
+ skills?: SkillsRuntime;
18
20
  }): Promise<AgentHost>;
@@ -21,6 +21,8 @@ export interface AgentWorkerOptions {
21
21
  credentials?: () => Promise<CredentialStore>;
22
22
  storage?: () => Promise<Storage>;
23
23
  assets?: Assets;
24
+ codemodeWasm?: () => Promise<WebAssembly.Module | undefined>;
25
+ codemodeWorker?: string | URL;
24
26
  }
25
27
  export declare function alive(client: KernelClient): (pid: number) => Promise<boolean>;
26
28
  export declare function runAgentWorker(scope: AgentWorkerScope, options?: AgentWorkerOptions): Promise<AgentHost>;
@@ -44,5 +44,6 @@ export interface LoadOptions {
44
44
  }
45
45
  export declare function loadRoles(options: LoadOptions, context: Context): Promise<Roles>;
46
46
  export declare function findRole(roles: readonly Role[], name: string): Role | undefined;
47
+ export declare function manifestDirs(files: RoleFiles, root: string, context: Context): Promise<string[]>;
47
48
  export declare function agentPaths(dir: string, text: string): string[];
48
49
  export declare function packageDirs(files: RoleFiles, root: string, context: Context): Promise<string[]>;
@@ -2,7 +2,6 @@ import type { Context } from '@earendil-works/chord';
2
2
  import { type ConversationId, type Extension, type Submission, type ToolRegistration, type Tx } from '@earendil-works/pi-durable';
3
3
  import { type ScoopRecord } from '../agents.ts';
4
4
  import type { LickEvent } from '../licks/licks.ts';
5
- import type { Role } from '../roles/roles.ts';
6
5
  import { type ScoopsHost } from './service.ts';
7
6
  export declare const PREVIEW = 1000;
8
7
  type Lookup = () => Promise<ScoopsHost>;
@@ -66,14 +65,9 @@ export declare function scoopTasks(lookup: Lookup): {
66
65
  phase: 'watch';
67
66
  }, null, object>;
68
67
  };
69
- export declare function rolesSection(roles: readonly Role[]): string;
70
- export interface ScoopSections {
71
- roles(): readonly Role[];
72
- skill(): string;
73
- }
74
- export declare function scoopsExtension(tasks: ReturnType<typeof scoopTasks>, sections: ScoopSections): Extension;
68
+ export declare function scoopsExtension(tasks: ReturnType<typeof scoopTasks>): Extension;
75
69
  export declare function allowed(record: Pick<ScoopRecord, 'folder'> & {
76
70
  roots?: ScoopRecord['roots'];
77
- }, coneCwd: string, path: string, writing: boolean): boolean;
71
+ }, coneCwd: string, path: string, writing: boolean, reads?: readonly string[]): boolean;
78
72
  export declare function guardExtension(lookup: Lookup, tools: readonly ToolRegistration[]): Extension;
79
73
  export {};
@@ -14,7 +14,7 @@ export declare function packageAssets(base?: URL): Assets;
14
14
  export interface AttachOptions {
15
15
  harness: Harness;
16
16
  agents: Agents;
17
- groups?: ProcessGroups;
17
+ groups: ProcessGroups;
18
18
  env: ExecutionEnv;
19
19
  home: string;
20
20
  assets?: Assets;
@@ -22,6 +22,7 @@ export interface AttachOptions {
22
22
  controlDir?: string;
23
23
  sweepEvery?: number;
24
24
  alive?: (pid: number) => Promise<boolean>;
25
+ reads: () => readonly string[];
25
26
  }
26
27
  export interface ScoopsRuntime {
27
28
  roles(context: Context): Promise<Roles>;
@@ -45,6 +45,7 @@ export interface ScoopsHost {
45
45
  licks: Licks;
46
46
  groups?: ProcessGroups;
47
47
  tools?: readonly ToolRegistration[];
48
+ reads?: () => readonly string[];
48
49
  }
49
50
  export interface Rewound {
50
51
  stopped: string[];
@@ -109,6 +109,12 @@ export interface AgentTranscript {
109
109
  readonly deliveries: ReplicatedState<Record<string, Delivered>>;
110
110
  readonly agents: ReplicatedState<AgentsSummary>;
111
111
  readonly views: ReplicatedState<Record<string, ConversationView>>;
112
+ readonly commands: ReplicatedState<Command[]>;
113
+ }
114
+ export interface Command {
115
+ name: string;
116
+ description: string;
117
+ kind: 'prompt' | 'skill';
112
118
  }
113
119
  export declare const AgentTranscript: import("@earendil-works/chord").Service<AgentTranscript>;
114
120
  export interface AgentSessions {