@ai-ecoverse/slicc-agent 6.5.1 → 6.7.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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' | 'global' | '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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",
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' | 'global' | 'package' | 'builtin';\n\nexport const PNPM_DIR = '/usr/local/share/pnpm/';\n\nexport interface SkillPackage {\n name: string;\n version?: string;\n}\n\nexport interface Skill {\n name: string;\n description: string;\n filePath: string;\n baseDir: string;\n source: SkillSource;\n disableModelInvocation: boolean;\n package?: SkillPackage;\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\nasync function packageAt(\n files: SkillFiles,\n real: string,\n context: Context\n): Promise<SkillPackage | undefined> {\n for (let dir = real; dir.startsWith(PNPM_DIR); dir = dirname(dir)) {\n const read = await files.readTextFile(`${dir}/package.json`, context);\n if (!read.ok) continue;\n let manifest: { name?: unknown; version?: unknown };\n try {\n manifest = JSON.parse(read.value);\n } catch {\n continue;\n }\n if (typeof manifest.name !== 'string') continue;\n return {\n name: manifest.name,\n ...(typeof manifest.version === 'string' ? { version: manifest.version } : {}),\n };\n }\n return undefined;\n}\n\nexport async function labelPackages(\n files: SkillFiles,\n skills: readonly Skill[],\n context: Context\n): Promise<Skill[]> {\n return Promise.all(\n skills.map(async (skill) => {\n if (skill.source !== 'global') return skill;\n const real = await files.canonicalPath(skill.baseDir, context);\n const found = real.ok ? await packageAt(files, real.value, context) : undefined;\n return found ? { ...skill, source: 'package' as const, package: found } : skill;\n })\n );\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
6
  "names": []
7
7
  }
@@ -29,6 +29,7 @@ class SettingsAdapter extends Emitter {
29
29
  #mcpLogin;
30
30
  #signing = /* @__PURE__ */ new Map();
31
31
  #local;
32
+ #offered = "";
32
33
  constructor(settings, agent, storage, login = null, mcp = mcpLogin()) {
33
34
  super();
34
35
  this.#mcpLogin = mcp;
@@ -39,9 +40,14 @@ class SettingsAdapter extends Emitter {
39
40
  const saved = JSON.parse(storage?.getItem(SETTINGS_KEY) ?? "{}");
40
41
  const { model: _model, thinking: _thinking, ...local } = { ...defaults, ...saved };
41
42
  this.#local = local;
43
+ this.#offered = JSON.stringify(this.providers());
42
44
  settings.state.subscribe(() => {
43
45
  this.emit("accounts", this.accounts());
44
46
  this.emit("settings", this.get());
47
+ const offered = JSON.stringify(this.providers());
48
+ if (offered === this.#offered) return;
49
+ this.#offered = offered;
50
+ this.emit("providers", this.providers());
45
51
  });
46
52
  agent.on("agents", () => this.emit("settings", this.get()));
47
53
  }
@@ -85,6 +91,22 @@ class SettingsAdapter extends Emitter {
85
91
  };
86
92
  });
87
93
  }
94
+ #offer(id) {
95
+ const option = this.providers().find((candidate) => candidate.id === id);
96
+ if (!option) return void 0;
97
+ return {
98
+ auth: option.auth === "oauth" ? "oauth" : "api-key",
99
+ store: "vault",
100
+ group: "providers"
101
+ };
102
+ }
103
+ providers() {
104
+ return this.#state().providers ?? [];
105
+ }
106
+ async remove(id) {
107
+ this.cancel(id);
108
+ await this.#settings.remove(id, BACKGROUND_CONTEXT);
109
+ }
88
110
  accounts() {
89
111
  return this.#state().accounts.map(({ needs, ...account }) => {
90
112
  const shown = this.#signing.has(account.id) ? { ...account, status: "signing-in" } : account;
@@ -92,7 +114,7 @@ class SettingsAdapter extends Emitter {
92
114
  });
93
115
  }
94
116
  async connect(id, secret, fields) {
95
- const account = this.#state().accounts.find((candidate) => candidate.id === id);
117
+ const account = this.#state().accounts.find((candidate) => candidate.id === id) ?? this.#offer(id);
96
118
  const mcp = account?.group === "mcp";
97
119
  const login = account?.auth !== "oauth" ? null : mcp ? this.#mcpLogin : this.#login;
98
120
  const given = fields ? { ...fields } : null;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/spectrum/settings-port.ts"],
4
- "sourcesContent": ["import type {\n Account,\n ModelOption,\n Settings,\n SettingsEvents,\n SettingsPort,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { preferredModel } from '../preferred.ts';\nimport type { AgentSettings, SettingsState, SignIn } from '../services.ts';\nimport type { AgentAdapter } from './agent-port.ts';\nimport { attempt } from './attempt.ts';\nimport { Emitter } from './emitter.ts';\nimport { mcpLogin } from './mcp-login.ts';\n\nexport const SETTINGS_KEY = 'slicc-agent.settings';\n\nconst defaults: Settings = {\n color: 'system',\n model: '',\n thinking: 'off',\n sendOnEnter: true,\n showThinking: false,\n diffStyle: 'unified',\n};\n\ntype Local = Omit<Settings, 'model' | 'thinking'>;\n\nexport type Sealed = { sealed: true };\n\nexport type Login = (\n providerId: string,\n signIn: () => Promise<SignIn | null>,\n options: { signal: AbortSignal }\n) => Promise<string | Sealed>;\n\nexport const SIGN_IN_NEEDS_VAULT = \"Adobe sign-in needs seven's vault: update seven\";\nexport const UNFINISHED = \"The sign-in didn't finish. Try again.\";\n\nexport function readable(error: unknown): Error {\n if (error instanceof DOMException && error.name === 'AbortError') return error;\n const text = (error instanceof Error ? error.message : String(error ?? '')).trim();\n if (!text) return new Error(UNFINISHED);\n const lead = /^[a-z]+\\b(?![-.@/])/.test(text) ? text[0].toUpperCase() + text.slice(1) : text;\n return new Error(/[.!?]$/.test(lead) ? lead : `${lead}.`);\n}\n\nexport class SettingsAdapter extends Emitter<SettingsEvents> implements SettingsPort {\n readonly #settings: AgentSettings;\n readonly #agent: AgentAdapter;\n readonly #storage: Pick<Storage, 'getItem' | 'setItem'> | null;\n readonly #login: Login | null;\n readonly #mcpLogin: Login;\n readonly #signing = new Map<string, AbortController>();\n #local: Local;\n\n constructor(\n settings: AgentSettings,\n agent: AgentAdapter,\n storage: Pick<Storage, 'getItem' | 'setItem'> | null,\n login: Login | null = null,\n mcp: Login = mcpLogin() as Login\n ) {\n super();\n this.#mcpLogin = mcp;\n this.#settings = settings;\n this.#agent = agent;\n this.#storage = storage;\n this.#login = login;\n const saved = JSON.parse(storage?.getItem(SETTINGS_KEY) ?? '{}') as Partial<Local>;\n const { model: _model, thinking: _thinking, ...local } = { ...defaults, ...saved };\n this.#local = local;\n settings.state.subscribe(() => {\n this.emit('accounts', this.accounts());\n this.emit('settings', this.get());\n });\n agent.on('agents', () => this.emit('settings', this.get()));\n }\n\n #state(): SettingsState {\n return this.#settings.state.value ?? { models: [], accounts: [] };\n }\n\n get(): Settings {\n const cone = this.#agent.list()[0];\n return { ...this.#local, model: cone?.model ?? '', thinking: cone?.thinking ?? 'off' };\n }\n\n update(patch: Partial<Settings>): void {\n const { model, thinking, ...local } = patch;\n const id = this.#agent.active();\n if (model !== undefined) this.#agent.setModel(id, model);\n if (thinking !== undefined) this.#agent.setThinking(id, thinking);\n this.#local = { ...this.#local, ...local };\n this.#storage?.setItem(SETTINGS_KEY, JSON.stringify(this.#local));\n this.emit('settings', this.get());\n }\n\n models(): readonly ModelOption[] {\n const choices = this.#state().models;\n const groups = Map.groupBy(choices, ({ id }) => id.slice(0, id.indexOf('/')));\n const chosen = new Set(\n [...groups].map(\n ([providerId, group]) =>\n preferredModel(\n providerId,\n group.map((choice) => ({ id: choice.id.slice(providerId.length + 1), choice }))\n )?.choice\n )\n );\n return choices.map((choice) => {\n const { id, label, provider, kind, reasoning, images } = choice;\n return {\n id,\n label,\n provider,\n kind,\n reasoning,\n ...(images === undefined ? {} : { images }),\n ...(chosen.has(choice) ? { default: true } : {}),\n };\n });\n }\n\n accounts(): readonly Account[] {\n return this.#state().accounts.map(({ needs, ...account }) => {\n const shown = this.#signing.has(account.id)\n ? { ...account, status: 'signing-in' as const }\n : account;\n return needs ? { ...shown, needs } : shown;\n });\n }\n\n async connect(id: string, secret?: string, fields?: Record<string, string>): Promise<void> {\n const account = this.#state().accounts.find((candidate) => candidate.id === id);\n const mcp = account?.group === 'mcp';\n const login = account?.auth !== 'oauth' ? null : mcp ? this.#mcpLogin : this.#login;\n const given = fields ? { ...fields } : null;\n if (secret !== undefined) {\n await this.#settings.connect(id, secret, given, BACKGROUND_CONTEXT);\n return;\n }\n if (account?.auth === 'oauth' && !login) throw new Error(`${id} needs a sign-in`);\n if (!login) {\n if (account?.store !== 'vault') throw new Error(`${id} needs an API key`);\n await this.#settings.connect(id, null, given, BACKGROUND_CONTEXT);\n return;\n }\n const answer = await this.#signIn(id, login);\n if (typeof answer === 'string') {\n if (!mcp) throw new Error(SIGN_IN_NEEDS_VAULT);\n await this.#settings.connect(id, answer, given, BACKGROUND_CONTEXT);\n return;\n }\n if (answer?.sealed !== true) throw new Error(UNFINISHED);\n await this.#settings.connect(id, null, given, BACKGROUND_CONTEXT);\n }\n\n async #signIn(id: string, login: Login): Promise<string | Sealed> {\n const controller = new AbortController();\n this.#signing\n .get(id)\n ?.abort(new DOMException('A newer sign-in replaced this one.', 'AbortError'));\n this.#signing.set(id, controller);\n this.emit('accounts', this.accounts());\n try {\n const key = await login(\n id,\n () => this.#settings.signIn(id, BACKGROUND_CONTEXT) as Promise<SignIn | null>,\n {\n signal: controller.signal,\n }\n ).catch((error: unknown) => {\n controller.signal.throwIfAborted();\n throw readable(error);\n });\n controller.signal.throwIfAborted();\n return key;\n } finally {\n if (this.#signing.get(id) === controller) this.#signing.delete(id);\n this.emit('accounts', this.accounts());\n }\n }\n\n cancel(id: string): void {\n const controller = this.#signing.get(id);\n if (!controller) return;\n this.#signing.delete(id);\n controller.abort(new DOMException('The sign-in was cancelled.', 'AbortError'));\n this.emit('accounts', this.accounts());\n }\n\n disconnect(id: string): void {\n attempt(this.#settings.disconnect(id, BACKGROUND_CONTEXT), 'disconnect accounts', (text) =>\n this.#agent.notify(text)\n );\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n Account,\n ModelOption,\n Settings,\n SettingsEvents,\n SettingsPort,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport { preferredModel } from '../preferred.ts';\nimport type { AgentSettings, ProviderOption, SettingsState, SignIn } from '../services.ts';\nimport type { AgentAdapter } from './agent-port.ts';\nimport { attempt } from './attempt.ts';\nimport { Emitter } from './emitter.ts';\nimport { mcpLogin } from './mcp-login.ts';\n\nexport const SETTINGS_KEY = 'slicc-agent.settings';\n\nconst defaults: Settings = {\n color: 'system',\n model: '',\n thinking: 'off',\n sendOnEnter: true,\n showThinking: false,\n diffStyle: 'unified',\n};\n\ntype Local = Omit<Settings, 'model' | 'thinking'>;\n\nexport type Sealed = { sealed: true };\n\nexport type Login = (\n providerId: string,\n signIn: () => Promise<SignIn | null>,\n options: { signal: AbortSignal }\n) => Promise<string | Sealed>;\n\nexport const SIGN_IN_NEEDS_VAULT = \"Adobe sign-in needs seven's vault: update seven\";\nexport const UNFINISHED = \"The sign-in didn't finish. Try again.\";\n\nexport function readable(error: unknown): Error {\n if (error instanceof DOMException && error.name === 'AbortError') return error;\n const text = (error instanceof Error ? error.message : String(error ?? '')).trim();\n if (!text) return new Error(UNFINISHED);\n const lead = /^[a-z]+\\b(?![-.@/])/.test(text) ? text[0].toUpperCase() + text.slice(1) : text;\n return new Error(/[.!?]$/.test(lead) ? lead : `${lead}.`);\n}\n\ntype Events = SettingsEvents & { providers: readonly ProviderOption[] };\n\nexport class SettingsAdapter extends Emitter<Events> implements SettingsPort {\n readonly #settings: AgentSettings;\n readonly #agent: AgentAdapter;\n readonly #storage: Pick<Storage, 'getItem' | 'setItem'> | null;\n readonly #login: Login | null;\n readonly #mcpLogin: Login;\n readonly #signing = new Map<string, AbortController>();\n #local: Local;\n #offered = '';\n\n constructor(\n settings: AgentSettings,\n agent: AgentAdapter,\n storage: Pick<Storage, 'getItem' | 'setItem'> | null,\n login: Login | null = null,\n mcp: Login = mcpLogin() as Login\n ) {\n super();\n this.#mcpLogin = mcp;\n this.#settings = settings;\n this.#agent = agent;\n this.#storage = storage;\n this.#login = login;\n const saved = JSON.parse(storage?.getItem(SETTINGS_KEY) ?? '{}') as Partial<Local>;\n const { model: _model, thinking: _thinking, ...local } = { ...defaults, ...saved };\n this.#local = local;\n this.#offered = JSON.stringify(this.providers());\n settings.state.subscribe(() => {\n this.emit('accounts', this.accounts());\n this.emit('settings', this.get());\n const offered = JSON.stringify(this.providers());\n if (offered === this.#offered) return;\n this.#offered = offered;\n this.emit('providers', this.providers());\n });\n agent.on('agents', () => this.emit('settings', this.get()));\n }\n\n #state(): SettingsState {\n return this.#settings.state.value ?? { models: [], accounts: [] };\n }\n\n get(): Settings {\n const cone = this.#agent.list()[0];\n return { ...this.#local, model: cone?.model ?? '', thinking: cone?.thinking ?? 'off' };\n }\n\n update(patch: Partial<Settings>): void {\n const { model, thinking, ...local } = patch;\n const id = this.#agent.active();\n if (model !== undefined) this.#agent.setModel(id, model);\n if (thinking !== undefined) this.#agent.setThinking(id, thinking);\n this.#local = { ...this.#local, ...local };\n this.#storage?.setItem(SETTINGS_KEY, JSON.stringify(this.#local));\n this.emit('settings', this.get());\n }\n\n models(): readonly ModelOption[] {\n const choices = this.#state().models;\n const groups = Map.groupBy(choices, ({ id }) => id.slice(0, id.indexOf('/')));\n const chosen = new Set(\n [...groups].map(\n ([providerId, group]) =>\n preferredModel(\n providerId,\n group.map((choice) => ({ id: choice.id.slice(providerId.length + 1), choice }))\n )?.choice\n )\n );\n return choices.map((choice) => {\n const { id, label, provider, kind, reasoning, images } = choice;\n return {\n id,\n label,\n provider,\n kind,\n reasoning,\n ...(images === undefined ? {} : { images }),\n ...(chosen.has(choice) ? { default: true } : {}),\n };\n });\n }\n\n #offer(id: string) {\n const option = this.providers().find((candidate) => candidate.id === id);\n if (!option) return undefined;\n return {\n auth: option.auth === 'oauth' ? ('oauth' as const) : ('api-key' as const),\n store: 'vault' as const,\n group: 'providers' as const,\n };\n }\n\n providers(): readonly ProviderOption[] {\n return this.#state().providers ?? [];\n }\n\n async remove(id: string): Promise<void> {\n this.cancel(id);\n await this.#settings.remove(id, BACKGROUND_CONTEXT);\n }\n\n accounts(): readonly Account[] {\n return this.#state().accounts.map(({ needs, ...account }) => {\n const shown = this.#signing.has(account.id)\n ? { ...account, status: 'signing-in' as const }\n : account;\n return needs ? { ...shown, needs } : shown;\n });\n }\n\n async connect(id: string, secret?: string, fields?: Record<string, string>): Promise<void> {\n const account =\n this.#state().accounts.find((candidate) => candidate.id === id) ?? this.#offer(id);\n const mcp = account?.group === 'mcp';\n const login = account?.auth !== 'oauth' ? null : mcp ? this.#mcpLogin : this.#login;\n const given = fields ? { ...fields } : null;\n if (secret !== undefined) {\n await this.#settings.connect(id, secret, given, BACKGROUND_CONTEXT);\n return;\n }\n if (account?.auth === 'oauth' && !login) throw new Error(`${id} needs a sign-in`);\n if (!login) {\n if (account?.store !== 'vault') throw new Error(`${id} needs an API key`);\n await this.#settings.connect(id, null, given, BACKGROUND_CONTEXT);\n return;\n }\n const answer = await this.#signIn(id, login);\n if (typeof answer === 'string') {\n if (!mcp) throw new Error(SIGN_IN_NEEDS_VAULT);\n await this.#settings.connect(id, answer, given, BACKGROUND_CONTEXT);\n return;\n }\n if (answer?.sealed !== true) throw new Error(UNFINISHED);\n await this.#settings.connect(id, null, given, BACKGROUND_CONTEXT);\n }\n\n async #signIn(id: string, login: Login): Promise<string | Sealed> {\n const controller = new AbortController();\n this.#signing\n .get(id)\n ?.abort(new DOMException('A newer sign-in replaced this one.', 'AbortError'));\n this.#signing.set(id, controller);\n this.emit('accounts', this.accounts());\n try {\n const key = await login(\n id,\n () => this.#settings.signIn(id, BACKGROUND_CONTEXT) as Promise<SignIn | null>,\n {\n signal: controller.signal,\n }\n ).catch((error: unknown) => {\n controller.signal.throwIfAborted();\n throw readable(error);\n });\n controller.signal.throwIfAborted();\n return key;\n } finally {\n if (this.#signing.get(id) === controller) this.#signing.delete(id);\n this.emit('accounts', this.accounts());\n }\n }\n\n cancel(id: string): void {\n const controller = this.#signing.get(id);\n if (!controller) return;\n this.#signing.delete(id);\n controller.abort(new DOMException('The sign-in was cancelled.', 'AbortError'));\n this.emit('accounts', this.accounts());\n }\n\n disconnect(id: string): void {\n attempt(this.#settings.disconnect(id, BACKGROUND_CONTEXT), 'disconnect accounts', (text) =>\n this.#agent.notify(text)\n );\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,21 @@
1
+ import type { Provider } from '@earendil-works/pi-ai';
2
+ export interface KeyProvider {
3
+ id: string;
4
+ make: () => Provider;
5
+ env: string;
6
+ description: string;
7
+ keyUrl: string;
8
+ cors: boolean;
9
+ }
10
+ export declare const KEY_PROVIDERS: readonly KeyProvider[];
11
+ export type ProviderAuth = 'api-key' | 'oauth' | 'fields' | 'local';
12
+ export interface Listing {
13
+ description: string;
14
+ keyUrl?: string;
15
+ auth: ProviderAuth;
16
+ corsFree: boolean;
17
+ vaultName: string;
18
+ }
19
+ export declare const LISTINGS: ReadonlyMap<string, Listing>;
20
+ export declare const ADDED_FIRST: readonly string[];
21
+ export declare const VAULT_KNOWN: ReadonlySet<string>;
@@ -11,6 +11,7 @@ import type { GuestDraft, GuestGates, GuestSeat } from './guests/seats.ts';
11
11
  import type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';
12
12
  import type { AgentRemote } from './remote/host.ts';
13
13
  import type { VaultState } from './secrets/vault.ts';
14
+ import type { SkillPackage, SkillSource } from './skills/skills.ts';
14
15
  import type { Sprinkle } from './sprinkles/kind.ts';
15
16
  export type SendMode = 'steer' | 'followUp' | 'reject';
16
17
  export interface SendRequest {
@@ -285,6 +286,8 @@ export interface Command {
285
286
  name: string;
286
287
  description: string;
287
288
  kind: 'prompt' | 'skill' | 'action';
289
+ source?: SkillSource;
290
+ package?: SkillPackage;
288
291
  }
289
292
  export declare const TRANSCRIPT_STATES: ("agents" | "approvalPolicy" | "approvals" | "changes" | "commands" | "deliveries" | "frozen" | "guests" | "importing" | "labels" | "memories" | "memoryScopes" | "monitor" | "privateRevision" | "sprinkles" | "state" | "vault" | "views")[];
290
293
  export declare const AgentTranscript: RemoteContract;
@@ -344,11 +347,26 @@ export interface AccountState {
344
347
  secret?: 'api-key' | 'token';
345
348
  store?: 'vault';
346
349
  fields?: AccountField[];
350
+ description?: string;
351
+ keyUrl?: string;
352
+ vaultName?: string;
347
353
  }
348
354
  export interface AccountField {
349
355
  name: string;
350
356
  label: string;
351
357
  required?: boolean;
358
+ placeholder?: string;
359
+ help?: string;
360
+ }
361
+ export interface ProviderOption {
362
+ id: string;
363
+ name: string;
364
+ description?: string;
365
+ auth: 'api-key' | 'oauth' | 'fields' | 'local';
366
+ fields?: AccountField[];
367
+ keyUrl?: string;
368
+ needs?: 'cors-free-transport';
369
+ vaultName?: string;
352
370
  }
353
371
  export interface BudgetState {
354
372
  provider: string;
@@ -368,6 +386,7 @@ export interface McpSignIn {
368
386
  export interface SettingsState {
369
387
  models: ModelChoice[];
370
388
  accounts: AccountState[];
389
+ providers?: ProviderOption[];
371
390
  budget?: BudgetState | null;
372
391
  moved?: {
373
392
  kind: 'model' | 'mcp';
@@ -379,8 +398,9 @@ export interface AgentSettings {
379
398
  readonly state: ReplicatedState<SettingsState>;
380
399
  connect(providerId: string, secret: string | null, fields: Record<string, string> | null, context: Context): Promise<void>;
381
400
  disconnect(providerId: string, context: Context): Promise<void>;
401
+ remove(providerId: string, context: Context): Promise<void>;
382
402
  signIn(providerId: string, context: Context): Promise<SignIn | McpSignIn | null>;
383
403
  dismissMoved(context: Context): Promise<void>;
384
404
  }
385
- export declare const SETTINGS_METHODS: ("connect" | "disconnect" | "dismissMoved" | "signIn")[];
405
+ export declare const SETTINGS_METHODS: ("connect" | "disconnect" | "dismissMoved" | "remove" | "signIn")[];
386
406
  export declare const AgentSettings: RemoteContract;
@@ -26,6 +26,9 @@ export interface ProviderFields {
26
26
  get(providerId: string): Readonly<Record<string, string>> | undefined;
27
27
  env(providerId: string): Readonly<Record<string, string>> | undefined;
28
28
  set(providerId: string, fields: Record<string, string> | undefined): Promise<void>;
29
+ added(): readonly string[] | undefined;
30
+ add(providerId: string): Promise<void>;
31
+ forget(providerId: string): Promise<void>;
29
32
  }
30
33
  export declare function providerFields(store: PlainStore, providers: readonly Provider[]): Promise<ProviderFields>;
31
34
  export type UsageReader = (token: string) => Promise<Budget | null>;
@@ -4,6 +4,11 @@ export declare const MAX_NAME_LENGTH = 64;
4
4
  export declare const MAX_DESCRIPTION_LENGTH = 1024;
5
5
  export type SkillFiles = Pick<ExecutionEnv, 'readTextFile' | 'listDir' | 'canonicalPath'>;
6
6
  export type SkillSource = 'user' | 'global' | 'package' | 'builtin';
7
+ export declare const PNPM_DIR = "/usr/local/share/pnpm/";
8
+ export interface SkillPackage {
9
+ name: string;
10
+ version?: string;
11
+ }
7
12
  export interface Skill {
8
13
  name: string;
9
14
  description: string;
@@ -11,6 +16,7 @@ export interface Skill {
11
16
  baseDir: string;
12
17
  source: SkillSource;
13
18
  disableModelInvocation: boolean;
19
+ package?: SkillPackage;
14
20
  }
15
21
  export interface Diagnostic {
16
22
  path: string;
@@ -28,5 +34,6 @@ export declare function validateName(name: string): string[];
28
34
  export declare function skillFromText(text: string, filePath: string, source: SkillSource): Loaded;
29
35
  export declare function loadSkillsFromDir(files: SkillFiles, dir: string, source: SkillSource, context: Context, root?: boolean, visited?: Set<string>): Promise<Loaded>;
30
36
  export declare function loadSkills(files: SkillFiles, roots: readonly SkillRoot[], context: Context): Promise<Loaded>;
37
+ export declare function labelPackages(files: SkillFiles, skills: readonly Skill[], context: Context): Promise<Skill[]>;
31
38
  export declare function formatSkillsForPrompt(skills: readonly Skill[]): string;
32
39
  export declare function expandSkillCommand(text: string, skills: readonly Skill[], files: SkillFiles, context: Context): Promise<string>;
@@ -1,5 +1,5 @@
1
1
  import type { Account, ModelOption, Settings, SettingsEvents, SettingsPort } from '@ai-ecoverse/slicc-spectrum/ui';
2
- import type { AgentSettings, SignIn } from '../services.ts';
2
+ import type { AgentSettings, ProviderOption, SignIn } from '../services.ts';
3
3
  import type { AgentAdapter } from './agent-port.ts';
4
4
  import { Emitter } from './emitter.ts';
5
5
  export declare const SETTINGS_KEY = "slicc-agent.settings";
@@ -12,14 +12,20 @@ export type Login = (providerId: string, signIn: () => Promise<SignIn | null>, o
12
12
  export declare const SIGN_IN_NEEDS_VAULT = "Adobe sign-in needs seven's vault: update seven";
13
13
  export declare const UNFINISHED = "The sign-in didn't finish. Try again.";
14
14
  export declare function readable(error: unknown): Error;
15
- export declare class SettingsAdapter extends Emitter<SettingsEvents> implements SettingsPort {
15
+ type Events = SettingsEvents & {
16
+ providers: readonly ProviderOption[];
17
+ };
18
+ export declare class SettingsAdapter extends Emitter<Events> implements SettingsPort {
16
19
  #private;
17
20
  constructor(settings: AgentSettings, agent: AgentAdapter, storage: Pick<Storage, 'getItem' | 'setItem'> | null, login?: Login | null, mcp?: Login);
18
21
  get(): Settings;
19
22
  update(patch: Partial<Settings>): void;
20
23
  models(): readonly ModelOption[];
24
+ providers(): readonly ProviderOption[];
25
+ remove(id: string): Promise<void>;
21
26
  accounts(): readonly Account[];
22
27
  connect(id: string, secret?: string, fields?: Record<string, string>): Promise<void>;
23
28
  cancel(id: string): void;
24
29
  disconnect(id: string): void;
25
30
  }
31
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-agent",
3
- "version": "6.5.1",
3
+ "version": "6.7.0",
4
4
  "description": "SLICC's agent core on pi durable: an agent worker, session storage, remoting and pi extensions",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -63,10 +63,10 @@
63
63
  "release": "semantic-release"
64
64
  },
65
65
  "devDependencies": {
66
- "@ai-ecoverse/slicc-kernel": "1.127.0",
66
+ "@ai-ecoverse/slicc-kernel": "1.128.0",
67
67
  "@ai-ecoverse/slicc-scrub": "1.0.0",
68
68
  "@ai-ecoverse/slicc-shared-web": "1.24.2",
69
- "@ai-ecoverse/slicc-spectrum": "1.89.0",
69
+ "@ai-ecoverse/slicc-spectrum": "1.89.1",
70
70
  "@ai-ecoverse/wasm-bash": "5.3.0-20",
71
71
  "@ai-ecoverse/wasm-coreutils": "9.12.0-10",
72
72
  "@ai-ecoverse/wasm-git": "2.55.0-17",