@geminixiang/mikan 1.0.0-beta.4 → 1.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/commands/extensions.d.ts.map +1 -1
- package/dist/commands/extensions.js +4 -2
- package/dist/commands/extensions.js.map +1 -1
- package/dist/harness/extensions/loader.d.ts +5 -5
- package/dist/harness/extensions/loader.d.ts.map +1 -1
- package/dist/harness/extensions/loader.js +18 -7
- package/dist/harness/extensions/loader.js.map +1 -1
- package/dist/harness/extensions/types.d.ts +18 -4
- package/dist/harness/extensions/types.d.ts.map +1 -1
- package/dist/harness/extensions/types.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,22 @@ any release.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [1.0.0-beta.5]
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Add `api.paths.sharedDataDir` for extension state intentionally shared across conversations.
|
|
17
|
+
- Add an idempotent, dry-run-first script for migrating extension code and data to the new layout.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Store conversation extension code and data under `conversations/<id>/` and global extension assets under `global/`.
|
|
22
|
+
- Make `api.paths.dataDir` conversation-scoped by default to prevent accidental cross-conversation state sharing.
|
|
23
|
+
|
|
24
|
+
### Tests
|
|
25
|
+
|
|
26
|
+
- Update extension loader and command coverage for the conversation-scoped layout.
|
|
27
|
+
|
|
12
28
|
## [1.0.0-beta.4]
|
|
13
29
|
|
|
14
30
|
### Changed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../src/commands/extensions.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAKjE;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,YAAW,cAAc;IACvD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../src/commands/extensions.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAKjE;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,YAAW,cAAc;IACvD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA8CzD;CACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { basename, join } from "node:path";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
3
|
import { defaultExtensionDirs, listInstalledExtensions } from "../harness/index.js";
|
|
4
4
|
import { readEnv } from "../utils/env.js";
|
|
5
5
|
import { matchCommand } from "./parse.js";
|
|
@@ -36,7 +36,9 @@ export class ExtensionsCommandHandler {
|
|
|
36
36
|
}
|
|
37
37
|
const lines = ["_Extensions_", ...misinstallLines];
|
|
38
38
|
for (const info of installed) {
|
|
39
|
-
|
|
39
|
+
// info.dir ends in `<scope>/extensions`; the scope is its parent segment
|
|
40
|
+
// (`global` or a conversation id).
|
|
41
|
+
const scope = basename(dirname(info.dir)) === "global" ? "global" : "this conversation";
|
|
40
42
|
const version = info.version ? `@${info.version}` : "";
|
|
41
43
|
const slug = info.slug !== info.name ? ` (slug: ${info.slug})` : "";
|
|
42
44
|
lines.push(`• *${info.name}*${version} — ${scope}${slug}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../src/commands/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../src/commands/extensions.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { defaultExtensionDirs, listInstalledExtensions } from \"../harness/index.js\";\nimport { readEnv } from \"../utils/env.js\";\nimport { matchCommand } from \"./parse.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replyDiagnosticWithContext } from \"./utils.js\";\n\nconst EXTENSIONS_COMMANDS = [\"/pi-extensions\", \"/extensions\"] as const;\n\n/**\n * `/pi-extensions` — inventory of extensions installed for this conversation.\n *\n * Discovery-only (nothing is imported or activated): it reports what is on\n * disk under the host-only state dir, for both the `global` scope and this\n * conversation's scope. Doubles as the user-facing answer to \"why can the\n * bot suddenly do X\" and the operator-facing answer to \"what is installed\n * where\".\n */\nexport class ExtensionsCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n const matched = matchCommand(context.commandText, EXTENSIONS_COMMANDS, { stripMention: true });\n if (!matched) return false;\n\n const dirs = defaultExtensionDirs(context.conversationId, readEnv(\"STATE_DIR\"));\n const installed = listInstalledExtensions(dirs);\n\n // A root-level index file means an extension's contents were copied into\n // the scope directory itself; the loader skips it (the slug would\n // degenerate to the scope name). Surface that here, not just in logs.\n const misinstalled = dirs.filter((dir) =>\n [\"index.mjs\", \"index.js\"].some((file) => existsSync(join(dir, file))),\n );\n const misinstallLines = misinstalled.map(\n (dir) =>\n `⚠️ \\`${dir}/index.mjs\\` 位於範圍根目錄,已被忽略 — 請移入具名子目錄(如 \\`${dir}/my-ext/\\`)。`,\n );\n\n if (installed.length === 0 && misinstallLines.length === 0) {\n await replyDiagnosticWithContext(\n context.responder,\n [\n \"_Extensions_\",\n \"沒有安裝任何 extension。\",\n `安裝位置:\\`${dirs[0]}\\`(所有會話)或 \\`${dirs[1]}\\`(僅此會話)。`,\n ].join(\"\\n\"),\n { style: \"muted\" },\n );\n return true;\n }\n\n const lines = [\"_Extensions_\", ...misinstallLines];\n for (const info of installed) {\n // info.dir ends in `<scope>/extensions`; the scope is its parent segment\n // (`global` or a conversation id).\n const scope = basename(dirname(info.dir)) === \"global\" ? \"global\" : \"this conversation\";\n const version = info.version ? `@${info.version}` : \"\";\n const slug = info.slug !== info.name ? ` (slug: ${info.slug})` : \"\";\n lines.push(`• *${info.name}*${version} — ${scope}${slug}`);\n if (info.description) lines.push(` ${info.description}`);\n if (info.skillNames.length > 0) lines.push(` skills: ${info.skillNames.join(\", \")}`);\n }\n lines.push(\"_(安裝/移除後,對話輸入 `/pi-new` 生效)_\");\n\n await replyDiagnosticWithContext(context.responder, lines.join(\"\\n\"), { style: \"muted\" });\n return true;\n }\n}\n"]}
|
|
@@ -4,11 +4,11 @@ import { type MikanSkill } from "../skills.js";
|
|
|
4
4
|
import { ExtensionRegistry } from "./registry.js";
|
|
5
5
|
import type { ExtensionHostServices, ExtensionLoadError, LoadedExtension } from "./types.js";
|
|
6
6
|
/**
|
|
7
|
-
* Canonical host-only extension directories for a conversation
|
|
8
|
-
* `<stateDir>/extensions
|
|
9
|
-
* `<stateDir>/
|
|
10
|
-
* `global`
|
|
11
|
-
*
|
|
7
|
+
* Canonical host-only extension code directories for a conversation, in load
|
|
8
|
+
* order: `<stateDir>/global/extensions` (all conversations) then
|
|
9
|
+
* `<stateDir>/conversations/<conversationId>/extensions` (that conversation
|
|
10
|
+
* only). `global` and `conversations` are the two sibling scopes; see
|
|
11
|
+
* `LAYOUT.md`. Conversation ids are used verbatim (see LAYOUT.md § Casing).
|
|
12
12
|
*/
|
|
13
13
|
export declare function defaultExtensionDirs(conversationId: string, stateDir?: string): string[];
|
|
14
14
|
/** Discovery-only view of an installed extension (module is not imported). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/harness/extensions/loader.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EAKlB,eAAe,EAIhB,MAAM,YAAY,CAAC;AAIpB;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,EACtB,QAAQ,GAAE,MAAkC,GAC3C,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/harness/extensions/loader.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EAKlB,eAAe,EAIhB,MAAM,YAAY,CAAC;AAIpB;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,EACtB,QAAQ,GAAE,MAAkC,GAC3C,MAAM,EAAE,CAKV;AAED,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACrC,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,sBAAsB,EAAE,CAkBhF;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE;QACP,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAClB,aAAa,EAAE,aAAa,CAAC;KAC9B,CAAC;IACF,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,4EAA4E;IAC5E,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AA0CD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAUxD;AA0MD,uEAAuE;AACvE,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAmD/B"}
|
|
@@ -29,14 +29,17 @@ import { loadSkillsFromDir } from "../skills.js";
|
|
|
29
29
|
import { ExtensionRegistry } from "./registry.js";
|
|
30
30
|
const EXTENSION_FILE_PATTERN = /\.(mjs|js)$/;
|
|
31
31
|
/**
|
|
32
|
-
* Canonical host-only extension directories for a conversation
|
|
33
|
-
* `<stateDir>/extensions
|
|
34
|
-
* `<stateDir>/
|
|
35
|
-
* `global`
|
|
36
|
-
*
|
|
32
|
+
* Canonical host-only extension code directories for a conversation, in load
|
|
33
|
+
* order: `<stateDir>/global/extensions` (all conversations) then
|
|
34
|
+
* `<stateDir>/conversations/<conversationId>/extensions` (that conversation
|
|
35
|
+
* only). `global` and `conversations` are the two sibling scopes; see
|
|
36
|
+
* `LAYOUT.md`. Conversation ids are used verbatim (see LAYOUT.md § Casing).
|
|
37
37
|
*/
|
|
38
38
|
export function defaultExtensionDirs(conversationId, stateDir = join(homedir(), ".mikan")) {
|
|
39
|
-
return [
|
|
39
|
+
return [
|
|
40
|
+
join(stateDir, "global", "extensions"),
|
|
41
|
+
join(stateDir, "conversations", conversationId, "extensions"),
|
|
42
|
+
];
|
|
40
43
|
}
|
|
41
44
|
/**
|
|
42
45
|
* List installed extensions without importing or activating them — safe for
|
|
@@ -235,7 +238,15 @@ function buildExtensionApi(params) {
|
|
|
235
238
|
context,
|
|
236
239
|
paths: {
|
|
237
240
|
get dataDir() {
|
|
238
|
-
|
|
241
|
+
// This conversation's own data, co-located with the conversation's
|
|
242
|
+
// other host-only assets; removed when the conversation is deleted.
|
|
243
|
+
const dir = join(stateDir, "conversations", conversationId, "extension-data", slug);
|
|
244
|
+
mkdirSync(dir, { recursive: true });
|
|
245
|
+
return dir;
|
|
246
|
+
},
|
|
247
|
+
get sharedDataDir() {
|
|
248
|
+
// Cross-conversation data under the sibling `global` scope.
|
|
249
|
+
const dir = join(stateDir, "global", "extension-data", slug);
|
|
239
250
|
mkdirSync(dir, { recursive: true });
|
|
240
251
|
return dir;
|
|
241
252
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/harness/extensions/loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extension discovery and activation.\n *\n * Extension modules execute inside the mikan host process with its full\n * privileges (platform tokens, vault, host filesystem). They must therefore\n * only ever load from host-controlled directories: installing an extension\n * is an administrator action. In particular, extension directories must not\n * live under paths mounted into sandbox containers (workspace and\n * conversation dirs in image mode), or code written from inside the sandbox\n * would be imported into the host process — a sandbox escape.\n * {@link defaultExtensionDirs} returns the canonical host-only locations\n * under mikan's state dir.\n *\n * Directories are scanned in order; all discovered extensions activate.\n * Accepted layouts:\n *\n * - `extensions/<name>.mjs` / `extensions/<name>.js`\n * - `extensions/<name>/index.mjs` / `extensions/<name>/index.js`\n *\n * Modules are imported with a cache-busting query so edited extensions are\n * picked up when a new harness instance is created for a conversation.\n */\nimport { existsSync, mkdirSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join } from \"path\";\nimport { pathToFileURL } from \"url\";\nimport type { AgentTool, ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport * as log from \"../../log.js\";\nimport { loadSkillsFromDir, type MikanSkill } from \"../skills.js\";\nimport { ExtensionRegistry } from \"./registry.js\";\nimport type {\n ExtensionHostServices,\n ExtensionLoadError,\n ExtensionManifest,\n ExtensionSchedulePayload,\n ExtensionScheduleInfo,\n ExtensionScheduleSpec,\n LoadedExtension,\n MikanExtensionActivate,\n MikanExtensionApi,\n MikanExtensionModule,\n} from \"./types.js\";\n\nconst EXTENSION_FILE_PATTERN = /\\.(mjs|js)$/;\n\n/**\n * Canonical host-only extension directories for a conversation:\n * `<stateDir>/extensions/global` (all conversations) and\n * `<stateDir>/extensions/<conversationId>` (that conversation only).\n * `global` is reserved as a scope name; platform conversation ids never\n * take that value.\n */\nexport function defaultExtensionDirs(\n conversationId: string,\n stateDir: string = join(homedir(), \".mikan\"),\n): string[] {\n return [join(stateDir, \"extensions\", \"global\"), join(stateDir, \"extensions\", conversationId)];\n}\n\n/** Discovery-only view of an installed extension (module is not imported). */\nexport interface InstalledExtensionInfo {\n /** Display name: manifest name, falling back to the slug. */\n name: string;\n slug: string;\n path: string;\n /** Scan directory the extension was found in. */\n dir: string;\n version?: string;\n description?: string;\n /** Names of skills shipped in the extension's skills/ directory. */\n skillNames: string[];\n}\n\n/**\n * List installed extensions without importing or activating them — safe for\n * inventory surfaces (commands, portal). Activation side effects (schedule\n * upserts, tool registration) only happen in {@link loadExtensions}.\n */\nexport function listInstalledExtensions(dirs: string[]): InstalledExtensionInfo[] {\n const infos: InstalledExtensionInfo[] = [];\n for (const dir of dirs) {\n for (const entrypoint of discoverExtensionEntrypoints(dir)) {\n const slug = extensionSlug(entrypoint);\n const manifest = readManifest(entrypoint);\n infos.push({\n name: manifest.name ?? slug,\n slug,\n path: entrypoint,\n dir,\n version: manifest.version,\n description: manifest.description,\n skillNames: loadExtensionSkills(entrypoint, slug).map((skill) => skill.name),\n });\n }\n }\n return infos;\n}\n\nexport interface LoadExtensionsOptions {\n /** Directories to scan for extensions. Missing directories are skipped. */\n dirs: string[];\n context: {\n conversationId: string;\n workspaceDir: string;\n model: Model<Api>;\n thinkingLevel: ThinkingLevel;\n };\n /** Embedder-provided services backing the v2 api surface. */\n services?: ExtensionHostServices;\n}\n\nexport interface LoadExtensionsResult {\n registry: ExtensionRegistry;\n extensions: LoadedExtension[];\n errors: ExtensionLoadError[];\n /** Skills contributed by extensions (inline: bodies ride in the prompt). */\n skills: MikanSkill[];\n}\n\nfunction discoverExtensionEntrypoints(dir: string): string[] {\n if (!existsSync(dir)) return [];\n const entrypoints: string[] = [];\n for (const name of readdirSync(dir).toSorted()) {\n if (name.startsWith(\".\")) continue;\n if (/^index\\.(mjs|js)$/.test(name)) {\n // An index file at the scan root means the extension's contents were\n // copied into the scope directory itself; the slug would degenerate to\n // the scope name (e.g. the conversation id), mis-keying its data dir,\n // secrets, and schedules. Require a named form instead.\n log.logWarning(\n `Ignoring extension index file at scope root: ${join(dir, name)}`,\n \"install into a named subdirectory (e.g. extensions/<scope>/my-ext/index.mjs)\",\n );\n continue;\n }\n const fullPath = join(dir, name);\n let stats;\n try {\n stats = statSync(fullPath);\n } catch {\n continue;\n }\n if (stats.isFile() && EXTENSION_FILE_PATTERN.test(name)) {\n entrypoints.push(fullPath);\n continue;\n }\n if (stats.isDirectory()) {\n for (const candidate of [\"index.mjs\", \"index.js\"]) {\n const indexPath = join(fullPath, candidate);\n if (existsSync(indexPath)) {\n entrypoints.push(indexPath);\n break;\n }\n }\n }\n }\n return entrypoints;\n}\n\n/**\n * Filesystem-safe identifier for an extension, derived from its install\n * location (directory name for `<name>/index.mjs`, file basename otherwise).\n * The slug keys the extension's data dir, secrets vault, and schedule\n * ownership — installing the same extension globally and per-conversation\n * shares one slug and therefore one data dir.\n */\nexport function extensionSlug(entrypoint: string): string {\n const base = /^index\\.(mjs|js)$/.test(basename(entrypoint))\n ? basename(dirname(entrypoint))\n : basename(entrypoint).replace(EXTENSION_FILE_PATTERN, \"\");\n const slug = base\n .toLowerCase()\n .replace(/[^a-z0-9_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 64);\n return slug || \"extension\";\n}\n\n/** Sanitize an extension-chosen schedule name into a filename segment. */\nfunction scheduleNameSegment(name: string): string {\n const segment = name\n .toLowerCase()\n .replace(/[^a-z0-9_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 64);\n if (!segment) throw new Error(`Invalid schedule name: ${JSON.stringify(name)}`);\n return segment;\n}\n\n/** Read `manifest.json` next to a directory-form extension's entrypoint. */\nfunction readManifest(entrypoint: string): ExtensionManifest {\n if (!/^index\\.(mjs|js)$/.test(basename(entrypoint))) return {};\n const manifestPath = join(dirname(entrypoint), \"manifest.json\");\n if (!existsSync(manifestPath)) return {};\n try {\n const parsed: unknown = JSON.parse(readFileSync(manifestPath, \"utf-8\"));\n if (!parsed || typeof parsed !== \"object\") return {};\n const record = parsed as Record<string, unknown>;\n return {\n name: typeof record.name === \"string\" ? record.name : undefined,\n version: typeof record.version === \"string\" ? record.version : undefined,\n description: typeof record.description === \"string\" ? record.description : undefined,\n };\n } catch (err) {\n log.logWarning(`Ignoring malformed extension manifest: ${manifestPath}`, String(err));\n return {};\n }\n}\n\n/**\n * Skills shipped inside a directory-form extension (`<dir>/skills/`).\n * Marked inline: extension files live under the host-only state dir, which\n * sandbox containers cannot read, so skill bodies must ride in the prompt.\n */\nfunction loadExtensionSkills(entrypoint: string, slug: string): MikanSkill[] {\n if (!/^index\\.(mjs|js)$/.test(basename(entrypoint))) return [];\n const skillsDir = join(dirname(entrypoint), \"skills\");\n if (!existsSync(skillsDir)) return [];\n const result = loadSkillsFromDir({ dir: skillsDir, source: `extension:${slug}` });\n for (const diagnostic of result.diagnostics) {\n log.logWarning(`Extension skill warning (${slug}): ${diagnostic.message}`, diagnostic.path);\n }\n for (const skill of result.skills) skill.inline = true;\n return result.skills;\n}\n\nconst SCHEDULE_FILE_SUFFIX = \".json\";\n\nfunction schedulePrefix(slug: string, conversationId: string): string {\n return `ext.${slug}.${scheduleNameSegment(conversationId)}.`;\n}\n\nfunction payloadFromSpec(\n spec: ExtensionScheduleSpec,\n conversationId: string,\n): ExtensionSchedulePayload {\n if (spec.type === \"periodic\") {\n if (!spec.schedule || !spec.timezone) {\n throw new Error(\"periodic schedule requires schedule and timezone\");\n }\n return {\n type: \"periodic\",\n conversationId,\n text: spec.text,\n schedule: spec.schedule,\n timezone: spec.timezone,\n ...(spec.platform ? { platform: spec.platform } : {}),\n };\n }\n if (!spec.at) throw new Error(\"one-shot schedule requires at\");\n return {\n type: \"one-shot\",\n conversationId,\n text: spec.text,\n at: spec.at,\n ...(spec.platform ? { platform: spec.platform } : {}),\n };\n}\n\nfunction specFromPayload(payload: ExtensionSchedulePayload): ExtensionScheduleSpec {\n if (payload.type === \"periodic\") {\n return {\n type: \"periodic\",\n schedule: payload.schedule ?? \"\",\n timezone: payload.timezone ?? \"\",\n text: payload.text,\n ...(payload.platform ? { platform: payload.platform } : {}),\n };\n }\n return {\n type: \"one-shot\",\n at: payload.at ?? \"\",\n text: payload.text,\n ...(payload.platform ? { platform: payload.platform } : {}),\n };\n}\n\n/** Build the v2 api surface for one extension over the injected services. */\nfunction buildExtensionApi(params: {\n name: string;\n slug: string;\n registry: ExtensionRegistry;\n context: LoadExtensionsOptions[\"context\"];\n services: ExtensionHostServices;\n}): MikanExtensionApi {\n const { name, slug, registry, context, services } = params;\n const stateDir = services.stateDir ?? join(homedir(), \".mikan\");\n const conversationId = context.conversationId;\n\n const requireScheduleStore = () => {\n if (!services.scheduleStore) {\n throw new Error(\"api.schedules is unavailable: this context provides no schedule store\");\n }\n return services.scheduleStore;\n };\n\n return {\n on: (hook, handler) => registry.register(name, hook, handler),\n registerTool: (tool: AgentTool) => registry.registerTool(tool),\n log: (message: string) => log.logInfo(`[extension:${name}] ${message}`),\n context,\n paths: {\n get dataDir(): string {\n const dir = join(stateDir, \"extension-data\", slug);\n mkdirSync(dir, { recursive: true });\n return dir;\n },\n },\n secrets: {\n get: (key: string) => services.resolveSecrets?.(slug)[key],\n list: () => Object.keys(services.resolveSecrets?.(slug) ?? {}),\n },\n schedules: {\n upsert: async (scheduleName, spec) => {\n const store = requireScheduleStore();\n const filename = `${schedulePrefix(slug, conversationId)}${scheduleNameSegment(scheduleName)}${SCHEDULE_FILE_SUFFIX}`;\n await store.write(filename, payloadFromSpec(spec, conversationId));\n },\n delete: async (scheduleName) => {\n const store = requireScheduleStore();\n const filename = `${schedulePrefix(slug, conversationId)}${scheduleNameSegment(scheduleName)}${SCHEDULE_FILE_SUFFIX}`;\n return store.delete(filename);\n },\n list: async () => {\n const store = requireScheduleStore();\n const prefix = schedulePrefix(slug, conversationId);\n const infos: ExtensionScheduleInfo[] = [];\n for (const entry of await store.list()) {\n if (!entry.filename.startsWith(prefix)) continue;\n if (!entry.filename.endsWith(SCHEDULE_FILE_SUFFIX)) continue;\n infos.push({\n name: entry.filename.slice(prefix.length, -SCHEDULE_FILE_SUFFIX.length),\n spec: specFromPayload(entry.payload),\n });\n }\n return infos;\n },\n },\n notify: async (text: string) => {\n if (!services.postMessage) {\n throw new Error(\"api.notify is unavailable: this context provides no platform messaging\");\n }\n await services.postMessage(conversationId, text);\n },\n };\n}\n\nfunction resolveActivate(moduleExports: unknown): MikanExtensionModule | undefined {\n if (!moduleExports || typeof moduleExports !== \"object\") return undefined;\n const candidates: unknown[] = [(moduleExports as { default?: unknown }).default, moduleExports];\n for (const candidate of candidates) {\n if (typeof candidate === \"function\") {\n return { activate: candidate as MikanExtensionActivate };\n }\n if (candidate && typeof candidate === \"object\") {\n const activate = (candidate as { activate?: unknown }).activate;\n if (typeof activate === \"function\") {\n return {\n name:\n typeof (candidate as { name?: unknown }).name === \"string\"\n ? (candidate as { name: string }).name\n : undefined,\n activate: activate as MikanExtensionActivate,\n };\n }\n }\n }\n return undefined;\n}\n\n/** Load and activate all extensions found in the given directories. */\nexport async function loadExtensions(\n options: LoadExtensionsOptions,\n): Promise<LoadExtensionsResult> {\n const registry = new ExtensionRegistry();\n const extensions: LoadedExtension[] = [];\n const errors: ExtensionLoadError[] = [];\n const skills: MikanSkill[] = [];\n const services = options.services ?? {};\n\n for (const dir of options.dirs) {\n for (const entrypoint of discoverExtensionEntrypoints(dir)) {\n try {\n const moduleUrl = `${pathToFileURL(entrypoint).href}?t=${Date.now()}`;\n const moduleExports: unknown = await import(moduleUrl);\n const extension = resolveActivate(moduleExports);\n if (!extension) {\n errors.push({\n path: entrypoint,\n error: \"extension must export an activate function (default or named)\",\n });\n continue;\n }\n const slug = extensionSlug(entrypoint);\n const manifest = readManifest(entrypoint);\n const name = manifest.name ?? extension.name ?? slug;\n const api = buildExtensionApi({\n name,\n slug,\n registry,\n context: options.context,\n services,\n });\n await extension.activate(api);\n const extensionSkills = loadExtensionSkills(entrypoint, slug);\n skills.push(...extensionSkills);\n extensions.push({\n name,\n path: entrypoint,\n slug,\n version: manifest.version,\n description: manifest.description,\n skills: extensionSkills,\n });\n } catch (err) {\n errors.push({\n path: entrypoint,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n return { registry, extensions, errors, skills };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/harness/extensions/loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extension discovery and activation.\n *\n * Extension modules execute inside the mikan host process with its full\n * privileges (platform tokens, vault, host filesystem). They must therefore\n * only ever load from host-controlled directories: installing an extension\n * is an administrator action. In particular, extension directories must not\n * live under paths mounted into sandbox containers (workspace and\n * conversation dirs in image mode), or code written from inside the sandbox\n * would be imported into the host process — a sandbox escape.\n * {@link defaultExtensionDirs} returns the canonical host-only locations\n * under mikan's state dir.\n *\n * Directories are scanned in order; all discovered extensions activate.\n * Accepted layouts:\n *\n * - `extensions/<name>.mjs` / `extensions/<name>.js`\n * - `extensions/<name>/index.mjs` / `extensions/<name>/index.js`\n *\n * Modules are imported with a cache-busting query so edited extensions are\n * picked up when a new harness instance is created for a conversation.\n */\nimport { existsSync, mkdirSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join } from \"path\";\nimport { pathToFileURL } from \"url\";\nimport type { AgentTool, ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport * as log from \"../../log.js\";\nimport { loadSkillsFromDir, type MikanSkill } from \"../skills.js\";\nimport { ExtensionRegistry } from \"./registry.js\";\nimport type {\n ExtensionHostServices,\n ExtensionLoadError,\n ExtensionManifest,\n ExtensionSchedulePayload,\n ExtensionScheduleInfo,\n ExtensionScheduleSpec,\n LoadedExtension,\n MikanExtensionActivate,\n MikanExtensionApi,\n MikanExtensionModule,\n} from \"./types.js\";\n\nconst EXTENSION_FILE_PATTERN = /\\.(mjs|js)$/;\n\n/**\n * Canonical host-only extension code directories for a conversation, in load\n * order: `<stateDir>/global/extensions` (all conversations) then\n * `<stateDir>/conversations/<conversationId>/extensions` (that conversation\n * only). `global` and `conversations` are the two sibling scopes; see\n * `LAYOUT.md`. Conversation ids are used verbatim (see LAYOUT.md § Casing).\n */\nexport function defaultExtensionDirs(\n conversationId: string,\n stateDir: string = join(homedir(), \".mikan\"),\n): string[] {\n return [\n join(stateDir, \"global\", \"extensions\"),\n join(stateDir, \"conversations\", conversationId, \"extensions\"),\n ];\n}\n\n/** Discovery-only view of an installed extension (module is not imported). */\nexport interface InstalledExtensionInfo {\n /** Display name: manifest name, falling back to the slug. */\n name: string;\n slug: string;\n path: string;\n /** Scan directory the extension was found in. */\n dir: string;\n version?: string;\n description?: string;\n /** Names of skills shipped in the extension's skills/ directory. */\n skillNames: string[];\n}\n\n/**\n * List installed extensions without importing or activating them — safe for\n * inventory surfaces (commands, portal). Activation side effects (schedule\n * upserts, tool registration) only happen in {@link loadExtensions}.\n */\nexport function listInstalledExtensions(dirs: string[]): InstalledExtensionInfo[] {\n const infos: InstalledExtensionInfo[] = [];\n for (const dir of dirs) {\n for (const entrypoint of discoverExtensionEntrypoints(dir)) {\n const slug = extensionSlug(entrypoint);\n const manifest = readManifest(entrypoint);\n infos.push({\n name: manifest.name ?? slug,\n slug,\n path: entrypoint,\n dir,\n version: manifest.version,\n description: manifest.description,\n skillNames: loadExtensionSkills(entrypoint, slug).map((skill) => skill.name),\n });\n }\n }\n return infos;\n}\n\nexport interface LoadExtensionsOptions {\n /** Directories to scan for extensions. Missing directories are skipped. */\n dirs: string[];\n context: {\n conversationId: string;\n workspaceDir: string;\n model: Model<Api>;\n thinkingLevel: ThinkingLevel;\n };\n /** Embedder-provided services backing the v2 api surface. */\n services?: ExtensionHostServices;\n}\n\nexport interface LoadExtensionsResult {\n registry: ExtensionRegistry;\n extensions: LoadedExtension[];\n errors: ExtensionLoadError[];\n /** Skills contributed by extensions (inline: bodies ride in the prompt). */\n skills: MikanSkill[];\n}\n\nfunction discoverExtensionEntrypoints(dir: string): string[] {\n if (!existsSync(dir)) return [];\n const entrypoints: string[] = [];\n for (const name of readdirSync(dir).toSorted()) {\n if (name.startsWith(\".\")) continue;\n if (/^index\\.(mjs|js)$/.test(name)) {\n // An index file at the scan root means the extension's contents were\n // copied into the scope directory itself; the slug would degenerate to\n // the scope name (e.g. the conversation id), mis-keying its data dir,\n // secrets, and schedules. Require a named form instead.\n log.logWarning(\n `Ignoring extension index file at scope root: ${join(dir, name)}`,\n \"install into a named subdirectory (e.g. extensions/<scope>/my-ext/index.mjs)\",\n );\n continue;\n }\n const fullPath = join(dir, name);\n let stats;\n try {\n stats = statSync(fullPath);\n } catch {\n continue;\n }\n if (stats.isFile() && EXTENSION_FILE_PATTERN.test(name)) {\n entrypoints.push(fullPath);\n continue;\n }\n if (stats.isDirectory()) {\n for (const candidate of [\"index.mjs\", \"index.js\"]) {\n const indexPath = join(fullPath, candidate);\n if (existsSync(indexPath)) {\n entrypoints.push(indexPath);\n break;\n }\n }\n }\n }\n return entrypoints;\n}\n\n/**\n * Filesystem-safe identifier for an extension, derived from its install\n * location (directory name for `<name>/index.mjs`, file basename otherwise).\n * The slug keys the extension's data dir, secrets vault, and schedule\n * ownership — installing the same extension globally and per-conversation\n * shares one slug and therefore one data dir.\n */\nexport function extensionSlug(entrypoint: string): string {\n const base = /^index\\.(mjs|js)$/.test(basename(entrypoint))\n ? basename(dirname(entrypoint))\n : basename(entrypoint).replace(EXTENSION_FILE_PATTERN, \"\");\n const slug = base\n .toLowerCase()\n .replace(/[^a-z0-9_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 64);\n return slug || \"extension\";\n}\n\n/** Sanitize an extension-chosen schedule name into a filename segment. */\nfunction scheduleNameSegment(name: string): string {\n const segment = name\n .toLowerCase()\n .replace(/[^a-z0-9_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 64);\n if (!segment) throw new Error(`Invalid schedule name: ${JSON.stringify(name)}`);\n return segment;\n}\n\n/** Read `manifest.json` next to a directory-form extension's entrypoint. */\nfunction readManifest(entrypoint: string): ExtensionManifest {\n if (!/^index\\.(mjs|js)$/.test(basename(entrypoint))) return {};\n const manifestPath = join(dirname(entrypoint), \"manifest.json\");\n if (!existsSync(manifestPath)) return {};\n try {\n const parsed: unknown = JSON.parse(readFileSync(manifestPath, \"utf-8\"));\n if (!parsed || typeof parsed !== \"object\") return {};\n const record = parsed as Record<string, unknown>;\n return {\n name: typeof record.name === \"string\" ? record.name : undefined,\n version: typeof record.version === \"string\" ? record.version : undefined,\n description: typeof record.description === \"string\" ? record.description : undefined,\n };\n } catch (err) {\n log.logWarning(`Ignoring malformed extension manifest: ${manifestPath}`, String(err));\n return {};\n }\n}\n\n/**\n * Skills shipped inside a directory-form extension (`<dir>/skills/`).\n * Marked inline: extension files live under the host-only state dir, which\n * sandbox containers cannot read, so skill bodies must ride in the prompt.\n */\nfunction loadExtensionSkills(entrypoint: string, slug: string): MikanSkill[] {\n if (!/^index\\.(mjs|js)$/.test(basename(entrypoint))) return [];\n const skillsDir = join(dirname(entrypoint), \"skills\");\n if (!existsSync(skillsDir)) return [];\n const result = loadSkillsFromDir({ dir: skillsDir, source: `extension:${slug}` });\n for (const diagnostic of result.diagnostics) {\n log.logWarning(`Extension skill warning (${slug}): ${diagnostic.message}`, diagnostic.path);\n }\n for (const skill of result.skills) skill.inline = true;\n return result.skills;\n}\n\nconst SCHEDULE_FILE_SUFFIX = \".json\";\n\nfunction schedulePrefix(slug: string, conversationId: string): string {\n return `ext.${slug}.${scheduleNameSegment(conversationId)}.`;\n}\n\nfunction payloadFromSpec(\n spec: ExtensionScheduleSpec,\n conversationId: string,\n): ExtensionSchedulePayload {\n if (spec.type === \"periodic\") {\n if (!spec.schedule || !spec.timezone) {\n throw new Error(\"periodic schedule requires schedule and timezone\");\n }\n return {\n type: \"periodic\",\n conversationId,\n text: spec.text,\n schedule: spec.schedule,\n timezone: spec.timezone,\n ...(spec.platform ? { platform: spec.platform } : {}),\n };\n }\n if (!spec.at) throw new Error(\"one-shot schedule requires at\");\n return {\n type: \"one-shot\",\n conversationId,\n text: spec.text,\n at: spec.at,\n ...(spec.platform ? { platform: spec.platform } : {}),\n };\n}\n\nfunction specFromPayload(payload: ExtensionSchedulePayload): ExtensionScheduleSpec {\n if (payload.type === \"periodic\") {\n return {\n type: \"periodic\",\n schedule: payload.schedule ?? \"\",\n timezone: payload.timezone ?? \"\",\n text: payload.text,\n ...(payload.platform ? { platform: payload.platform } : {}),\n };\n }\n return {\n type: \"one-shot\",\n at: payload.at ?? \"\",\n text: payload.text,\n ...(payload.platform ? { platform: payload.platform } : {}),\n };\n}\n\n/** Build the v2 api surface for one extension over the injected services. */\nfunction buildExtensionApi(params: {\n name: string;\n slug: string;\n registry: ExtensionRegistry;\n context: LoadExtensionsOptions[\"context\"];\n services: ExtensionHostServices;\n}): MikanExtensionApi {\n const { name, slug, registry, context, services } = params;\n const stateDir = services.stateDir ?? join(homedir(), \".mikan\");\n const conversationId = context.conversationId;\n\n const requireScheduleStore = () => {\n if (!services.scheduleStore) {\n throw new Error(\"api.schedules is unavailable: this context provides no schedule store\");\n }\n return services.scheduleStore;\n };\n\n return {\n on: (hook, handler) => registry.register(name, hook, handler),\n registerTool: (tool: AgentTool) => registry.registerTool(tool),\n log: (message: string) => log.logInfo(`[extension:${name}] ${message}`),\n context,\n paths: {\n get dataDir(): string {\n // This conversation's own data, co-located with the conversation's\n // other host-only assets; removed when the conversation is deleted.\n const dir = join(stateDir, \"conversations\", conversationId, \"extension-data\", slug);\n mkdirSync(dir, { recursive: true });\n return dir;\n },\n get sharedDataDir(): string {\n // Cross-conversation data under the sibling `global` scope.\n const dir = join(stateDir, \"global\", \"extension-data\", slug);\n mkdirSync(dir, { recursive: true });\n return dir;\n },\n },\n secrets: {\n get: (key: string) => services.resolveSecrets?.(slug)[key],\n list: () => Object.keys(services.resolveSecrets?.(slug) ?? {}),\n },\n schedules: {\n upsert: async (scheduleName, spec) => {\n const store = requireScheduleStore();\n const filename = `${schedulePrefix(slug, conversationId)}${scheduleNameSegment(scheduleName)}${SCHEDULE_FILE_SUFFIX}`;\n await store.write(filename, payloadFromSpec(spec, conversationId));\n },\n delete: async (scheduleName) => {\n const store = requireScheduleStore();\n const filename = `${schedulePrefix(slug, conversationId)}${scheduleNameSegment(scheduleName)}${SCHEDULE_FILE_SUFFIX}`;\n return store.delete(filename);\n },\n list: async () => {\n const store = requireScheduleStore();\n const prefix = schedulePrefix(slug, conversationId);\n const infos: ExtensionScheduleInfo[] = [];\n for (const entry of await store.list()) {\n if (!entry.filename.startsWith(prefix)) continue;\n if (!entry.filename.endsWith(SCHEDULE_FILE_SUFFIX)) continue;\n infos.push({\n name: entry.filename.slice(prefix.length, -SCHEDULE_FILE_SUFFIX.length),\n spec: specFromPayload(entry.payload),\n });\n }\n return infos;\n },\n },\n notify: async (text: string) => {\n if (!services.postMessage) {\n throw new Error(\"api.notify is unavailable: this context provides no platform messaging\");\n }\n await services.postMessage(conversationId, text);\n },\n };\n}\n\nfunction resolveActivate(moduleExports: unknown): MikanExtensionModule | undefined {\n if (!moduleExports || typeof moduleExports !== \"object\") return undefined;\n const candidates: unknown[] = [(moduleExports as { default?: unknown }).default, moduleExports];\n for (const candidate of candidates) {\n if (typeof candidate === \"function\") {\n return { activate: candidate as MikanExtensionActivate };\n }\n if (candidate && typeof candidate === \"object\") {\n const activate = (candidate as { activate?: unknown }).activate;\n if (typeof activate === \"function\") {\n return {\n name:\n typeof (candidate as { name?: unknown }).name === \"string\"\n ? (candidate as { name: string }).name\n : undefined,\n activate: activate as MikanExtensionActivate,\n };\n }\n }\n }\n return undefined;\n}\n\n/** Load and activate all extensions found in the given directories. */\nexport async function loadExtensions(\n options: LoadExtensionsOptions,\n): Promise<LoadExtensionsResult> {\n const registry = new ExtensionRegistry();\n const extensions: LoadedExtension[] = [];\n const errors: ExtensionLoadError[] = [];\n const skills: MikanSkill[] = [];\n const services = options.services ?? {};\n\n for (const dir of options.dirs) {\n for (const entrypoint of discoverExtensionEntrypoints(dir)) {\n try {\n const moduleUrl = `${pathToFileURL(entrypoint).href}?t=${Date.now()}`;\n const moduleExports: unknown = await import(moduleUrl);\n const extension = resolveActivate(moduleExports);\n if (!extension) {\n errors.push({\n path: entrypoint,\n error: \"extension must export an activate function (default or named)\",\n });\n continue;\n }\n const slug = extensionSlug(entrypoint);\n const manifest = readManifest(entrypoint);\n const name = manifest.name ?? extension.name ?? slug;\n const api = buildExtensionApi({\n name,\n slug,\n registry,\n context: options.context,\n services,\n });\n await extension.activate(api);\n const extensionSkills = loadExtensionSkills(entrypoint, slug);\n skills.push(...extensionSkills);\n extensions.push({\n name,\n path: entrypoint,\n slug,\n version: manifest.version,\n description: manifest.description,\n skills: extensionSkills,\n });\n } catch (err) {\n errors.push({\n path: entrypoint,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n return { registry, extensions, errors, skills };\n}\n"]}
|
|
@@ -166,14 +166,28 @@ export interface MikanExtensionApi {
|
|
|
166
166
|
readonly model: Model<Api>;
|
|
167
167
|
readonly thinkingLevel: ThinkingLevel;
|
|
168
168
|
};
|
|
169
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* Host-only filesystem locations owned by this extension (never mounted
|
|
171
|
+
* into sandbox containers). Which one you use is a declaration: `dataDir`
|
|
172
|
+
* for conversation-scoped state (the safe default — isolation for free),
|
|
173
|
+
* `sharedDataDir` for deliberately cross-conversation state (you own tenant
|
|
174
|
+
* partitioning and concurrency). See `LAYOUT.md`.
|
|
175
|
+
*/
|
|
170
176
|
readonly paths: {
|
|
171
177
|
/**
|
|
172
|
-
*
|
|
173
|
-
* (
|
|
174
|
-
*
|
|
178
|
+
* This conversation's private data directory
|
|
179
|
+
* (`conversations/<conversationId>/extension-data/<slug>`), created on
|
|
180
|
+
* first access. Default choice: co-located with the conversation, so it
|
|
181
|
+
* disappears when the conversation is deleted.
|
|
175
182
|
*/
|
|
176
183
|
readonly dataDir: string;
|
|
184
|
+
/**
|
|
185
|
+
* Data shared across all conversations this extension serves
|
|
186
|
+
* (`global/extension-data/<slug>`), created on first access. Explicit
|
|
187
|
+
* opt-in for multi-conversation applications (e.g. a PM tool with
|
|
188
|
+
* cross-channel views); key rows by conversation id yourself.
|
|
189
|
+
*/
|
|
190
|
+
readonly sharedDataDir: string;
|
|
177
191
|
};
|
|
178
192
|
/**
|
|
179
193
|
* Read-only secrets from the embedder's vault
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/harness/extensions/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/harness/extensions/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/harness/extensions/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * mikan extension system, v1.\n *\n * Extensions are ES modules that export an `activate` function (either as\n * the default export or a named `activate` export, optionally wrapped in an\n * object that also carries a `name`). `activate` receives a\n * {@link MikanExtensionApi} and registers hooks and tools.\n *\n * ```js\n * // extensions/audit.mjs\n * export default function activate(api) {\n * api.on(\"tool_call\", ({ toolName, args }) => {\n * if (toolName === \"bash\" && String(args.command).includes(\"rm -rf /\")) {\n * return { block: true, reason: \"destructive command\" };\n * }\n * });\n * }\n * ```\n *\n * Hooks run in registration order. For hooks with results, the first\n * non-undefined result wins (v1 semantics; later versions may merge).\n * Hook errors are logged and never crash a run.\n */\nimport type { AgentMessage, AgentTool, ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, ImageContent, Model, TextContent } from \"@earendil-works/pi-ai\";\nimport type { MikanSkill } from \"../skills.js\";\nimport type { CompactionEntry } from \"../types.js\";\n\nexport interface BeforeAgentStartHookEvent {\n prompt: string;\n images?: ImageContent[];\n systemPrompt: string;\n}\n\nexport interface BeforeAgentStartHookResult {\n /** Replace the system prompt for this turn. */\n systemPrompt?: string;\n}\n\nexport interface ToolCallHookEvent {\n toolCallId: string;\n toolName: string;\n args: unknown;\n}\n\nexport interface ToolCallHookResult {\n /** Block the tool call; the model receives an error tool result instead. */\n block?: boolean;\n reason?: string;\n}\n\nexport interface ToolResultHookEvent {\n toolCallId: string;\n toolName: string;\n args: unknown;\n content: (TextContent | ImageContent)[];\n isError: boolean;\n}\n\nexport interface MessageEndHookEvent {\n message: AgentMessage;\n}\n\nexport interface TurnEndHookEvent {\n messages: AgentMessage[];\n}\n\nexport interface SessionCompactHookEvent {\n entry: CompactionEntry;\n reason: \"threshold\" | \"overflow\" | \"manual\";\n}\n\n/** Map of hook names to handler signatures. */\nexport interface MikanHookMap {\n before_agent_start: (\n event: BeforeAgentStartHookEvent,\n ) =>\n | BeforeAgentStartHookResult\n | undefined\n | void\n | Promise<BeforeAgentStartHookResult | undefined | void>;\n tool_call: (\n event: ToolCallHookEvent,\n ) => ToolCallHookResult | undefined | void | Promise<ToolCallHookResult | undefined | void>;\n tool_result: (event: ToolResultHookEvent) => void | Promise<void>;\n message_end: (event: MessageEndHookEvent) => void | Promise<void>;\n turn_end: (event: TurnEndHookEvent) => void | Promise<void>;\n session_compact: (event: SessionCompactHookEvent) => void | Promise<void>;\n}\n\nexport type MikanHookName = keyof MikanHookMap;\n\n// ── v2: schedules ────────────────────────────────────────────────────────────\n\n/**\n * A schedule contributed by an extension. Fires an autonomous agent run in\n * this conversation with `text` as the task prompt (the run does not inherit\n * conversation history — write `text` self-contained).\n */\nexport type ExtensionScheduleSpec =\n | {\n type: \"periodic\";\n /** Cron expression (croner syntax). */\n schedule: string;\n /** IANA timezone, e.g. \"Asia/Taipei\". */\n timezone: string;\n text: string;\n /** Target platform; optional when only one platform is running. */\n platform?: string;\n }\n | {\n type: \"one-shot\";\n /** ISO 8601 timestamp with offset. */\n at: string;\n text: string;\n platform?: string;\n };\n\nexport interface ExtensionScheduleInfo {\n /** Extension-chosen schedule name. */\n name: string;\n spec: ExtensionScheduleSpec;\n}\n\n/**\n * Event-file payload the harness hands to the embedder's schedule store.\n * Mirrors mikan's event-file shape; `platform` may be omitted when the\n * embedder runs a single platform.\n */\nexport interface ExtensionSchedulePayload {\n type: \"one-shot\" | \"periodic\";\n conversationId: string;\n text: string;\n platform?: string;\n at?: string;\n schedule?: string;\n timezone?: string;\n}\n\n// ── v2: embedder-injected services ───────────────────────────────────────────\n\n/**\n * Persistence backend for extension schedules, injected by the embedder\n * (mikan backs this with event files watched by its EventsWatcher).\n * Filenames are fully qualified by the harness (`ext-<slug>-<name>.json`).\n */\nexport interface ExtensionScheduleStore {\n write(filename: string, payload: ExtensionSchedulePayload): Promise<void>;\n delete(filename: string): Promise<boolean>;\n /** List all schedule files, harness filters by ownership prefix. */\n list(): Promise<Array<{ filename: string; payload: ExtensionSchedulePayload }>>;\n}\n\n/**\n * Host services injected into extensions by the embedder. All fields are\n * optional: the corresponding api surface throws an informative error when\n * the running context does not provide the service.\n */\nexport interface ExtensionHostServices {\n /** State dir for per-extension data dirs; defaults to `~/.mikan`. */\n stateDir?: string;\n /** Schedule persistence; enables `api.schedules`. */\n scheduleStore?: ExtensionScheduleStore;\n /** Post a message to a conversation without an agent run; enables `api.notify`. */\n postMessage?: (conversationId: string, text: string, platform?: string) => Promise<void>;\n /** Resolve read-only secrets for an extension slug; enables `api.secrets`. */\n resolveSecrets?: (slug: string) => Record<string, string>;\n}\n\n// ── v2: manifest ─────────────────────────────────────────────────────────────\n\n/**\n * Optional `manifest.json` next to a directory-form extension's entrypoint.\n * `name` is the display name only; the slug (data dir, secrets, schedule\n * ownership) always derives from the install path so identity is\n * admin-controlled and stable across manifest edits.\n */\nexport interface ExtensionManifest {\n name?: string;\n version?: string;\n description?: string;\n}\n\n/** API handed to an extension's `activate` function. */\nexport interface MikanExtensionApi {\n /** Register a hook handler. */\n on<T extends MikanHookName>(hook: T, handler: MikanHookMap[T]): void;\n /** Contribute an additional agent tool. */\n registerTool(tool: AgentTool): void;\n /** Extension-scoped logging that lands in mikan's structured log. */\n log(message: string): void;\n /** Context about the conversation this harness instance serves. */\n readonly context: {\n readonly conversationId: string;\n readonly workspaceDir: string;\n readonly model: Model<Api>;\n readonly thinkingLevel: ThinkingLevel;\n };\n
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/harness/extensions/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * mikan extension system, v1.\n *\n * Extensions are ES modules that export an `activate` function (either as\n * the default export or a named `activate` export, optionally wrapped in an\n * object that also carries a `name`). `activate` receives a\n * {@link MikanExtensionApi} and registers hooks and tools.\n *\n * ```js\n * // extensions/audit.mjs\n * export default function activate(api) {\n * api.on(\"tool_call\", ({ toolName, args }) => {\n * if (toolName === \"bash\" && String(args.command).includes(\"rm -rf /\")) {\n * return { block: true, reason: \"destructive command\" };\n * }\n * });\n * }\n * ```\n *\n * Hooks run in registration order. For hooks with results, the first\n * non-undefined result wins (v1 semantics; later versions may merge).\n * Hook errors are logged and never crash a run.\n */\nimport type { AgentMessage, AgentTool, ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, ImageContent, Model, TextContent } from \"@earendil-works/pi-ai\";\nimport type { MikanSkill } from \"../skills.js\";\nimport type { CompactionEntry } from \"../types.js\";\n\nexport interface BeforeAgentStartHookEvent {\n prompt: string;\n images?: ImageContent[];\n systemPrompt: string;\n}\n\nexport interface BeforeAgentStartHookResult {\n /** Replace the system prompt for this turn. */\n systemPrompt?: string;\n}\n\nexport interface ToolCallHookEvent {\n toolCallId: string;\n toolName: string;\n args: unknown;\n}\n\nexport interface ToolCallHookResult {\n /** Block the tool call; the model receives an error tool result instead. */\n block?: boolean;\n reason?: string;\n}\n\nexport interface ToolResultHookEvent {\n toolCallId: string;\n toolName: string;\n args: unknown;\n content: (TextContent | ImageContent)[];\n isError: boolean;\n}\n\nexport interface MessageEndHookEvent {\n message: AgentMessage;\n}\n\nexport interface TurnEndHookEvent {\n messages: AgentMessage[];\n}\n\nexport interface SessionCompactHookEvent {\n entry: CompactionEntry;\n reason: \"threshold\" | \"overflow\" | \"manual\";\n}\n\n/** Map of hook names to handler signatures. */\nexport interface MikanHookMap {\n before_agent_start: (\n event: BeforeAgentStartHookEvent,\n ) =>\n | BeforeAgentStartHookResult\n | undefined\n | void\n | Promise<BeforeAgentStartHookResult | undefined | void>;\n tool_call: (\n event: ToolCallHookEvent,\n ) => ToolCallHookResult | undefined | void | Promise<ToolCallHookResult | undefined | void>;\n tool_result: (event: ToolResultHookEvent) => void | Promise<void>;\n message_end: (event: MessageEndHookEvent) => void | Promise<void>;\n turn_end: (event: TurnEndHookEvent) => void | Promise<void>;\n session_compact: (event: SessionCompactHookEvent) => void | Promise<void>;\n}\n\nexport type MikanHookName = keyof MikanHookMap;\n\n// ── v2: schedules ────────────────────────────────────────────────────────────\n\n/**\n * A schedule contributed by an extension. Fires an autonomous agent run in\n * this conversation with `text` as the task prompt (the run does not inherit\n * conversation history — write `text` self-contained).\n */\nexport type ExtensionScheduleSpec =\n | {\n type: \"periodic\";\n /** Cron expression (croner syntax). */\n schedule: string;\n /** IANA timezone, e.g. \"Asia/Taipei\". */\n timezone: string;\n text: string;\n /** Target platform; optional when only one platform is running. */\n platform?: string;\n }\n | {\n type: \"one-shot\";\n /** ISO 8601 timestamp with offset. */\n at: string;\n text: string;\n platform?: string;\n };\n\nexport interface ExtensionScheduleInfo {\n /** Extension-chosen schedule name. */\n name: string;\n spec: ExtensionScheduleSpec;\n}\n\n/**\n * Event-file payload the harness hands to the embedder's schedule store.\n * Mirrors mikan's event-file shape; `platform` may be omitted when the\n * embedder runs a single platform.\n */\nexport interface ExtensionSchedulePayload {\n type: \"one-shot\" | \"periodic\";\n conversationId: string;\n text: string;\n platform?: string;\n at?: string;\n schedule?: string;\n timezone?: string;\n}\n\n// ── v2: embedder-injected services ───────────────────────────────────────────\n\n/**\n * Persistence backend for extension schedules, injected by the embedder\n * (mikan backs this with event files watched by its EventsWatcher).\n * Filenames are fully qualified by the harness (`ext-<slug>-<name>.json`).\n */\nexport interface ExtensionScheduleStore {\n write(filename: string, payload: ExtensionSchedulePayload): Promise<void>;\n delete(filename: string): Promise<boolean>;\n /** List all schedule files, harness filters by ownership prefix. */\n list(): Promise<Array<{ filename: string; payload: ExtensionSchedulePayload }>>;\n}\n\n/**\n * Host services injected into extensions by the embedder. All fields are\n * optional: the corresponding api surface throws an informative error when\n * the running context does not provide the service.\n */\nexport interface ExtensionHostServices {\n /** State dir for per-extension data dirs; defaults to `~/.mikan`. */\n stateDir?: string;\n /** Schedule persistence; enables `api.schedules`. */\n scheduleStore?: ExtensionScheduleStore;\n /** Post a message to a conversation without an agent run; enables `api.notify`. */\n postMessage?: (conversationId: string, text: string, platform?: string) => Promise<void>;\n /** Resolve read-only secrets for an extension slug; enables `api.secrets`. */\n resolveSecrets?: (slug: string) => Record<string, string>;\n}\n\n// ── v2: manifest ─────────────────────────────────────────────────────────────\n\n/**\n * Optional `manifest.json` next to a directory-form extension's entrypoint.\n * `name` is the display name only; the slug (data dir, secrets, schedule\n * ownership) always derives from the install path so identity is\n * admin-controlled and stable across manifest edits.\n */\nexport interface ExtensionManifest {\n name?: string;\n version?: string;\n description?: string;\n}\n\n/** API handed to an extension's `activate` function. */\nexport interface MikanExtensionApi {\n /** Register a hook handler. */\n on<T extends MikanHookName>(hook: T, handler: MikanHookMap[T]): void;\n /** Contribute an additional agent tool. */\n registerTool(tool: AgentTool): void;\n /** Extension-scoped logging that lands in mikan's structured log. */\n log(message: string): void;\n /** Context about the conversation this harness instance serves. */\n readonly context: {\n readonly conversationId: string;\n readonly workspaceDir: string;\n readonly model: Model<Api>;\n readonly thinkingLevel: ThinkingLevel;\n };\n /**\n * Host-only filesystem locations owned by this extension (never mounted\n * into sandbox containers). Which one you use is a declaration: `dataDir`\n * for conversation-scoped state (the safe default — isolation for free),\n * `sharedDataDir` for deliberately cross-conversation state (you own tenant\n * partitioning and concurrency). See `LAYOUT.md`.\n */\n readonly paths: {\n /**\n * This conversation's private data directory\n * (`conversations/<conversationId>/extension-data/<slug>`), created on\n * first access. Default choice: co-located with the conversation, so it\n * disappears when the conversation is deleted.\n */\n readonly dataDir: string;\n /**\n * Data shared across all conversations this extension serves\n * (`global/extension-data/<slug>`), created on first access. Explicit\n * opt-in for multi-conversation applications (e.g. a PM tool with\n * cross-channel views); key rows by conversation id yourself.\n */\n readonly sharedDataDir: string;\n };\n /**\n * Read-only secrets from the embedder's vault\n * (mikan: `<stateDir>/vaults/extensions/<slug>/env`).\n */\n readonly secrets: {\n get(key: string): string | undefined;\n /** Secret names only, never values. */\n list(): string[];\n };\n /**\n * Named schedules owned by this extension + conversation. Backed by the\n * embedder's schedule store; in mikan these become event files that fire\n * autonomous agent runs (hot-reloaded, persisted across restarts).\n */\n readonly schedules: {\n /** Create or replace a named schedule. */\n upsert(name: string, spec: ExtensionScheduleSpec): Promise<void>;\n /** Delete a named schedule. Returns false when it did not exist. */\n delete(name: string): Promise<boolean>;\n /** Schedules owned by this extension in this conversation. */\n list(): Promise<ExtensionScheduleInfo[]>;\n };\n /**\n * Post text into this conversation without triggering an agent run.\n * Available when the embedder provides platform messaging.\n */\n notify(text: string): Promise<void>;\n}\n\nexport type MikanExtensionActivate = (api: MikanExtensionApi) => void | Promise<void>;\n\nexport interface MikanExtensionModule {\n name?: string;\n activate: MikanExtensionActivate;\n}\n\nexport interface LoadedExtension {\n name: string;\n path: string;\n /** Filesystem-safe identifier used for data dirs, secrets, and schedules. */\n slug: string;\n version?: string;\n description?: string;\n /** Skills discovered under the extension's `skills/` directory. */\n skills: MikanSkill[];\n}\n\nexport interface ExtensionLoadError {\n path: string;\n error: string;\n}\n"]}
|