@dyyz1993/pi-coding-agent 0.74.20 → 0.74.22
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/dist/core/agent-session.d.ts +4 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +41 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-types.d.ts +7 -0
- package/dist/core/agent-types.d.ts.map +1 -1
- package/dist/core/agent-types.js +46 -2
- package/dist/core/agent-types.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +7 -2
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +5 -6
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +2 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +5 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client-types.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +18 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +12 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +35 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +45 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/package.json +4 -4
|
@@ -42,14 +42,21 @@ export interface AgentConfig {
|
|
|
42
42
|
skills?: string[];
|
|
43
43
|
hooks?: AgentHooks;
|
|
44
44
|
variables?: Record<string, string>;
|
|
45
|
+
tier?: AgentTier;
|
|
46
|
+
thinkingLevel?: string;
|
|
47
|
+
mode?: AgentMode;
|
|
48
|
+
hidden?: boolean;
|
|
45
49
|
}
|
|
46
50
|
export type AgentSource = "builtin" | "plugin" | "user" | "project" | "flag" | "policy";
|
|
51
|
+
export type AgentTier = "fast" | "pro" | "max";
|
|
52
|
+
export type AgentMode = "primary" | "subagent" | "all";
|
|
47
53
|
export interface AgentDiscoveryResult {
|
|
48
54
|
agents: AgentConfig[];
|
|
49
55
|
projectAgentsDir: string | null;
|
|
50
56
|
}
|
|
51
57
|
export declare function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[];
|
|
52
58
|
export declare function mergeAgentsByPriority(...groups: AgentConfig[][]): AgentConfig[];
|
|
59
|
+
export declare function getBuiltinAgents(): AgentConfig[];
|
|
53
60
|
export declare function discoverAgents(cwd: string, scope: AgentScope, overrideAgents?: AgentConfig[]): AgentDiscoveryResult;
|
|
54
61
|
export declare function formatAgentList(agents: AgentConfig[], maxItems: number): {
|
|
55
62
|
text: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-types.d.ts","sourceRoot":"","sources":["../../src/core/agent-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AAErD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,cAAc,GAAG,aAAa,CAAC;AAE1G,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAEvD,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,QAAQ,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,MAAM,SAAS,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAE3D,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;AAE9D,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IAEjB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAExF,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAoFD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,WAAW,EAAE,CAiEjF;AAsBD,wBAAgB,qBAAqB,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,WAAW,EAAE,CAQ/E;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,cAAc,CAAC,EAAE,WAAW,EAAE,GAAG,oBAAoB,CAqBnH;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAQ5G","sourcesContent":["/**\n * Shared agent types and discovery logic.\n *\n * Used by multiple extensions (subagent, subagent-v2, agent-permissions)\n * and exported from the public API so extensions don't need cross-plugin imports.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"path\";\nimport { getAgentDir } from \"../config.js\";\nimport { parseFrontmatter } from \"../utils/frontmatter.js\";\n\nexport type AgentScope = \"user\" | \"project\" | \"both\";\n\nexport type PermissionMode = \"auto\" | \"acceptEdits\" | \"plan\" | \"dontAsk\" | \"always-allow\" | \"always-deny\";\n\nexport type AgentColor = \"red\" | \"blue\" | \"green\" | \"yellow\" | \"purple\" | \"orange\";\n\nexport type MemoryScope = \"user\" | \"project\" | \"local\";\n\nexport type IsolationMode = \"worktree\" | \"remote\";\n\nexport interface AgentHookCommand {\n\ttype: \"command\";\n\tcommand: string;\n\tif?: string;\n\tasync?: boolean;\n}\n\nexport interface AgentHookPrompt {\n\ttype: \"prompt\";\n\tprompt: string;\n\tif?: string;\n}\n\nexport type AgentHook = AgentHookCommand | AgentHookPrompt;\n\nexport type AgentHooks = Partial<Record<string, AgentHook[]>>;\n\nexport interface AgentConfig {\n\tname: string;\n\tdescription: string;\n\ttools?: string[];\n\tdisallowedTools?: string[];\n\tmodel?: string;\n\tsystemPrompt: string;\n\tsource: AgentSource;\n\tfilePath: string;\n\n\tpermissionMode?: PermissionMode;\n\tmaxTurns?: number;\n\teffort?: string;\n\tcolor?: AgentColor;\n\tbackground?: boolean;\n\tmemory?: MemoryScope;\n\tisolation?: IsolationMode;\n\tinitialPrompt?: string;\n\tskills?: string[];\n\thooks?: AgentHooks;\n\tvariables?: Record<string, string>;\n}\n\nexport type AgentSource = \"builtin\" | \"plugin\" | \"user\" | \"project\" | \"flag\" | \"policy\";\n\nexport interface AgentDiscoveryResult {\n\tagents: AgentConfig[];\n\tprojectAgentsDir: string | null;\n}\n\nconst STRING_FIELDS: ReadonlySet<string> = new Set([\n\t\"description\",\n\t\"model\",\n\t\"permissionMode\",\n\t\"effort\",\n\t\"color\",\n\t\"memory\",\n\t\"isolation\",\n\t\"initialPrompt\",\n]);\n\nconst STRING_ARRAY_FIELDS: ReadonlySet<string> = new Set([\"tools\", \"disallowedTools\", \"skills\"]);\n\nconst BOOLEAN_FIELDS: ReadonlySet<string> = new Set([\"background\"]);\n\nconst NUMBER_FIELDS: ReadonlySet<string> = new Set([\"maxTurns\"]);\n\nfunction coerceField(key: string, raw: unknown): unknown {\n\tif (raw === undefined || raw === null) return undefined;\n\n\tif (STRING_FIELDS.has(key)) {\n\t\treturn typeof raw === \"string\" ? raw : String(raw);\n\t}\n\n\tif (STRING_ARRAY_FIELDS.has(key)) {\n\t\tif (Array.isArray(raw)) return raw.map(String);\n\t\tif (typeof raw === \"string\") {\n\t\t\treturn raw\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s: string) => s.trim())\n\t\t\t\t.filter(Boolean);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (BOOLEAN_FIELDS.has(key)) {\n\t\tif (typeof raw === \"boolean\") return raw;\n\t\tif (typeof raw === \"string\") return raw === \"true\" || raw === \"yes\";\n\t\treturn undefined;\n\t}\n\n\tif (NUMBER_FIELDS.has(key)) {\n\t\tif (typeof raw === \"number\") return raw;\n\t\tif (typeof raw === \"string\") {\n\t\t\tconst n = Number.parseInt(raw, 10);\n\t\t\treturn Number.isFinite(n) ? n : undefined;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\treturn raw;\n}\n\nfunction parseHooks(raw: unknown): AgentHooks | undefined {\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst hooks: AgentHooks = {};\n\tfor (const [event, handlers] of Object.entries(raw as Record<string, unknown>)) {\n\t\tif (!Array.isArray(handlers)) continue;\n\t\tconst parsed: AgentHook[] = [];\n\t\tfor (const h of handlers) {\n\t\t\tif (!h || typeof h !== \"object\") continue;\n\t\t\tconst obj = h as Record<string, unknown>;\n\t\t\tif (obj.type === \"command\" && typeof obj.command === \"string\") {\n\t\t\t\tparsed.push({\n\t\t\t\t\ttype: \"command\",\n\t\t\t\t\tcommand: obj.command,\n\t\t\t\t\tif: typeof obj.if === \"string\" ? obj.if : undefined,\n\t\t\t\t\tasync: obj.async === true,\n\t\t\t\t});\n\t\t\t} else if (obj.type === \"prompt\" && typeof obj.prompt === \"string\") {\n\t\t\t\tparsed.push({\n\t\t\t\t\ttype: \"prompt\",\n\t\t\t\t\tprompt: obj.prompt,\n\t\t\t\t\tif: typeof obj.if === \"string\" ? obj.if : undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tif (parsed.length > 0) hooks[event] = parsed;\n\t}\n\treturn Object.keys(hooks).length > 0 ? hooks : undefined;\n}\n\nexport function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {\n\tconst agents: AgentConfig[] = [];\n\n\tif (!fs.existsSync(dir)) {\n\t\treturn agents;\n\t}\n\n\tlet entries: fs.Dirent[];\n\ttry {\n\t\tentries = fs.readdirSync(dir, { withFileTypes: true });\n\t} catch {\n\t\treturn agents;\n\t}\n\n\tfor (const entry of entries) {\n\t\tif (!entry.name.endsWith(\".md\")) continue;\n\t\tif (!entry.isFile() && !entry.isSymbolicLink()) continue;\n\n\t\tconst filePath = path.join(dir, entry.name);\n\t\tlet content: string;\n\t\ttry {\n\t\t\tcontent = fs.readFileSync(filePath, \"utf-8\");\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(content);\n\n\t\tif (!frontmatter.name || !frontmatter.description) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst tools = coerceField(\"tools\", frontmatter.tools) as string[] | undefined;\n\t\tconst disallowedTools = coerceField(\"disallowedTools\", frontmatter.disallowedTools) as string[] | undefined;\n\t\tconst skills = coerceField(\"skills\", frontmatter.skills) as string[] | undefined;\n\t\tconst hooks = parseHooks(frontmatter.hooks);\n\t\tconst variables =\n\t\t\tfrontmatter.variables && typeof frontmatter.variables === \"object\"\n\t\t\t\t? (frontmatter.variables as Record<string, string>)\n\t\t\t\t: undefined;\n\n\t\tagents.push({\n\t\t\tname: coerceField(\"name\", frontmatter.name) as string,\n\t\t\tdescription: coerceField(\"description\", frontmatter.description) as string,\n\t\t\ttools: tools && tools.length > 0 ? tools : undefined,\n\t\t\tdisallowedTools: disallowedTools && disallowedTools.length > 0 ? disallowedTools : undefined,\n\t\t\tmodel: coerceField(\"model\", frontmatter.model) as string | undefined,\n\t\t\tsystemPrompt: body,\n\t\t\tsource,\n\t\t\tfilePath,\n\t\t\tpermissionMode: coerceField(\"permissionMode\", frontmatter.permissionMode) as PermissionMode | undefined,\n\t\t\tmaxTurns: coerceField(\"maxTurns\", frontmatter.maxTurns) as number | undefined,\n\t\t\teffort: coerceField(\"effort\", frontmatter.effort) as string | undefined,\n\t\t\tcolor: coerceField(\"color\", frontmatter.color) as AgentColor | undefined,\n\t\t\tbackground: coerceField(\"background\", frontmatter.background) as boolean | undefined,\n\t\t\tmemory: coerceField(\"memory\", frontmatter.memory) as MemoryScope | undefined,\n\t\t\tisolation: coerceField(\"isolation\", frontmatter.isolation) as IsolationMode | undefined,\n\t\t\tinitialPrompt: coerceField(\"initialPrompt\", frontmatter.initialPrompt) as string | undefined,\n\t\t\tskills: skills && skills.length > 0 ? skills : undefined,\n\t\t\thooks,\n\t\t\tvariables,\n\t\t});\n\t}\n\n\treturn agents;\n}\n\nfunction isDirectory(p: string): boolean {\n\ttry {\n\t\treturn fs.statSync(p).isDirectory();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction findNearestProjectAgentsDir(cwd: string): string | null {\n\tlet currentDir = cwd;\n\twhile (true) {\n\t\tconst candidate = path.join(currentDir, \".pi\", \"agents\");\n\t\tif (isDirectory(candidate)) return candidate;\n\n\t\tconst parentDir = path.dirname(currentDir);\n\t\tif (parentDir === currentDir) return null;\n\t\tcurrentDir = parentDir;\n\t}\n}\n\nexport function mergeAgentsByPriority(...groups: AgentConfig[][]): AgentConfig[] {\n\tconst agentMap = new Map<string, AgentConfig>();\n\tfor (const group of groups) {\n\t\tfor (const agent of group) {\n\t\t\tagentMap.set(agent.name, agent);\n\t\t}\n\t}\n\treturn Array.from(agentMap.values());\n}\n\nexport function discoverAgents(cwd: string, scope: AgentScope, overrideAgents?: AgentConfig[]): AgentDiscoveryResult {\n\tconst userDir = path.join(getAgentDir(), \"agents\");\n\tconst projectAgentsDir = findNearestProjectAgentsDir(cwd);\n\n\tconst builtinAgents: AgentConfig[] = [];\n\tconst pluginAgents: AgentConfig[] = [];\n\tconst userAgents = scope === \"project\" ? [] : loadAgentsFromDir(userDir, \"user\");\n\tconst projectAgents = scope === \"user\" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, \"project\");\n\tconst flagAgents = overrideAgents ?? [];\n\tconst policyAgents: AgentConfig[] = [];\n\n\tconst agents = mergeAgentsByPriority(\n\t\tbuiltinAgents,\n\t\tpluginAgents,\n\t\tuserAgents,\n\t\tprojectAgents,\n\t\tflagAgents,\n\t\tpolicyAgents,\n\t);\n\n\treturn { agents, projectAgentsDir };\n}\n\nexport function formatAgentList(agents: AgentConfig[], maxItems: number): { text: string; remaining: number } {\n\tif (agents.length === 0) return { text: \"none\", remaining: 0 };\n\tconst listed = agents.slice(0, maxItems);\n\tconst remaining = agents.length - listed.length;\n\treturn {\n\t\ttext: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join(\"; \"),\n\t\tremaining,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent-types.d.ts","sourceRoot":"","sources":["../../src/core/agent-types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared agent types and discovery logic.\n *\n * Used by multiple extensions (subagent, subagent-v2, agent-permissions)\n * and exported from the public API so extensions don't need cross-plugin imports.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"path\";\nimport { getAgentDir } from \"../config.js\";\nimport { parseFrontmatter } from \"../utils/frontmatter.js\";\n\nexport type AgentScope = \"user\" | \"project\" | \"both\";\n\nexport type PermissionMode = \"auto\" | \"acceptEdits\" | \"plan\" | \"dontAsk\" | \"always-allow\" | \"always-deny\";\n\nexport type AgentColor = \"red\" | \"blue\" | \"green\" | \"yellow\" | \"purple\" | \"orange\";\n\nexport type MemoryScope = \"user\" | \"project\" | \"local\";\n\nexport type IsolationMode = \"worktree\" | \"remote\";\n\nexport interface AgentHookCommand {\n\ttype: \"command\";\n\tcommand: string;\n\tif?: string;\n\tasync?: boolean;\n}\n\nexport interface AgentHookPrompt {\n\ttype: \"prompt\";\n\tprompt: string;\n\tif?: string;\n}\n\nexport type AgentHook = AgentHookCommand | AgentHookPrompt;\n\nexport type AgentHooks = Partial<Record<string, AgentHook[]>>;\n\nexport interface AgentConfig {\n\tname: string;\n\tdescription: string;\n\ttools?: string[];\n\tdisallowedTools?: string[];\n\tmodel?: string;\n\tsystemPrompt: string;\n\tsource: AgentSource;\n\tfilePath: string;\n\n\tpermissionMode?: PermissionMode;\n\tmaxTurns?: number;\n\teffort?: string;\n\tcolor?: AgentColor;\n\tbackground?: boolean;\n\tmemory?: MemoryScope;\n\tisolation?: IsolationMode;\n\tinitialPrompt?: string;\n\tskills?: string[];\n\thooks?: AgentHooks;\n\tvariables?: Record<string, string>;\n\n\ttier?: AgentTier;\n\tthinkingLevel?: string;\n\tmode?: AgentMode;\n\thidden?: boolean;\n}\n\nexport type AgentSource = \"builtin\" | \"plugin\" | \"user\" | \"project\" | \"flag\" | \"policy\";\n\nexport type AgentTier = \"fast\" | \"pro\" | \"max\";\n\nexport type AgentMode = \"primary\" | \"subagent\" | \"all\";\n\nexport interface AgentDiscoveryResult {\n\tagents: AgentConfig[];\n\tprojectAgentsDir: string | null;\n}\n\nconst STRING_FIELDS: ReadonlySet<string> = new Set([\n\t\"description\",\n\t\"model\",\n\t\"permissionMode\",\n\t\"effort\",\n\t\"color\",\n\t\"memory\",\n\t\"isolation\",\n\t\"initialPrompt\",\n\t\"tier\",\n\t\"thinkingLevel\",\n\t\"mode\",\n]);\n\nconst STRING_ARRAY_FIELDS: ReadonlySet<string> = new Set([\"tools\", \"disallowedTools\", \"skills\"]);\n\nconst BOOLEAN_FIELDS: ReadonlySet<string> = new Set([\"background\", \"hidden\"]);\n\nconst NUMBER_FIELDS: ReadonlySet<string> = new Set([\"maxTurns\"]);\n\nfunction coerceField(key: string, raw: unknown): unknown {\n\tif (raw === undefined || raw === null) return undefined;\n\n\tif (STRING_FIELDS.has(key)) {\n\t\treturn typeof raw === \"string\" ? raw : String(raw);\n\t}\n\n\tif (STRING_ARRAY_FIELDS.has(key)) {\n\t\tif (Array.isArray(raw)) return raw.map(String);\n\t\tif (typeof raw === \"string\") {\n\t\t\treturn raw\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s: string) => s.trim())\n\t\t\t\t.filter(Boolean);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (BOOLEAN_FIELDS.has(key)) {\n\t\tif (typeof raw === \"boolean\") return raw;\n\t\tif (typeof raw === \"string\") return raw === \"true\" || raw === \"yes\";\n\t\treturn undefined;\n\t}\n\n\tif (NUMBER_FIELDS.has(key)) {\n\t\tif (typeof raw === \"number\") return raw;\n\t\tif (typeof raw === \"string\") {\n\t\t\tconst n = Number.parseInt(raw, 10);\n\t\t\treturn Number.isFinite(n) ? n : undefined;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\treturn raw;\n}\n\nfunction parseHooks(raw: unknown): AgentHooks | undefined {\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst hooks: AgentHooks = {};\n\tfor (const [event, handlers] of Object.entries(raw as Record<string, unknown>)) {\n\t\tif (!Array.isArray(handlers)) continue;\n\t\tconst parsed: AgentHook[] = [];\n\t\tfor (const h of handlers) {\n\t\t\tif (!h || typeof h !== \"object\") continue;\n\t\t\tconst obj = h as Record<string, unknown>;\n\t\t\tif (obj.type === \"command\" && typeof obj.command === \"string\") {\n\t\t\t\tparsed.push({\n\t\t\t\t\ttype: \"command\",\n\t\t\t\t\tcommand: obj.command,\n\t\t\t\t\tif: typeof obj.if === \"string\" ? obj.if : undefined,\n\t\t\t\t\tasync: obj.async === true,\n\t\t\t\t});\n\t\t\t} else if (obj.type === \"prompt\" && typeof obj.prompt === \"string\") {\n\t\t\t\tparsed.push({\n\t\t\t\t\ttype: \"prompt\",\n\t\t\t\t\tprompt: obj.prompt,\n\t\t\t\t\tif: typeof obj.if === \"string\" ? obj.if : undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tif (parsed.length > 0) hooks[event] = parsed;\n\t}\n\treturn Object.keys(hooks).length > 0 ? hooks : undefined;\n}\n\nexport function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {\n\tconst agents: AgentConfig[] = [];\n\n\tif (!fs.existsSync(dir)) {\n\t\treturn agents;\n\t}\n\n\tlet entries: fs.Dirent[];\n\ttry {\n\t\tentries = fs.readdirSync(dir, { withFileTypes: true });\n\t} catch {\n\t\treturn agents;\n\t}\n\n\tfor (const entry of entries) {\n\t\tif (!entry.name.endsWith(\".md\")) continue;\n\t\tif (!entry.isFile() && !entry.isSymbolicLink()) continue;\n\n\t\tconst filePath = path.join(dir, entry.name);\n\t\tlet content: string;\n\t\ttry {\n\t\t\tcontent = fs.readFileSync(filePath, \"utf-8\");\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(content);\n\n\t\tif (!frontmatter.name || !frontmatter.description) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst tools = coerceField(\"tools\", frontmatter.tools) as string[] | undefined;\n\t\tconst disallowedTools = coerceField(\"disallowedTools\", frontmatter.disallowedTools) as string[] | undefined;\n\t\tconst skills = coerceField(\"skills\", frontmatter.skills) as string[] | undefined;\n\t\tconst hooks = parseHooks(frontmatter.hooks);\n\t\tconst variables =\n\t\t\tfrontmatter.variables && typeof frontmatter.variables === \"object\"\n\t\t\t\t? (frontmatter.variables as Record<string, string>)\n\t\t\t\t: undefined;\n\n\t\tagents.push({\n\t\t\tname: coerceField(\"name\", frontmatter.name) as string,\n\t\t\tdescription: coerceField(\"description\", frontmatter.description) as string,\n\t\t\ttools: tools && tools.length > 0 ? tools : undefined,\n\t\t\tdisallowedTools: disallowedTools && disallowedTools.length > 0 ? disallowedTools : undefined,\n\t\t\tmodel: coerceField(\"model\", frontmatter.model) as string | undefined,\n\t\t\tsystemPrompt: body,\n\t\t\tsource,\n\t\t\tfilePath,\n\t\t\tpermissionMode: coerceField(\"permissionMode\", frontmatter.permissionMode) as PermissionMode | undefined,\n\t\t\tmaxTurns: coerceField(\"maxTurns\", frontmatter.maxTurns) as number | undefined,\n\t\t\teffort: coerceField(\"effort\", frontmatter.effort) as string | undefined,\n\t\t\tcolor: coerceField(\"color\", frontmatter.color) as AgentColor | undefined,\n\t\t\tbackground: coerceField(\"background\", frontmatter.background) as boolean | undefined,\n\t\t\tmemory: coerceField(\"memory\", frontmatter.memory) as MemoryScope | undefined,\n\t\t\tisolation: coerceField(\"isolation\", frontmatter.isolation) as IsolationMode | undefined,\n\t\t\tinitialPrompt: coerceField(\"initialPrompt\", frontmatter.initialPrompt) as string | undefined,\n\t\t\tskills: skills && skills.length > 0 ? skills : undefined,\n\t\t\thooks,\n\t\t\tvariables,\n\t\t\ttier: coerceField(\"tier\", frontmatter.tier) as AgentTier | undefined,\n\t\t\tthinkingLevel: coerceField(\"thinkingLevel\", frontmatter.thinkingLevel) as string | undefined,\n\t\t\tmode: coerceField(\"mode\", frontmatter.mode) as AgentMode | undefined,\n\t\t\thidden: coerceField(\"hidden\", frontmatter.hidden) as boolean | undefined,\n\t\t});\n\t}\n\n\treturn agents;\n}\n\nfunction isDirectory(p: string): boolean {\n\ttry {\n\t\treturn fs.statSync(p).isDirectory();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction findNearestProjectAgentsDir(cwd: string): string | null {\n\tlet currentDir = cwd;\n\twhile (true) {\n\t\tconst candidate = path.join(currentDir, \".pi\", \"agents\");\n\t\tif (isDirectory(candidate)) return candidate;\n\n\t\tconst parentDir = path.dirname(currentDir);\n\t\tif (parentDir === currentDir) return null;\n\t\tcurrentDir = parentDir;\n\t}\n}\n\nexport function mergeAgentsByPriority(...groups: AgentConfig[][]): AgentConfig[] {\n\tconst agentMap = new Map<string, AgentConfig>();\n\tfor (const group of groups) {\n\t\tfor (const agent of group) {\n\t\t\tagentMap.set(agent.name, agent);\n\t\t}\n\t}\n\treturn Array.from(agentMap.values());\n}\n\nexport function getBuiltinAgents(): AgentConfig[] {\n\treturn [\n\t\t{\n\t\t\tname: \"build\",\n\t\t\tdescription: \"Full-stack development with read, write, edit and execution capabilities\",\n\t\t\tsystemPrompt: \"\",\n\t\t\tsource: \"builtin\",\n\t\t\tfilePath: \"\",\n\t\t\tmode: \"primary\",\n\t\t},\n\t\t{\n\t\t\tname: \"explore\",\n\t\t\tdescription: \"Read-only exploration, search and read code\",\n\t\t\ttools: [\"read\", \"grep\", \"find\", \"ls\", \"glob\", \"bash\"],\n\t\t\tdisallowedTools: [\"edit\", \"write\"],\n\t\t\tsystemPrompt:\n\t\t\t\t\"You are a code exploration specialist. You can only read and search code, never modify any files.\\n\\nYour capabilities:\\n- Use grep to search code content\\n- Use glob to find files\\n- Use read to read files\\n- Use bash for read-only commands (cat, ls, head, wc, git log, etc.)\\n\\nStrictly forbidden:\\n- Do not modify any files\\n- Do not run commands that change system state\\n\\nIf the user asks to modify code, refuse and suggest switching to the Build agent.\\n\\nOutput format:\\n### Key Findings\\n### File List\\n### Code Structure Analysis\",\n\t\t\tsource: \"builtin\",\n\t\t\tfilePath: \"\",\n\t\t\tmode: \"primary\",\n\t\t\ttier: \"fast\",\n\t\t\tcolor: \"blue\",\n\t\t},\n\t\t{\n\t\t\tname: \"plan\",\n\t\t\tdescription: \"Planning mode, output analysis and specs only\",\n\t\t\ttools: [\"read\", \"grep\", \"find\", \"ls\", \"glob\"],\n\t\t\tdisallowedTools: [\"edit\", \"write\", \"bash\"],\n\t\t\tsystemPrompt:\n\t\t\t\t\"You are a planning specialist. You only output analysis reports and implementation plans (spec), you cannot edit any code files.\\n\\nOutput format:\\n### Requirements Analysis\\nUnderstanding and clarification of requirements\\n\\n### Technical Solution\\nSolution choice and rationale\\n\\n### Implementation Steps\\n1. Specific steps...\\n\\n### File Change List\\n- path/to/file — change description\\n\\n### Risks and Considerations\\nPotential issues and mitigation strategies\",\n\t\t\tsource: \"builtin\",\n\t\t\tfilePath: \"\",\n\t\t\tmode: \"primary\",\n\t\t\ttier: \"max\",\n\t\t\tthinkingLevel: \"high\",\n\t\t\tcolor: \"purple\",\n\t\t},\n\t];\n}\n\nexport function discoverAgents(cwd: string, scope: AgentScope, overrideAgents?: AgentConfig[]): AgentDiscoveryResult {\n\tconst userDir = path.join(getAgentDir(), \"agents\");\n\tconst projectAgentsDir = findNearestProjectAgentsDir(cwd);\n\n\tconst builtinAgents = getBuiltinAgents();\n\tconst pluginAgents: AgentConfig[] = [];\n\tconst userAgents = scope === \"project\" ? [] : loadAgentsFromDir(userDir, \"user\");\n\tconst projectAgents = scope === \"user\" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, \"project\");\n\tconst flagAgents = overrideAgents ?? [];\n\tconst policyAgents: AgentConfig[] = [];\n\n\tconst agents = mergeAgentsByPriority(\n\t\tbuiltinAgents,\n\t\tpluginAgents,\n\t\tuserAgents,\n\t\tprojectAgents,\n\t\tflagAgents,\n\t\tpolicyAgents,\n\t);\n\n\treturn { agents, projectAgentsDir };\n}\n\nexport function formatAgentList(agents: AgentConfig[], maxItems: number): { text: string; remaining: number } {\n\tif (agents.length === 0) return { text: \"none\", remaining: 0 };\n\tconst listed = agents.slice(0, maxItems);\n\tconst remaining = agents.length - listed.length;\n\treturn {\n\t\ttext: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join(\"; \"),\n\t\tremaining,\n\t};\n}\n"]}
|
package/dist/core/agent-types.js
CHANGED
|
@@ -17,9 +17,12 @@ const STRING_FIELDS = new Set([
|
|
|
17
17
|
"memory",
|
|
18
18
|
"isolation",
|
|
19
19
|
"initialPrompt",
|
|
20
|
+
"tier",
|
|
21
|
+
"thinkingLevel",
|
|
22
|
+
"mode",
|
|
20
23
|
]);
|
|
21
24
|
const STRING_ARRAY_FIELDS = new Set(["tools", "disallowedTools", "skills"]);
|
|
22
|
-
const BOOLEAN_FIELDS = new Set(["background"]);
|
|
25
|
+
const BOOLEAN_FIELDS = new Set(["background", "hidden"]);
|
|
23
26
|
const NUMBER_FIELDS = new Set(["maxTurns"]);
|
|
24
27
|
function coerceField(key, raw) {
|
|
25
28
|
if (raw === undefined || raw === null)
|
|
@@ -145,6 +148,10 @@ export function loadAgentsFromDir(dir, source) {
|
|
|
145
148
|
skills: skills && skills.length > 0 ? skills : undefined,
|
|
146
149
|
hooks,
|
|
147
150
|
variables,
|
|
151
|
+
tier: coerceField("tier", frontmatter.tier),
|
|
152
|
+
thinkingLevel: coerceField("thinkingLevel", frontmatter.thinkingLevel),
|
|
153
|
+
mode: coerceField("mode", frontmatter.mode),
|
|
154
|
+
hidden: coerceField("hidden", frontmatter.hidden),
|
|
148
155
|
});
|
|
149
156
|
}
|
|
150
157
|
return agents;
|
|
@@ -178,10 +185,47 @@ export function mergeAgentsByPriority(...groups) {
|
|
|
178
185
|
}
|
|
179
186
|
return Array.from(agentMap.values());
|
|
180
187
|
}
|
|
188
|
+
export function getBuiltinAgents() {
|
|
189
|
+
return [
|
|
190
|
+
{
|
|
191
|
+
name: "build",
|
|
192
|
+
description: "Full-stack development with read, write, edit and execution capabilities",
|
|
193
|
+
systemPrompt: "",
|
|
194
|
+
source: "builtin",
|
|
195
|
+
filePath: "",
|
|
196
|
+
mode: "primary",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "explore",
|
|
200
|
+
description: "Read-only exploration, search and read code",
|
|
201
|
+
tools: ["read", "grep", "find", "ls", "glob", "bash"],
|
|
202
|
+
disallowedTools: ["edit", "write"],
|
|
203
|
+
systemPrompt: "You are a code exploration specialist. You can only read and search code, never modify any files.\n\nYour capabilities:\n- Use grep to search code content\n- Use glob to find files\n- Use read to read files\n- Use bash for read-only commands (cat, ls, head, wc, git log, etc.)\n\nStrictly forbidden:\n- Do not modify any files\n- Do not run commands that change system state\n\nIf the user asks to modify code, refuse and suggest switching to the Build agent.\n\nOutput format:\n### Key Findings\n### File List\n### Code Structure Analysis",
|
|
204
|
+
source: "builtin",
|
|
205
|
+
filePath: "",
|
|
206
|
+
mode: "primary",
|
|
207
|
+
tier: "fast",
|
|
208
|
+
color: "blue",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "plan",
|
|
212
|
+
description: "Planning mode, output analysis and specs only",
|
|
213
|
+
tools: ["read", "grep", "find", "ls", "glob"],
|
|
214
|
+
disallowedTools: ["edit", "write", "bash"],
|
|
215
|
+
systemPrompt: "You are a planning specialist. You only output analysis reports and implementation plans (spec), you cannot edit any code files.\n\nOutput format:\n### Requirements Analysis\nUnderstanding and clarification of requirements\n\n### Technical Solution\nSolution choice and rationale\n\n### Implementation Steps\n1. Specific steps...\n\n### File Change List\n- path/to/file — change description\n\n### Risks and Considerations\nPotential issues and mitigation strategies",
|
|
216
|
+
source: "builtin",
|
|
217
|
+
filePath: "",
|
|
218
|
+
mode: "primary",
|
|
219
|
+
tier: "max",
|
|
220
|
+
thinkingLevel: "high",
|
|
221
|
+
color: "purple",
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
}
|
|
181
225
|
export function discoverAgents(cwd, scope, overrideAgents) {
|
|
182
226
|
const userDir = path.join(getAgentDir(), "agents");
|
|
183
227
|
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
184
|
-
const builtinAgents =
|
|
228
|
+
const builtinAgents = getBuiltinAgents();
|
|
185
229
|
const pluginAgents = [];
|
|
186
230
|
const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
|
|
187
231
|
const projectAgents = scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-types.js","sourceRoot":"","sources":["../../src/core/agent-types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared agent types and discovery logic.\n *\n * Used by multiple extensions (subagent, subagent-v2, agent-permissions)\n * and exported from the public API so extensions don't need cross-plugin imports.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"path\";\nimport { getAgentDir } from \"../config.js\";\nimport { parseFrontmatter } from \"../utils/frontmatter.js\";\n\nexport type AgentScope = \"user\" | \"project\" | \"both\";\n\nexport type PermissionMode = \"auto\" | \"acceptEdits\" | \"plan\" | \"dontAsk\" | \"always-allow\" | \"always-deny\";\n\nexport type AgentColor = \"red\" | \"blue\" | \"green\" | \"yellow\" | \"purple\" | \"orange\";\n\nexport type MemoryScope = \"user\" | \"project\" | \"local\";\n\nexport type IsolationMode = \"worktree\" | \"remote\";\n\nexport interface AgentHookCommand {\n\ttype: \"command\";\n\tcommand: string;\n\tif?: string;\n\tasync?: boolean;\n}\n\nexport interface AgentHookPrompt {\n\ttype: \"prompt\";\n\tprompt: string;\n\tif?: string;\n}\n\nexport type AgentHook = AgentHookCommand | AgentHookPrompt;\n\nexport type AgentHooks = Partial<Record<string, AgentHook[]>>;\n\nexport interface AgentConfig {\n\tname: string;\n\tdescription: string;\n\ttools?: string[];\n\tdisallowedTools?: string[];\n\tmodel?: string;\n\tsystemPrompt: string;\n\tsource: AgentSource;\n\tfilePath: string;\n\n\tpermissionMode?: PermissionMode;\n\tmaxTurns?: number;\n\teffort?: string;\n\tcolor?: AgentColor;\n\tbackground?: boolean;\n\tmemory?: MemoryScope;\n\tisolation?: IsolationMode;\n\tinitialPrompt?: string;\n\tskills?: string[];\n\thooks?: AgentHooks;\n\tvariables?: Record<string, string>;\n}\n\nexport type AgentSource = \"builtin\" | \"plugin\" | \"user\" | \"project\" | \"flag\" | \"policy\";\n\nexport interface AgentDiscoveryResult {\n\tagents: AgentConfig[];\n\tprojectAgentsDir: string | null;\n}\n\nconst STRING_FIELDS: ReadonlySet<string> = new Set([\n\t\"description\",\n\t\"model\",\n\t\"permissionMode\",\n\t\"effort\",\n\t\"color\",\n\t\"memory\",\n\t\"isolation\",\n\t\"initialPrompt\",\n]);\n\nconst STRING_ARRAY_FIELDS: ReadonlySet<string> = new Set([\"tools\", \"disallowedTools\", \"skills\"]);\n\nconst BOOLEAN_FIELDS: ReadonlySet<string> = new Set([\"background\"]);\n\nconst NUMBER_FIELDS: ReadonlySet<string> = new Set([\"maxTurns\"]);\n\nfunction coerceField(key: string, raw: unknown): unknown {\n\tif (raw === undefined || raw === null) return undefined;\n\n\tif (STRING_FIELDS.has(key)) {\n\t\treturn typeof raw === \"string\" ? raw : String(raw);\n\t}\n\n\tif (STRING_ARRAY_FIELDS.has(key)) {\n\t\tif (Array.isArray(raw)) return raw.map(String);\n\t\tif (typeof raw === \"string\") {\n\t\t\treturn raw\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s: string) => s.trim())\n\t\t\t\t.filter(Boolean);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (BOOLEAN_FIELDS.has(key)) {\n\t\tif (typeof raw === \"boolean\") return raw;\n\t\tif (typeof raw === \"string\") return raw === \"true\" || raw === \"yes\";\n\t\treturn undefined;\n\t}\n\n\tif (NUMBER_FIELDS.has(key)) {\n\t\tif (typeof raw === \"number\") return raw;\n\t\tif (typeof raw === \"string\") {\n\t\t\tconst n = Number.parseInt(raw, 10);\n\t\t\treturn Number.isFinite(n) ? n : undefined;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\treturn raw;\n}\n\nfunction parseHooks(raw: unknown): AgentHooks | undefined {\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst hooks: AgentHooks = {};\n\tfor (const [event, handlers] of Object.entries(raw as Record<string, unknown>)) {\n\t\tif (!Array.isArray(handlers)) continue;\n\t\tconst parsed: AgentHook[] = [];\n\t\tfor (const h of handlers) {\n\t\t\tif (!h || typeof h !== \"object\") continue;\n\t\t\tconst obj = h as Record<string, unknown>;\n\t\t\tif (obj.type === \"command\" && typeof obj.command === \"string\") {\n\t\t\t\tparsed.push({\n\t\t\t\t\ttype: \"command\",\n\t\t\t\t\tcommand: obj.command,\n\t\t\t\t\tif: typeof obj.if === \"string\" ? obj.if : undefined,\n\t\t\t\t\tasync: obj.async === true,\n\t\t\t\t});\n\t\t\t} else if (obj.type === \"prompt\" && typeof obj.prompt === \"string\") {\n\t\t\t\tparsed.push({\n\t\t\t\t\ttype: \"prompt\",\n\t\t\t\t\tprompt: obj.prompt,\n\t\t\t\t\tif: typeof obj.if === \"string\" ? obj.if : undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tif (parsed.length > 0) hooks[event] = parsed;\n\t}\n\treturn Object.keys(hooks).length > 0 ? hooks : undefined;\n}\n\nexport function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {\n\tconst agents: AgentConfig[] = [];\n\n\tif (!fs.existsSync(dir)) {\n\t\treturn agents;\n\t}\n\n\tlet entries: fs.Dirent[];\n\ttry {\n\t\tentries = fs.readdirSync(dir, { withFileTypes: true });\n\t} catch {\n\t\treturn agents;\n\t}\n\n\tfor (const entry of entries) {\n\t\tif (!entry.name.endsWith(\".md\")) continue;\n\t\tif (!entry.isFile() && !entry.isSymbolicLink()) continue;\n\n\t\tconst filePath = path.join(dir, entry.name);\n\t\tlet content: string;\n\t\ttry {\n\t\t\tcontent = fs.readFileSync(filePath, \"utf-8\");\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(content);\n\n\t\tif (!frontmatter.name || !frontmatter.description) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst tools = coerceField(\"tools\", frontmatter.tools) as string[] | undefined;\n\t\tconst disallowedTools = coerceField(\"disallowedTools\", frontmatter.disallowedTools) as string[] | undefined;\n\t\tconst skills = coerceField(\"skills\", frontmatter.skills) as string[] | undefined;\n\t\tconst hooks = parseHooks(frontmatter.hooks);\n\t\tconst variables =\n\t\t\tfrontmatter.variables && typeof frontmatter.variables === \"object\"\n\t\t\t\t? (frontmatter.variables as Record<string, string>)\n\t\t\t\t: undefined;\n\n\t\tagents.push({\n\t\t\tname: coerceField(\"name\", frontmatter.name) as string,\n\t\t\tdescription: coerceField(\"description\", frontmatter.description) as string,\n\t\t\ttools: tools && tools.length > 0 ? tools : undefined,\n\t\t\tdisallowedTools: disallowedTools && disallowedTools.length > 0 ? disallowedTools : undefined,\n\t\t\tmodel: coerceField(\"model\", frontmatter.model) as string | undefined,\n\t\t\tsystemPrompt: body,\n\t\t\tsource,\n\t\t\tfilePath,\n\t\t\tpermissionMode: coerceField(\"permissionMode\", frontmatter.permissionMode) as PermissionMode | undefined,\n\t\t\tmaxTurns: coerceField(\"maxTurns\", frontmatter.maxTurns) as number | undefined,\n\t\t\teffort: coerceField(\"effort\", frontmatter.effort) as string | undefined,\n\t\t\tcolor: coerceField(\"color\", frontmatter.color) as AgentColor | undefined,\n\t\t\tbackground: coerceField(\"background\", frontmatter.background) as boolean | undefined,\n\t\t\tmemory: coerceField(\"memory\", frontmatter.memory) as MemoryScope | undefined,\n\t\t\tisolation: coerceField(\"isolation\", frontmatter.isolation) as IsolationMode | undefined,\n\t\t\tinitialPrompt: coerceField(\"initialPrompt\", frontmatter.initialPrompt) as string | undefined,\n\t\t\tskills: skills && skills.length > 0 ? skills : undefined,\n\t\t\thooks,\n\t\t\tvariables,\n\t\t});\n\t}\n\n\treturn agents;\n}\n\nfunction isDirectory(p: string): boolean {\n\ttry {\n\t\treturn fs.statSync(p).isDirectory();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction findNearestProjectAgentsDir(cwd: string): string | null {\n\tlet currentDir = cwd;\n\twhile (true) {\n\t\tconst candidate = path.join(currentDir, \".pi\", \"agents\");\n\t\tif (isDirectory(candidate)) return candidate;\n\n\t\tconst parentDir = path.dirname(currentDir);\n\t\tif (parentDir === currentDir) return null;\n\t\tcurrentDir = parentDir;\n\t}\n}\n\nexport function mergeAgentsByPriority(...groups: AgentConfig[][]): AgentConfig[] {\n\tconst agentMap = new Map<string, AgentConfig>();\n\tfor (const group of groups) {\n\t\tfor (const agent of group) {\n\t\t\tagentMap.set(agent.name, agent);\n\t\t}\n\t}\n\treturn Array.from(agentMap.values());\n}\n\nexport function discoverAgents(cwd: string, scope: AgentScope, overrideAgents?: AgentConfig[]): AgentDiscoveryResult {\n\tconst userDir = path.join(getAgentDir(), \"agents\");\n\tconst projectAgentsDir = findNearestProjectAgentsDir(cwd);\n\n\tconst builtinAgents: AgentConfig[] = [];\n\tconst pluginAgents: AgentConfig[] = [];\n\tconst userAgents = scope === \"project\" ? [] : loadAgentsFromDir(userDir, \"user\");\n\tconst projectAgents = scope === \"user\" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, \"project\");\n\tconst flagAgents = overrideAgents ?? [];\n\tconst policyAgents: AgentConfig[] = [];\n\n\tconst agents = mergeAgentsByPriority(\n\t\tbuiltinAgents,\n\t\tpluginAgents,\n\t\tuserAgents,\n\t\tprojectAgents,\n\t\tflagAgents,\n\t\tpolicyAgents,\n\t);\n\n\treturn { agents, projectAgentsDir };\n}\n\nexport function formatAgentList(agents: AgentConfig[], maxItems: number): { text: string; remaining: number } {\n\tif (agents.length === 0) return { text: \"none\", remaining: 0 };\n\tconst listed = agents.slice(0, maxItems);\n\tconst remaining = agents.length - listed.length;\n\treturn {\n\t\ttext: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join(\"; \"),\n\t\tremaining,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent-types.js","sourceRoot":"","sources":["../../src/core/agent-types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared agent types and discovery logic.\n *\n * Used by multiple extensions (subagent, subagent-v2, agent-permissions)\n * and exported from the public API so extensions don't need cross-plugin imports.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"path\";\nimport { getAgentDir } from \"../config.js\";\nimport { parseFrontmatter } from \"../utils/frontmatter.js\";\n\nexport type AgentScope = \"user\" | \"project\" | \"both\";\n\nexport type PermissionMode = \"auto\" | \"acceptEdits\" | \"plan\" | \"dontAsk\" | \"always-allow\" | \"always-deny\";\n\nexport type AgentColor = \"red\" | \"blue\" | \"green\" | \"yellow\" | \"purple\" | \"orange\";\n\nexport type MemoryScope = \"user\" | \"project\" | \"local\";\n\nexport type IsolationMode = \"worktree\" | \"remote\";\n\nexport interface AgentHookCommand {\n\ttype: \"command\";\n\tcommand: string;\n\tif?: string;\n\tasync?: boolean;\n}\n\nexport interface AgentHookPrompt {\n\ttype: \"prompt\";\n\tprompt: string;\n\tif?: string;\n}\n\nexport type AgentHook = AgentHookCommand | AgentHookPrompt;\n\nexport type AgentHooks = Partial<Record<string, AgentHook[]>>;\n\nexport interface AgentConfig {\n\tname: string;\n\tdescription: string;\n\ttools?: string[];\n\tdisallowedTools?: string[];\n\tmodel?: string;\n\tsystemPrompt: string;\n\tsource: AgentSource;\n\tfilePath: string;\n\n\tpermissionMode?: PermissionMode;\n\tmaxTurns?: number;\n\teffort?: string;\n\tcolor?: AgentColor;\n\tbackground?: boolean;\n\tmemory?: MemoryScope;\n\tisolation?: IsolationMode;\n\tinitialPrompt?: string;\n\tskills?: string[];\n\thooks?: AgentHooks;\n\tvariables?: Record<string, string>;\n\n\ttier?: AgentTier;\n\tthinkingLevel?: string;\n\tmode?: AgentMode;\n\thidden?: boolean;\n}\n\nexport type AgentSource = \"builtin\" | \"plugin\" | \"user\" | \"project\" | \"flag\" | \"policy\";\n\nexport type AgentTier = \"fast\" | \"pro\" | \"max\";\n\nexport type AgentMode = \"primary\" | \"subagent\" | \"all\";\n\nexport interface AgentDiscoveryResult {\n\tagents: AgentConfig[];\n\tprojectAgentsDir: string | null;\n}\n\nconst STRING_FIELDS: ReadonlySet<string> = new Set([\n\t\"description\",\n\t\"model\",\n\t\"permissionMode\",\n\t\"effort\",\n\t\"color\",\n\t\"memory\",\n\t\"isolation\",\n\t\"initialPrompt\",\n\t\"tier\",\n\t\"thinkingLevel\",\n\t\"mode\",\n]);\n\nconst STRING_ARRAY_FIELDS: ReadonlySet<string> = new Set([\"tools\", \"disallowedTools\", \"skills\"]);\n\nconst BOOLEAN_FIELDS: ReadonlySet<string> = new Set([\"background\", \"hidden\"]);\n\nconst NUMBER_FIELDS: ReadonlySet<string> = new Set([\"maxTurns\"]);\n\nfunction coerceField(key: string, raw: unknown): unknown {\n\tif (raw === undefined || raw === null) return undefined;\n\n\tif (STRING_FIELDS.has(key)) {\n\t\treturn typeof raw === \"string\" ? raw : String(raw);\n\t}\n\n\tif (STRING_ARRAY_FIELDS.has(key)) {\n\t\tif (Array.isArray(raw)) return raw.map(String);\n\t\tif (typeof raw === \"string\") {\n\t\t\treturn raw\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s: string) => s.trim())\n\t\t\t\t.filter(Boolean);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (BOOLEAN_FIELDS.has(key)) {\n\t\tif (typeof raw === \"boolean\") return raw;\n\t\tif (typeof raw === \"string\") return raw === \"true\" || raw === \"yes\";\n\t\treturn undefined;\n\t}\n\n\tif (NUMBER_FIELDS.has(key)) {\n\t\tif (typeof raw === \"number\") return raw;\n\t\tif (typeof raw === \"string\") {\n\t\t\tconst n = Number.parseInt(raw, 10);\n\t\t\treturn Number.isFinite(n) ? n : undefined;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\treturn raw;\n}\n\nfunction parseHooks(raw: unknown): AgentHooks | undefined {\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst hooks: AgentHooks = {};\n\tfor (const [event, handlers] of Object.entries(raw as Record<string, unknown>)) {\n\t\tif (!Array.isArray(handlers)) continue;\n\t\tconst parsed: AgentHook[] = [];\n\t\tfor (const h of handlers) {\n\t\t\tif (!h || typeof h !== \"object\") continue;\n\t\t\tconst obj = h as Record<string, unknown>;\n\t\t\tif (obj.type === \"command\" && typeof obj.command === \"string\") {\n\t\t\t\tparsed.push({\n\t\t\t\t\ttype: \"command\",\n\t\t\t\t\tcommand: obj.command,\n\t\t\t\t\tif: typeof obj.if === \"string\" ? obj.if : undefined,\n\t\t\t\t\tasync: obj.async === true,\n\t\t\t\t});\n\t\t\t} else if (obj.type === \"prompt\" && typeof obj.prompt === \"string\") {\n\t\t\t\tparsed.push({\n\t\t\t\t\ttype: \"prompt\",\n\t\t\t\t\tprompt: obj.prompt,\n\t\t\t\t\tif: typeof obj.if === \"string\" ? obj.if : undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tif (parsed.length > 0) hooks[event] = parsed;\n\t}\n\treturn Object.keys(hooks).length > 0 ? hooks : undefined;\n}\n\nexport function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {\n\tconst agents: AgentConfig[] = [];\n\n\tif (!fs.existsSync(dir)) {\n\t\treturn agents;\n\t}\n\n\tlet entries: fs.Dirent[];\n\ttry {\n\t\tentries = fs.readdirSync(dir, { withFileTypes: true });\n\t} catch {\n\t\treturn agents;\n\t}\n\n\tfor (const entry of entries) {\n\t\tif (!entry.name.endsWith(\".md\")) continue;\n\t\tif (!entry.isFile() && !entry.isSymbolicLink()) continue;\n\n\t\tconst filePath = path.join(dir, entry.name);\n\t\tlet content: string;\n\t\ttry {\n\t\t\tcontent = fs.readFileSync(filePath, \"utf-8\");\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(content);\n\n\t\tif (!frontmatter.name || !frontmatter.description) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst tools = coerceField(\"tools\", frontmatter.tools) as string[] | undefined;\n\t\tconst disallowedTools = coerceField(\"disallowedTools\", frontmatter.disallowedTools) as string[] | undefined;\n\t\tconst skills = coerceField(\"skills\", frontmatter.skills) as string[] | undefined;\n\t\tconst hooks = parseHooks(frontmatter.hooks);\n\t\tconst variables =\n\t\t\tfrontmatter.variables && typeof frontmatter.variables === \"object\"\n\t\t\t\t? (frontmatter.variables as Record<string, string>)\n\t\t\t\t: undefined;\n\n\t\tagents.push({\n\t\t\tname: coerceField(\"name\", frontmatter.name) as string,\n\t\t\tdescription: coerceField(\"description\", frontmatter.description) as string,\n\t\t\ttools: tools && tools.length > 0 ? tools : undefined,\n\t\t\tdisallowedTools: disallowedTools && disallowedTools.length > 0 ? disallowedTools : undefined,\n\t\t\tmodel: coerceField(\"model\", frontmatter.model) as string | undefined,\n\t\t\tsystemPrompt: body,\n\t\t\tsource,\n\t\t\tfilePath,\n\t\t\tpermissionMode: coerceField(\"permissionMode\", frontmatter.permissionMode) as PermissionMode | undefined,\n\t\t\tmaxTurns: coerceField(\"maxTurns\", frontmatter.maxTurns) as number | undefined,\n\t\t\teffort: coerceField(\"effort\", frontmatter.effort) as string | undefined,\n\t\t\tcolor: coerceField(\"color\", frontmatter.color) as AgentColor | undefined,\n\t\t\tbackground: coerceField(\"background\", frontmatter.background) as boolean | undefined,\n\t\t\tmemory: coerceField(\"memory\", frontmatter.memory) as MemoryScope | undefined,\n\t\t\tisolation: coerceField(\"isolation\", frontmatter.isolation) as IsolationMode | undefined,\n\t\t\tinitialPrompt: coerceField(\"initialPrompt\", frontmatter.initialPrompt) as string | undefined,\n\t\t\tskills: skills && skills.length > 0 ? skills : undefined,\n\t\t\thooks,\n\t\t\tvariables,\n\t\t\ttier: coerceField(\"tier\", frontmatter.tier) as AgentTier | undefined,\n\t\t\tthinkingLevel: coerceField(\"thinkingLevel\", frontmatter.thinkingLevel) as string | undefined,\n\t\t\tmode: coerceField(\"mode\", frontmatter.mode) as AgentMode | undefined,\n\t\t\thidden: coerceField(\"hidden\", frontmatter.hidden) as boolean | undefined,\n\t\t});\n\t}\n\n\treturn agents;\n}\n\nfunction isDirectory(p: string): boolean {\n\ttry {\n\t\treturn fs.statSync(p).isDirectory();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction findNearestProjectAgentsDir(cwd: string): string | null {\n\tlet currentDir = cwd;\n\twhile (true) {\n\t\tconst candidate = path.join(currentDir, \".pi\", \"agents\");\n\t\tif (isDirectory(candidate)) return candidate;\n\n\t\tconst parentDir = path.dirname(currentDir);\n\t\tif (parentDir === currentDir) return null;\n\t\tcurrentDir = parentDir;\n\t}\n}\n\nexport function mergeAgentsByPriority(...groups: AgentConfig[][]): AgentConfig[] {\n\tconst agentMap = new Map<string, AgentConfig>();\n\tfor (const group of groups) {\n\t\tfor (const agent of group) {\n\t\t\tagentMap.set(agent.name, agent);\n\t\t}\n\t}\n\treturn Array.from(agentMap.values());\n}\n\nexport function getBuiltinAgents(): AgentConfig[] {\n\treturn [\n\t\t{\n\t\t\tname: \"build\",\n\t\t\tdescription: \"Full-stack development with read, write, edit and execution capabilities\",\n\t\t\tsystemPrompt: \"\",\n\t\t\tsource: \"builtin\",\n\t\t\tfilePath: \"\",\n\t\t\tmode: \"primary\",\n\t\t},\n\t\t{\n\t\t\tname: \"explore\",\n\t\t\tdescription: \"Read-only exploration, search and read code\",\n\t\t\ttools: [\"read\", \"grep\", \"find\", \"ls\", \"glob\", \"bash\"],\n\t\t\tdisallowedTools: [\"edit\", \"write\"],\n\t\t\tsystemPrompt:\n\t\t\t\t\"You are a code exploration specialist. You can only read and search code, never modify any files.\\n\\nYour capabilities:\\n- Use grep to search code content\\n- Use glob to find files\\n- Use read to read files\\n- Use bash for read-only commands (cat, ls, head, wc, git log, etc.)\\n\\nStrictly forbidden:\\n- Do not modify any files\\n- Do not run commands that change system state\\n\\nIf the user asks to modify code, refuse and suggest switching to the Build agent.\\n\\nOutput format:\\n### Key Findings\\n### File List\\n### Code Structure Analysis\",\n\t\t\tsource: \"builtin\",\n\t\t\tfilePath: \"\",\n\t\t\tmode: \"primary\",\n\t\t\ttier: \"fast\",\n\t\t\tcolor: \"blue\",\n\t\t},\n\t\t{\n\t\t\tname: \"plan\",\n\t\t\tdescription: \"Planning mode, output analysis and specs only\",\n\t\t\ttools: [\"read\", \"grep\", \"find\", \"ls\", \"glob\"],\n\t\t\tdisallowedTools: [\"edit\", \"write\", \"bash\"],\n\t\t\tsystemPrompt:\n\t\t\t\t\"You are a planning specialist. You only output analysis reports and implementation plans (spec), you cannot edit any code files.\\n\\nOutput format:\\n### Requirements Analysis\\nUnderstanding and clarification of requirements\\n\\n### Technical Solution\\nSolution choice and rationale\\n\\n### Implementation Steps\\n1. Specific steps...\\n\\n### File Change List\\n- path/to/file — change description\\n\\n### Risks and Considerations\\nPotential issues and mitigation strategies\",\n\t\t\tsource: \"builtin\",\n\t\t\tfilePath: \"\",\n\t\t\tmode: \"primary\",\n\t\t\ttier: \"max\",\n\t\t\tthinkingLevel: \"high\",\n\t\t\tcolor: \"purple\",\n\t\t},\n\t];\n}\n\nexport function discoverAgents(cwd: string, scope: AgentScope, overrideAgents?: AgentConfig[]): AgentDiscoveryResult {\n\tconst userDir = path.join(getAgentDir(), \"agents\");\n\tconst projectAgentsDir = findNearestProjectAgentsDir(cwd);\n\n\tconst builtinAgents = getBuiltinAgents();\n\tconst pluginAgents: AgentConfig[] = [];\n\tconst userAgents = scope === \"project\" ? [] : loadAgentsFromDir(userDir, \"user\");\n\tconst projectAgents = scope === \"user\" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, \"project\");\n\tconst flagAgents = overrideAgents ?? [];\n\tconst policyAgents: AgentConfig[] = [];\n\n\tconst agents = mergeAgentsByPriority(\n\t\tbuiltinAgents,\n\t\tpluginAgents,\n\t\tuserAgents,\n\t\tprojectAgents,\n\t\tflagAgents,\n\t\tpolicyAgents,\n\t);\n\n\treturn { agents, projectAgentsDir };\n}\n\nexport function formatAgentList(agents: AgentConfig[], maxItems: number): { text: string; remaining: number } {\n\tif (agents.length === 0) return { text: \"none\", remaining: 0 };\n\tconst listed = agents.slice(0, maxItems);\n\tconst remaining = agents.length - listed.length;\n\treturn {\n\t\ttext: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join(\"; \"),\n\t\tremaining,\n\t};\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAwBH,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAKhE,OAAO,KAAK,EACX,SAAS,EAET,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EAKpB,MAAM,YAAY,CAAC;AAwGpB;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,gBAAgB,CAoHzD;AAyMD,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAoED;;GAEG;AACH,wBAAsB,wBAAwB,CAC7C,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,gBAAgB,EACzB,aAAa,SAAa,GACxB,OAAO,CAAC,SAAS,CAAC,CAKpB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAyCrH;AA+GD;;GAEG;AACH,wBAAsB,yBAAyB,CAC9C,eAAe,EAAE,MAAM,EAAE,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAsB,EAChC,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,oBAAoB,CAAC,CAyC/B","sourcesContent":["/**\n * Extension loader - loads TypeScript extension modules using jiti.\n *\n */\n\nimport * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport * as _bundledPiAgentCore from \"@dyyz1993/pi-agent-core\";\nimport * as _bundledPiAi from \"@dyyz1993/pi-ai\";\nimport * as _bundledPiAiOauth from \"@dyyz1993/pi-ai/oauth\";\nimport type { KeyId } from \"@dyyz1993/pi-tui\";\nimport * as _bundledPiTui from \"@dyyz1993/pi-tui\";\nimport { createJiti } from \"jiti/static\";\nimport { time } from \"../timings.js\";\n// Static imports of packages that extensions may use.\n// These MUST be static so Bun bundles them into the compiled binary.\n// The virtualModules option then makes them available to extensions.\nimport * as _bundledTypebox from \"typebox\";\nimport * as _bundledTypeboxCompile from \"typebox/compile\";\nimport * as _bundledTypeboxValue from \"typebox/value\";\nimport { CONFIG_DIR_NAME, getAgentDir, isBunBinary } from \"../../config.js\";\n// NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,\n// avoiding a circular dependency. Extensions can import from @dyyz1993/pi-coding-agent.\nimport * as _bundledPiCodingAgent from \"../../index.js\";\nimport { createEventBus, type EventBus } from \"../event-bus.js\";\nimport type { ExecOptions } from \"../exec.js\";\nimport { execCommand } from \"../exec.js\";\nimport { createSyntheticSourceInfo } from \"../source-info.js\";\nimport type { Channel } from \"./channel-types.js\";\nimport type {\n\tExtension,\n\tExtensionAPI,\n\tExtensionFactory,\n\tExtensionRuntime,\n\tLoadExtensionsResult,\n\tMessageRenderer,\n\tProviderConfig,\n\tRegisteredCommand,\n\tToolDefinition,\n} from \"./types.js\";\n\n/** Modules available to extensions via virtualModules (for compiled Bun binary) */\nconst VIRTUAL_MODULES: Record<string, unknown> = {\n\ttypebox: _bundledTypebox,\n\t\"typebox/compile\": _bundledTypeboxCompile,\n\t\"typebox/value\": _bundledTypeboxValue,\n\t\"@sinclair/typebox\": _bundledTypebox,\n\t\"@sinclair/typebox/compile\": _bundledTypeboxCompile,\n\t\"@sinclair/typebox/value\": _bundledTypeboxValue,\n\t\"@dyyz1993/pi-agent-core\": _bundledPiAgentCore,\n\t\"@dyyz1993/pi-tui\": _bundledPiTui,\n\t\"@dyyz1993/pi-ai\": _bundledPiAi,\n\t\"@dyyz1993/pi-ai/oauth\": _bundledPiAiOauth,\n\t\"@dyyz1993/pi-coding-agent\": _bundledPiCodingAgent,\n\t\"@mariozechner/pi-agent-core\": _bundledPiAgentCore,\n\t\"@mariozechner/pi-tui\": _bundledPiTui,\n\t\"@mariozechner/pi-ai\": _bundledPiAi,\n\t\"@mariozechner/pi-ai/oauth\": _bundledPiAiOauth,\n\t\"@mariozechner/pi-coding-agent\": _bundledPiCodingAgent,\n};\n\nconst require = createRequire(import.meta.url);\n\n/**\n * Get aliases for jiti (used in Node.js/development mode).\n * In Bun binary mode, virtualModules is used instead.\n */\nlet _aliases: Record<string, string> | null = null;\n\nfunction getAliases(): Record<string, string> {\n\tif (_aliases) return _aliases;\n\n\tconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\tconst packageIndex = path.resolve(__dirname, \"../..\", \"index.js\");\n\n\tconst typeboxEntry = require.resolve(\"typebox\");\n\tconst typeboxCompileEntry = require.resolve(\"typebox/compile\");\n\tconst typeboxValueEntry = require.resolve(\"typebox/value\");\n\n\tconst packagesRoot = path.resolve(__dirname, \"../../../../\");\n\tconst resolveWorkspaceOrImport = (workspaceRelativePath: string, specifier: string): string => {\n\t\tconst workspacePath = path.join(packagesRoot, workspaceRelativePath);\n\t\tif (fs.existsSync(workspacePath)) {\n\t\t\treturn workspacePath;\n\t\t}\n\t\treturn fileURLToPath(import.meta.resolve(specifier));\n\t};\n\n\tconst piCodingAgentEntry = packageIndex;\n\tconst piAgentCoreEntry = resolveWorkspaceOrImport(\"agent/dist/index.js\", \"@dyyz1993/pi-agent-core\");\n\tconst piTuiEntry = resolveWorkspaceOrImport(\"tui/dist/index.js\", \"@dyyz1993/pi-tui\");\n\tconst piAiEntry = resolveWorkspaceOrImport(\"ai/dist/index.js\", \"@dyyz1993/pi-ai\");\n\tconst piAiOauthEntry = resolveWorkspaceOrImport(\"ai/dist/oauth.js\", \"@dyyz1993/pi-ai/oauth\");\n\n\t_aliases = {\n\t\t\"@dyyz1993/pi-coding-agent\": piCodingAgentEntry,\n\t\t\"@dyyz1993/pi-agent-core\": piAgentCoreEntry,\n\t\t\"@dyyz1993/pi-tui\": piTuiEntry,\n\t\t\"@dyyz1993/pi-ai\": piAiEntry,\n\t\t\"@dyyz1993/pi-ai/oauth\": piAiOauthEntry,\n\t\t\"@mariozechner/pi-coding-agent\": piCodingAgentEntry,\n\t\t\"@mariozechner/pi-agent-core\": piAgentCoreEntry,\n\t\t\"@mariozechner/pi-tui\": piTuiEntry,\n\t\t\"@mariozechner/pi-ai\": piAiEntry,\n\t\t\"@mariozechner/pi-ai/oauth\": piAiOauthEntry,\n\t\ttypebox: typeboxEntry,\n\t\t\"typebox/compile\": typeboxCompileEntry,\n\t\t\"typebox/value\": typeboxValueEntry,\n\t\t\"@sinclair/typebox\": typeboxEntry,\n\t\t\"@sinclair/typebox/compile\": typeboxCompileEntry,\n\t\t\"@sinclair/typebox/value\": typeboxValueEntry,\n\t};\n\n\treturn _aliases;\n}\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/g;\n\nfunction normalizeUnicodeSpaces(str: string): string {\n\treturn str.replace(UNICODE_SPACES, \" \");\n}\n\nfunction expandPath(p: string): string {\n\tconst normalized = normalizeUnicodeSpaces(p);\n\tif (normalized.startsWith(\"~/\")) {\n\t\treturn path.join(os.homedir(), normalized.slice(2));\n\t}\n\tif (normalized.startsWith(\"~\")) {\n\t\treturn path.join(os.homedir(), normalized.slice(1));\n\t}\n\treturn normalized;\n}\n\nfunction resolvePath(extPath: string, cwd: string): string {\n\tconst expanded = expandPath(extPath);\n\tif (path.isAbsolute(expanded)) {\n\t\treturn expanded;\n\t}\n\treturn path.resolve(cwd, expanded);\n}\n\ntype HandlerFn = (...args: unknown[]) => Promise<unknown>;\n\n/**\n * Create a runtime with throwing stubs for action methods.\n * Runner.bindCore() replaces these with real implementations.\n */\nexport function createExtensionRuntime(): ExtensionRuntime {\n\tconst notInitialized = () => {\n\t\tthrow new Error(\"Extension runtime not initialized. Action methods cannot be called during extension loading.\");\n\t};\n\tconst state: { staleMessage?: string } = {};\n\tconst assertActive = () => {\n\t\tif (state.staleMessage) {\n\t\t\tthrow new Error(state.staleMessage);\n\t\t}\n\t};\n\n\tconst runtime: ExtensionRuntime = {\n\t\tsendMessage: notInitialized,\n\t\tsendUserMessage: notInitialized,\n\t\tappendEntry: notInitialized,\n\t\tfoldEntry: notInitialized,\n\t\tsetSessionName: notInitialized,\n\t\tgetSessionName: notInitialized,\n\t\tsetLabel: notInitialized,\n\t\tgetActiveTools: notInitialized,\n\t\tgetAllTools: notInitialized,\n\t\tsetActiveTools: notInitialized,\n\t\trefreshTools: () => {},\n\t\tgetCommands: notInitialized,\n\t\tsetModel: () => Promise.reject(new Error(\"Extension runtime not initialized\")),\n\t\tgetThinkingLevel: notInitialized,\n\t\tsetThinkingLevel: notInitialized,\n\t\tflagValues: new Map(),\n\t\tpendingProviderRegistrations: [],\n\t\tpendingChannelRegistrations: [],\n\t\tresolvedChannels: new Map(),\n\t\tregisterChannel: (name: string) => {\n\t\t\tif (runtime.resolvedChannels.has(name)) {\n\t\t\t\treturn runtime.resolvedChannels.get(name)!;\n\t\t\t}\n\t\t\tconst existing = runtime.pendingChannelRegistrations.find((p) => p.name === name);\n\t\t\tif (existing) {\n\t\t\t\tthrow new Error(`Channel \"${name}\" is already registered`);\n\t\t\t}\n\n\t\t\tconst bufferedSends: unknown[] = [];\n\t\t\tconst bufferedHandlers: ((data: unknown) => void)[] = [];\n\t\t\tlet realChannel: Channel | undefined;\n\n\t\t\tconst deferred: Channel = {\n\t\t\t\tname,\n\t\t\t\tsend: (data: unknown) => {\n\t\t\t\t\tif (realChannel) {\n\t\t\t\t\t\trealChannel.send(data);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbufferedSends.push(data);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tonReceive: (handler: (data: unknown) => void) => {\n\t\t\t\t\tif (realChannel) {\n\t\t\t\t\t\treturn realChannel.onReceive(handler);\n\t\t\t\t\t}\n\t\t\t\t\tbufferedHandlers.push(handler);\n\t\t\t\t\treturn () => {\n\t\t\t\t\t\tconst idx = bufferedHandlers.indexOf(handler);\n\t\t\t\t\t\tif (idx >= 0) bufferedHandlers.splice(idx, 1);\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tinvoke: (data: unknown, timeoutMs?: number) => {\n\t\t\t\t\tif (realChannel) {\n\t\t\t\t\t\treturn realChannel.invoke(data, timeoutMs);\n\t\t\t\t\t}\n\t\t\t\t\treturn Promise.reject(new Error(`Channel \"${name}\" not yet resolved`));\n\t\t\t\t},\n\t\t\t\tcall: (method: string, params: Record<string, unknown>, timeoutMs?: number) => {\n\t\t\t\t\tif (realChannel) {\n\t\t\t\t\t\treturn realChannel.call(method, params, timeoutMs);\n\t\t\t\t\t}\n\t\t\t\t\treturn Promise.reject(new Error(`Channel \"${name}\" not yet resolved`));\n\t\t\t\t},\n\t\t\t};\n\n\t\t\truntime.pendingChannelRegistrations.push({\n\t\t\t\tname,\n\t\t\t\tresolve: (channel: Channel) => {\n\t\t\t\t\trealChannel = channel;\n\t\t\t\t\tfor (const handler of bufferedHandlers) {\n\t\t\t\t\t\tchannel.onReceive(handler);\n\t\t\t\t\t}\n\t\t\t\t\tbufferedHandlers.length = 0;\n\t\t\t\t\tfor (const buffered of bufferedSends) {\n\t\t\t\t\t\tchannel.send(buffered);\n\t\t\t\t\t}\n\t\t\t\t\tbufferedSends.length = 0;\n\t\t\t\t},\n\t\t\t\treject: (_err: Error) => {},\n\t\t\t});\n\n\t\t\treturn deferred;\n\t\t},\n\t\tcallLLM: () => Promise.reject(new Error(\"Extension runtime not initialized\")),\n\t\tcallLLMStructured: () => Promise.reject(new Error(\"Extension runtime not initialized\")),\n\t\tforkAgent: () => Promise.reject(new Error(\"Extension runtime not initialized\")),\n\t\tbackground: notInitialized,\n\t\tassertActive,\n\t\tinvalidate: (message) => {\n\t\t\tstate.staleMessage ??=\n\t\t\t\tmessage ??\n\t\t\t\t\"This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().\";\n\t\t},\n\t\t// Pre-bind: queue registrations so bindCore() can flush them once the\n\t\t// model registry is available. bindCore() replaces both with direct calls.\n\t\tregisterProvider: (name, config, extensionPath = \"<unknown>\") => {\n\t\t\truntime.pendingProviderRegistrations.push({ name, config, extensionPath });\n\t\t},\n\t\tunregisterProvider: (name) => {\n\t\t\truntime.pendingProviderRegistrations = runtime.pendingProviderRegistrations.filter((r) => r.name !== name);\n\t\t},\n\t};\n\n\treturn runtime;\n}\n\n/**\n * Create the ExtensionAPI for an extension.\n * Registration methods write to the extension object.\n * Action methods delegate to the shared runtime.\n */\nfunction createExtensionAPI(\n\textension: Extension,\n\truntime: ExtensionRuntime,\n\tcwd: string,\n\teventBus: EventBus,\n): ExtensionAPI {\n\tconst api = {\n\t\t// Registration methods - write to extension\n\t\ton(event: string, handler: HandlerFn): void {\n\t\t\truntime.assertActive();\n\t\t\tconst list = extension.handlers.get(event) ?? [];\n\t\t\tlist.push(handler);\n\t\t\textension.handlers.set(event, list);\n\t\t},\n\n\t\tregisterTool(tool: ToolDefinition): void {\n\t\t\truntime.assertActive();\n\t\t\textension.tools.set(tool.name, {\n\t\t\t\tdefinition: tool,\n\t\t\t\tsourceInfo: extension.sourceInfo,\n\t\t\t});\n\t\t\truntime.refreshTools();\n\t\t},\n\n\t\tregisterCommand(name: string, options: Omit<RegisteredCommand, \"name\" | \"sourceInfo\">): void {\n\t\t\truntime.assertActive();\n\t\t\textension.commands.set(name, {\n\t\t\t\tname,\n\t\t\t\tsourceInfo: extension.sourceInfo,\n\t\t\t\t...options,\n\t\t\t});\n\t\t},\n\n\t\tregisterShortcut(\n\t\t\tshortcut: KeyId,\n\t\t\toptions: {\n\t\t\t\tdescription?: string;\n\t\t\t\thandler: (ctx: import(\"./types.js\").ExtensionContext) => Promise<void> | void;\n\t\t\t},\n\t\t): void {\n\t\t\truntime.assertActive();\n\t\t\textension.shortcuts.set(shortcut, { shortcut, extensionPath: extension.path, extensionName: extension.name, ...options });\n\t\t},\n\n\t\tregisterFlag(\n\t\t\tname: string,\n\t\t\toptions: { description?: string; type: \"boolean\" | \"string\"; default?: boolean | string },\n\t\t): void {\n\t\t\truntime.assertActive();\n\t\t\textension.flags.set(name, { name, extensionPath: extension.path, ...options });\n\t\t\tif (options.default !== undefined && !runtime.flagValues.has(name)) {\n\t\t\t\truntime.flagValues.set(name, options.default);\n\t\t\t}\n\t\t},\n\n\t\tregisterMessageRenderer<T>(customType: string, renderer: MessageRenderer<T>): void {\n\t\t\truntime.assertActive();\n\t\t\textension.messageRenderers.set(customType, renderer as MessageRenderer);\n\t\t},\n\n\t\t// Flag access - checks extension registered it, reads from runtime\n\t\tgetFlag(name: string): boolean | string | undefined {\n\t\t\truntime.assertActive();\n\t\t\tif (!extension.flags.has(name)) return undefined;\n\t\t\treturn runtime.flagValues.get(name);\n\t\t},\n\n\t\t// Action methods - delegate to shared runtime\n\t\tsendMessage(message, options): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.sendMessage(message, options);\n\t\t},\n\n\t\tsendUserMessage(content, options): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.sendUserMessage(content, options);\n\t\t},\n\n\t\tappendEntry(customType: string, data?: unknown): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.appendEntry(customType, data);\n\t\t},\n\n\t\tsetSessionName(name: string): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.setSessionName(name);\n\t\t},\n\n\t\tgetSessionName(): string | undefined {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.getSessionName();\n\t\t},\n\n\t\tsetLabel(entryId: string, label: string | undefined): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.setLabel(entryId, label);\n\t\t},\n\n\t\texec(command: string, args: string[], options?: ExecOptions) {\n\t\t\truntime.assertActive();\n\t\t\treturn execCommand(command, args, options?.cwd ?? cwd, options);\n\t\t},\n\n\t\tgetActiveTools(): string[] {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.getActiveTools();\n\t\t},\n\n\t\tgetAllTools() {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.getAllTools();\n\t\t},\n\n\t\tsetActiveTools(toolNames: string[]): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.setActiveTools(toolNames);\n\t\t},\n\n\t\tgetCommands() {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.getCommands();\n\t\t},\n\n\t\tsetModel(model) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.setModel(model);\n\t\t},\n\n\t\tgetThinkingLevel() {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.getThinkingLevel();\n\t\t},\n\n\t\tsetThinkingLevel(level) {\n\t\t\truntime.assertActive();\n\t\t\truntime.setThinkingLevel(level);\n\t\t},\n\n\t\tregisterProvider(name: string, config: ProviderConfig) {\n\t\t\truntime.assertActive();\n\t\t\truntime.registerProvider(name, config, extension.path);\n\t\t},\n\n\t\tunregisterProvider(name: string) {\n\t\t\truntime.assertActive();\n\t\t\truntime.unregisterProvider(name, extension.path);\n\t\t},\n\n\t\tregisterChannel(name: string) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.registerChannel(name);\n\t\t},\n\n\t\tcallLLM(options) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.callLLM(options);\n\t\t},\n\n\t\tcallLLMStructured(options) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.callLLMStructured(options);\n\t\t},\n\n\t\tforkAgent(prompt, options) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.forkAgent(prompt, options);\n\t\t},\n\n\t\tbackground(fn) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.background(fn);\n\t\t},\n\n\t\tfoldEntry(entryId, summary, originalTokens) {\n\t\t\truntime.assertActive();\n\t\t\truntime.foldEntry(entryId, summary, originalTokens);\n\t\t},\n\n\t\tsetName(name: string) {\n\t\t\textension.name = name;\n\t\t},\n\n\t\tget extensionName() {\n\t\t\treturn extension.name;\n\t\t},\n\n\t\tevents: eventBus,\n\t} as ExtensionAPI;\n\n\treturn api;\n}\n\nlet sharedJiti: ReturnType<typeof createJiti> | undefined;\n\nexport function invalidateExtensionModuleCache(): void {\n\tsharedJiti = undefined;\n}\n\nasync function loadExtensionModule(extensionPath: string) {\n\tif (!sharedJiti) {\n\t\tsharedJiti = createJiti(import.meta.url, {\n\t\t\tmoduleCache: true,\n\t\t\t...(isBunBinary ? { virtualModules: VIRTUAL_MODULES, tryNative: false } : { alias: getAliases() }),\n\t\t});\n\t}\n\n\tconst module = await sharedJiti.import(extensionPath, { default: true });\n\tconst factory = module as ExtensionFactory;\n\treturn typeof factory !== \"function\" ? undefined : factory;\n}\n\n/**\n * Create an Extension object with empty collections.\n */\nfunction createExtension(extensionPath: string, resolvedPath: string): Extension {\n\tconst source =\n\t\textensionPath.startsWith(\"<\") && extensionPath.endsWith(\">\")\n\t\t\t? extensionPath.slice(1, -1).split(\":\")[0] || \"temporary\"\n\t\t\t: \"local\";\n\tconst baseDir = extensionPath.startsWith(\"<\") ? undefined : path.dirname(resolvedPath);\n\tconst name = path.basename(extensionPath, path.extname(extensionPath));\n\n\treturn {\n\t\tname,\n\t\tpath: extensionPath,\n\t\tresolvedPath,\n\t\tsourceInfo: createSyntheticSourceInfo(extensionPath, { source, baseDir }),\n\t\thandlers: new Map(),\n\t\ttools: new Map(),\n\t\tmessageRenderers: new Map(),\n\t\tcommands: new Map(),\n\t\tflags: new Map(),\n\t\tshortcuts: new Map(),\n\t};\n}\n\nasync function loadExtension(\n\textensionPath: string,\n\tcwd: string,\n\teventBus: EventBus,\n\truntime: ExtensionRuntime,\n): Promise<{ extension: Extension | null; error: string | null }> {\n\tconst resolvedPath = resolvePath(extensionPath, cwd);\n\tconst extName = path.basename(extensionPath, path.extname(extensionPath));\n\n\ttry {\n\t\tconst factory = await loadExtensionModule(resolvedPath);\n\t\ttime(` jiti.import(${extName})`);\n\t\tif (!factory) {\n\t\t\treturn { extension: null, error: `Extension does not export a valid factory function: ${extensionPath}` };\n\t\t}\n\n\t\tconst extension = createExtension(extensionPath, resolvedPath);\n\t\tconst api = createExtensionAPI(extension, runtime, cwd, eventBus);\n\t\tawait factory(api);\n\t\ttime(` factory(${extName})`);\n\n\t\treturn { extension, error: null };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn { extension: null, error: `Failed to load extension: ${message}` };\n\t}\n}\n\n/**\n * Create an Extension from an inline factory function.\n */\nexport async function loadExtensionFromFactory(\n\tfactory: ExtensionFactory,\n\tcwd: string,\n\teventBus: EventBus,\n\truntime: ExtensionRuntime,\n\textensionPath = \"<inline>\",\n): Promise<Extension> {\n\tconst extension = createExtension(extensionPath, extensionPath);\n\tconst api = createExtensionAPI(extension, runtime, cwd, eventBus);\n\tawait factory(api);\n\treturn extension;\n}\n\n/**\n * Load extensions from paths.\n */\nexport async function loadExtensions(paths: string[], cwd: string, eventBus?: EventBus): Promise<LoadExtensionsResult> {\n\tconst extensions: Extension[] = [];\n\tconst errors: Array<{ path: string; error: string }> = [];\n\tconst resolvedEventBus = eventBus ?? createEventBus();\n\tconst runtime = createExtensionRuntime();\n\n\tif (paths.length <= 1) {\n\t\tfor (const extPath of paths) {\n\t\t\tconst { extension, error } = await loadExtension(extPath, cwd, resolvedEventBus, runtime);\n\t\t\tif (error) {\n\t\t\t\terrors.push({ path: extPath, error });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (extension) {\n\t\t\t\textensions.push(extension);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tconst results = await Promise.allSettled(\n\t\t\tpaths.map((extPath) => loadExtension(extPath, cwd, resolvedEventBus, runtime)),\n\t\t);\n\t\tfor (let i = 0; i < results.length; i++) {\n\t\t\tconst result = results[i];\n\t\t\tif (result.status === \"fulfilled\") {\n\t\t\t\tconst { extension, error } = result.value;\n\t\t\t\tif (error) {\n\t\t\t\t\terrors.push({ path: paths[i], error });\n\t\t\t\t} else if (extension) {\n\t\t\t\t\textensions.push(extension);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\terrors.push({ path: paths[i], error: `Failed to load extension: ${result.reason}` });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\textensions,\n\t\terrors,\n\t\truntime,\n\t};\n}\n\ninterface PiManifest {\n\textensions?: string[];\n\tthemes?: string[];\n\tskills?: string[];\n\tprompts?: string[];\n}\n\nfunction readPiManifest(packageJsonPath: string): PiManifest | null {\n\ttry {\n\t\tconst content = fs.readFileSync(packageJsonPath, \"utf-8\");\n\t\tconst pkg = JSON.parse(content);\n\t\tif (pkg.pi && typeof pkg.pi === \"object\") {\n\t\t\treturn pkg.pi as PiManifest;\n\t\t}\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isExtensionFile(name: string): boolean {\n\treturn name.endsWith(\".ts\") || name.endsWith(\".js\");\n}\n\n/**\n * Resolve extension entry points from a directory.\n *\n * Checks for:\n * 1. package.json with \"pi.extensions\" field -> returns declared paths\n * 2. index.ts or index.js -> returns the index file\n *\n * Returns resolved paths or null if no entry points found.\n */\nfunction resolveExtensionEntries(dir: string): string[] | null {\n\t// Check for package.json with \"pi\" field first\n\tconst packageJsonPath = path.join(dir, \"package.json\");\n\tif (fs.existsSync(packageJsonPath)) {\n\t\tconst manifest = readPiManifest(packageJsonPath);\n\t\tif (manifest?.extensions?.length) {\n\t\t\tconst entries: string[] = [];\n\t\t\tfor (const extPath of manifest.extensions) {\n\t\t\t\tconst resolvedExtPath = path.resolve(dir, extPath);\n\t\t\t\tif (fs.existsSync(resolvedExtPath)) {\n\t\t\t\t\tentries.push(resolvedExtPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (entries.length > 0) {\n\t\t\t\treturn entries;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check for index.ts or index.js\n\tconst indexTs = path.join(dir, \"index.ts\");\n\tconst indexJs = path.join(dir, \"index.js\");\n\tif (fs.existsSync(indexTs)) {\n\t\treturn [indexTs];\n\t}\n\tif (fs.existsSync(indexJs)) {\n\t\treturn [indexJs];\n\t}\n\n\treturn null;\n}\n\n/**\n * Discover extensions in a directory.\n *\n * Discovery rules:\n * 1. Direct files: `extensions/*.ts` or `*.js` → load\n * 2. Subdirectory with index: `extensions/* /index.ts` or `index.js` → load\n * 3. Subdirectory with package.json: `extensions/* /package.json` with \"pi\" field → load what it declares\n *\n * No recursion beyond one level. Complex packages must use package.json manifest.\n */\nfunction discoverExtensionsInDir(dir: string): string[] {\n\tif (!fs.existsSync(dir)) {\n\t\treturn [];\n\t}\n\n\tconst discovered: string[] = [];\n\n\ttry {\n\t\tconst entries = fs.readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst entryPath = path.join(dir, entry.name);\n\n\t\t\t// 1. Direct files: *.ts or *.js\n\t\t\tif ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {\n\t\t\t\tdiscovered.push(entryPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// 2 & 3. Subdirectories\n\t\t\tif (entry.isDirectory() || entry.isSymbolicLink()) {\n\t\t\t\tconst entries = resolveExtensionEntries(entryPath);\n\t\t\t\tif (entries) {\n\t\t\t\t\tdiscovered.push(...entries);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn [];\n\t}\n\n\treturn discovered;\n}\n\n/**\n * Discover and load extensions from standard locations.\n */\nexport async function discoverAndLoadExtensions(\n\tconfiguredPaths: string[],\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n\teventBus?: EventBus,\n): Promise<LoadExtensionsResult> {\n\tconst allPaths: string[] = [];\n\tconst seen = new Set<string>();\n\n\tconst addPaths = (paths: string[]) => {\n\t\tfor (const p of paths) {\n\t\t\tconst resolved = path.resolve(p);\n\t\t\tif (!seen.has(resolved)) {\n\t\t\t\tseen.add(resolved);\n\t\t\t\tallPaths.push(p);\n\t\t\t}\n\t\t}\n\t};\n\n\t// 1. Project-local extensions: cwd/${CONFIG_DIR_NAME}/extensions/\n\tconst localExtDir = path.join(cwd, CONFIG_DIR_NAME, \"extensions\");\n\taddPaths(discoverExtensionsInDir(localExtDir));\n\n\t// 2. Global extensions: agentDir/extensions/\n\tconst globalExtDir = path.join(agentDir, \"extensions\");\n\taddPaths(discoverExtensionsInDir(globalExtDir));\n\n\t// 3. Explicitly configured paths\n\tfor (const p of configuredPaths) {\n\t\tconst resolved = resolvePath(p, cwd);\n\t\tif (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {\n\t\t\t// Check for package.json with pi manifest or index.ts\n\t\t\tconst entries = resolveExtensionEntries(resolved);\n\t\t\tif (entries) {\n\t\t\t\taddPaths(entries);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// No explicit entries - discover individual files in directory\n\t\t\taddPaths(discoverExtensionsInDir(resolved));\n\t\t\tcontinue;\n\t\t}\n\n\t\taddPaths([resolved]);\n\t}\n\n\treturn loadExtensions(allPaths, cwd, eventBus);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuBH,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAMhE,OAAO,KAAK,EACX,SAAS,EAET,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EAKpB,MAAM,YAAY,CAAC;AAwGpB;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,gBAAgB,CAoHzD;AA8MD,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAoED;;GAEG;AACH,wBAAsB,wBAAwB,CAC7C,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,gBAAgB,EACzB,aAAa,SAAa,GACxB,OAAO,CAAC,SAAS,CAAC,CAKpB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAyCrH;AA+GD;;GAEG;AACH,wBAAsB,yBAAyB,CAC9C,eAAe,EAAE,MAAM,EAAE,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAsB,EAChC,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,oBAAoB,CAAC,CAyC/B","sourcesContent":["/**\n * Extension loader - loads TypeScript extension modules using jiti.\n *\n */\n\nimport * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport * as _bundledPiAgentCore from \"@dyyz1993/pi-agent-core\";\nimport * as _bundledPiAi from \"@dyyz1993/pi-ai\";\nimport * as _bundledPiAiOauth from \"@dyyz1993/pi-ai/oauth\";\nimport type { KeyId } from \"@dyyz1993/pi-tui\";\nimport * as _bundledPiTui from \"@dyyz1993/pi-tui\";\nimport { createJiti } from \"jiti/static\";\n// Static imports of packages that extensions may use.\n// These MUST be static so Bun bundles them into the compiled binary.\n// The virtualModules option then makes them available to extensions.\nimport * as _bundledTypebox from \"typebox\";\nimport * as _bundledTypeboxCompile from \"typebox/compile\";\nimport * as _bundledTypeboxValue from \"typebox/value\";\nimport { CONFIG_DIR_NAME, getAgentDir, isBunBinary } from \"../../config.js\";\n// NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,\n// avoiding a circular dependency. Extensions can import from @dyyz1993/pi-coding-agent.\nimport * as _bundledPiCodingAgent from \"../../index.js\";\nimport { createEventBus, type EventBus } from \"../event-bus.js\";\nimport type { ExecOptions } from \"../exec.js\";\nimport { execCommand } from \"../exec.js\";\nimport { createSyntheticSourceInfo } from \"../source-info.js\";\nimport { time } from \"../timings.js\";\nimport type { Channel } from \"./channel-types.js\";\nimport type {\n\tExtension,\n\tExtensionAPI,\n\tExtensionFactory,\n\tExtensionRuntime,\n\tLoadExtensionsResult,\n\tMessageRenderer,\n\tProviderConfig,\n\tRegisteredCommand,\n\tToolDefinition,\n} from \"./types.js\";\n\n/** Modules available to extensions via virtualModules (for compiled Bun binary) */\nconst VIRTUAL_MODULES: Record<string, unknown> = {\n\ttypebox: _bundledTypebox,\n\t\"typebox/compile\": _bundledTypeboxCompile,\n\t\"typebox/value\": _bundledTypeboxValue,\n\t\"@sinclair/typebox\": _bundledTypebox,\n\t\"@sinclair/typebox/compile\": _bundledTypeboxCompile,\n\t\"@sinclair/typebox/value\": _bundledTypeboxValue,\n\t\"@dyyz1993/pi-agent-core\": _bundledPiAgentCore,\n\t\"@dyyz1993/pi-tui\": _bundledPiTui,\n\t\"@dyyz1993/pi-ai\": _bundledPiAi,\n\t\"@dyyz1993/pi-ai/oauth\": _bundledPiAiOauth,\n\t\"@dyyz1993/pi-coding-agent\": _bundledPiCodingAgent,\n\t\"@mariozechner/pi-agent-core\": _bundledPiAgentCore,\n\t\"@mariozechner/pi-tui\": _bundledPiTui,\n\t\"@mariozechner/pi-ai\": _bundledPiAi,\n\t\"@mariozechner/pi-ai/oauth\": _bundledPiAiOauth,\n\t\"@mariozechner/pi-coding-agent\": _bundledPiCodingAgent,\n};\n\nconst require = createRequire(import.meta.url);\n\n/**\n * Get aliases for jiti (used in Node.js/development mode).\n * In Bun binary mode, virtualModules is used instead.\n */\nlet _aliases: Record<string, string> | null = null;\n\nfunction getAliases(): Record<string, string> {\n\tif (_aliases) return _aliases;\n\n\tconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\tconst packageIndex = path.resolve(__dirname, \"../..\", \"index.js\");\n\n\tconst typeboxEntry = require.resolve(\"typebox\");\n\tconst typeboxCompileEntry = require.resolve(\"typebox/compile\");\n\tconst typeboxValueEntry = require.resolve(\"typebox/value\");\n\n\tconst packagesRoot = path.resolve(__dirname, \"../../../../\");\n\tconst resolveWorkspaceOrImport = (workspaceRelativePath: string, specifier: string): string => {\n\t\tconst workspacePath = path.join(packagesRoot, workspaceRelativePath);\n\t\tif (fs.existsSync(workspacePath)) {\n\t\t\treturn workspacePath;\n\t\t}\n\t\treturn fileURLToPath(import.meta.resolve(specifier));\n\t};\n\n\tconst piCodingAgentEntry = packageIndex;\n\tconst piAgentCoreEntry = resolveWorkspaceOrImport(\"agent/dist/index.js\", \"@dyyz1993/pi-agent-core\");\n\tconst piTuiEntry = resolveWorkspaceOrImport(\"tui/dist/index.js\", \"@dyyz1993/pi-tui\");\n\tconst piAiEntry = resolveWorkspaceOrImport(\"ai/dist/index.js\", \"@dyyz1993/pi-ai\");\n\tconst piAiOauthEntry = resolveWorkspaceOrImport(\"ai/dist/oauth.js\", \"@dyyz1993/pi-ai/oauth\");\n\n\t_aliases = {\n\t\t\"@dyyz1993/pi-coding-agent\": piCodingAgentEntry,\n\t\t\"@dyyz1993/pi-agent-core\": piAgentCoreEntry,\n\t\t\"@dyyz1993/pi-tui\": piTuiEntry,\n\t\t\"@dyyz1993/pi-ai\": piAiEntry,\n\t\t\"@dyyz1993/pi-ai/oauth\": piAiOauthEntry,\n\t\t\"@mariozechner/pi-coding-agent\": piCodingAgentEntry,\n\t\t\"@mariozechner/pi-agent-core\": piAgentCoreEntry,\n\t\t\"@mariozechner/pi-tui\": piTuiEntry,\n\t\t\"@mariozechner/pi-ai\": piAiEntry,\n\t\t\"@mariozechner/pi-ai/oauth\": piAiOauthEntry,\n\t\ttypebox: typeboxEntry,\n\t\t\"typebox/compile\": typeboxCompileEntry,\n\t\t\"typebox/value\": typeboxValueEntry,\n\t\t\"@sinclair/typebox\": typeboxEntry,\n\t\t\"@sinclair/typebox/compile\": typeboxCompileEntry,\n\t\t\"@sinclair/typebox/value\": typeboxValueEntry,\n\t};\n\n\treturn _aliases;\n}\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/g;\n\nfunction normalizeUnicodeSpaces(str: string): string {\n\treturn str.replace(UNICODE_SPACES, \" \");\n}\n\nfunction expandPath(p: string): string {\n\tconst normalized = normalizeUnicodeSpaces(p);\n\tif (normalized.startsWith(\"~/\")) {\n\t\treturn path.join(os.homedir(), normalized.slice(2));\n\t}\n\tif (normalized.startsWith(\"~\")) {\n\t\treturn path.join(os.homedir(), normalized.slice(1));\n\t}\n\treturn normalized;\n}\n\nfunction resolvePath(extPath: string, cwd: string): string {\n\tconst expanded = expandPath(extPath);\n\tif (path.isAbsolute(expanded)) {\n\t\treturn expanded;\n\t}\n\treturn path.resolve(cwd, expanded);\n}\n\ntype HandlerFn = (...args: unknown[]) => Promise<unknown>;\n\n/**\n * Create a runtime with throwing stubs for action methods.\n * Runner.bindCore() replaces these with real implementations.\n */\nexport function createExtensionRuntime(): ExtensionRuntime {\n\tconst notInitialized = () => {\n\t\tthrow new Error(\"Extension runtime not initialized. Action methods cannot be called during extension loading.\");\n\t};\n\tconst state: { staleMessage?: string } = {};\n\tconst assertActive = () => {\n\t\tif (state.staleMessage) {\n\t\t\tthrow new Error(state.staleMessage);\n\t\t}\n\t};\n\n\tconst runtime: ExtensionRuntime = {\n\t\tsendMessage: notInitialized,\n\t\tsendUserMessage: notInitialized,\n\t\tappendEntry: notInitialized,\n\t\tfoldEntry: notInitialized,\n\t\tsetSessionName: notInitialized,\n\t\tgetSessionName: notInitialized,\n\t\tsetLabel: notInitialized,\n\t\tgetActiveTools: notInitialized,\n\t\tgetAllTools: notInitialized,\n\t\tsetActiveTools: notInitialized,\n\t\trefreshTools: () => {},\n\t\tgetCommands: notInitialized,\n\t\tsetModel: () => Promise.reject(new Error(\"Extension runtime not initialized\")),\n\t\tgetThinkingLevel: notInitialized,\n\t\tsetThinkingLevel: notInitialized,\n\t\tflagValues: new Map(),\n\t\tpendingProviderRegistrations: [],\n\t\tpendingChannelRegistrations: [],\n\t\tresolvedChannels: new Map(),\n\t\tregisterChannel: (name: string) => {\n\t\t\tif (runtime.resolvedChannels.has(name)) {\n\t\t\t\treturn runtime.resolvedChannels.get(name)!;\n\t\t\t}\n\t\t\tconst existing = runtime.pendingChannelRegistrations.find((p) => p.name === name);\n\t\t\tif (existing) {\n\t\t\t\tthrow new Error(`Channel \"${name}\" is already registered`);\n\t\t\t}\n\n\t\t\tconst bufferedSends: unknown[] = [];\n\t\t\tconst bufferedHandlers: ((data: unknown) => void)[] = [];\n\t\t\tlet realChannel: Channel | undefined;\n\n\t\t\tconst deferred: Channel = {\n\t\t\t\tname,\n\t\t\t\tsend: (data: unknown) => {\n\t\t\t\t\tif (realChannel) {\n\t\t\t\t\t\trealChannel.send(data);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbufferedSends.push(data);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tonReceive: (handler: (data: unknown) => void) => {\n\t\t\t\t\tif (realChannel) {\n\t\t\t\t\t\treturn realChannel.onReceive(handler);\n\t\t\t\t\t}\n\t\t\t\t\tbufferedHandlers.push(handler);\n\t\t\t\t\treturn () => {\n\t\t\t\t\t\tconst idx = bufferedHandlers.indexOf(handler);\n\t\t\t\t\t\tif (idx >= 0) bufferedHandlers.splice(idx, 1);\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tinvoke: (data: unknown, timeoutMs?: number) => {\n\t\t\t\t\tif (realChannel) {\n\t\t\t\t\t\treturn realChannel.invoke(data, timeoutMs);\n\t\t\t\t\t}\n\t\t\t\t\treturn Promise.reject(new Error(`Channel \"${name}\" not yet resolved`));\n\t\t\t\t},\n\t\t\t\tcall: (method: string, params: Record<string, unknown>, timeoutMs?: number) => {\n\t\t\t\t\tif (realChannel) {\n\t\t\t\t\t\treturn realChannel.call(method, params, timeoutMs);\n\t\t\t\t\t}\n\t\t\t\t\treturn Promise.reject(new Error(`Channel \"${name}\" not yet resolved`));\n\t\t\t\t},\n\t\t\t};\n\n\t\t\truntime.pendingChannelRegistrations.push({\n\t\t\t\tname,\n\t\t\t\tresolve: (channel: Channel) => {\n\t\t\t\t\trealChannel = channel;\n\t\t\t\t\tfor (const handler of bufferedHandlers) {\n\t\t\t\t\t\tchannel.onReceive(handler);\n\t\t\t\t\t}\n\t\t\t\t\tbufferedHandlers.length = 0;\n\t\t\t\t\tfor (const buffered of bufferedSends) {\n\t\t\t\t\t\tchannel.send(buffered);\n\t\t\t\t\t}\n\t\t\t\t\tbufferedSends.length = 0;\n\t\t\t\t},\n\t\t\t\treject: (_err: Error) => {},\n\t\t\t});\n\n\t\t\treturn deferred;\n\t\t},\n\t\tcallLLM: () => Promise.reject(new Error(\"Extension runtime not initialized\")),\n\t\tcallLLMStructured: () => Promise.reject(new Error(\"Extension runtime not initialized\")),\n\t\tforkAgent: () => Promise.reject(new Error(\"Extension runtime not initialized\")),\n\t\tbackground: notInitialized,\n\t\tassertActive,\n\t\tinvalidate: (message) => {\n\t\t\tstate.staleMessage ??=\n\t\t\t\tmessage ??\n\t\t\t\t\"This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().\";\n\t\t},\n\t\t// Pre-bind: queue registrations so bindCore() can flush them once the\n\t\t// model registry is available. bindCore() replaces both with direct calls.\n\t\tregisterProvider: (name, config, extensionPath = \"<unknown>\") => {\n\t\t\truntime.pendingProviderRegistrations.push({ name, config, extensionPath });\n\t\t},\n\t\tunregisterProvider: (name) => {\n\t\t\truntime.pendingProviderRegistrations = runtime.pendingProviderRegistrations.filter((r) => r.name !== name);\n\t\t},\n\t};\n\n\treturn runtime;\n}\n\n/**\n * Create the ExtensionAPI for an extension.\n * Registration methods write to the extension object.\n * Action methods delegate to the shared runtime.\n */\nfunction createExtensionAPI(\n\textension: Extension,\n\truntime: ExtensionRuntime,\n\tcwd: string,\n\teventBus: EventBus,\n): ExtensionAPI {\n\tconst api = {\n\t\t// Registration methods - write to extension\n\t\ton(event: string, handler: HandlerFn): void {\n\t\t\truntime.assertActive();\n\t\t\tconst list = extension.handlers.get(event) ?? [];\n\t\t\tlist.push(handler);\n\t\t\textension.handlers.set(event, list);\n\t\t},\n\n\t\tregisterTool(tool: ToolDefinition): void {\n\t\t\truntime.assertActive();\n\t\t\textension.tools.set(tool.name, {\n\t\t\t\tdefinition: tool,\n\t\t\t\tsourceInfo: extension.sourceInfo,\n\t\t\t});\n\t\t\truntime.refreshTools();\n\t\t},\n\n\t\tregisterCommand(name: string, options: Omit<RegisteredCommand, \"name\" | \"sourceInfo\">): void {\n\t\t\truntime.assertActive();\n\t\t\textension.commands.set(name, {\n\t\t\t\tname,\n\t\t\t\tsourceInfo: extension.sourceInfo,\n\t\t\t\t...options,\n\t\t\t});\n\t\t},\n\n\t\tregisterShortcut(\n\t\t\tshortcut: KeyId,\n\t\t\toptions: {\n\t\t\t\tdescription?: string;\n\t\t\t\thandler: (ctx: import(\"./types.js\").ExtensionContext) => Promise<void> | void;\n\t\t\t},\n\t\t): void {\n\t\t\truntime.assertActive();\n\t\t\textension.shortcuts.set(shortcut, {\n\t\t\t\tshortcut,\n\t\t\t\textensionPath: extension.path,\n\t\t\t\textensionName: extension.name,\n\t\t\t\t...options,\n\t\t\t});\n\t\t},\n\n\t\tregisterFlag(\n\t\t\tname: string,\n\t\t\toptions: { description?: string; type: \"boolean\" | \"string\"; default?: boolean | string },\n\t\t): void {\n\t\t\truntime.assertActive();\n\t\t\textension.flags.set(name, { name, extensionPath: extension.path, ...options });\n\t\t\tif (options.default !== undefined && !runtime.flagValues.has(name)) {\n\t\t\t\truntime.flagValues.set(name, options.default);\n\t\t\t}\n\t\t},\n\n\t\tregisterMessageRenderer<T>(customType: string, renderer: MessageRenderer<T>): void {\n\t\t\truntime.assertActive();\n\t\t\textension.messageRenderers.set(customType, renderer as MessageRenderer);\n\t\t},\n\n\t\t// Flag access - checks extension registered it, reads from runtime\n\t\tgetFlag(name: string): boolean | string | undefined {\n\t\t\truntime.assertActive();\n\t\t\tif (!extension.flags.has(name)) return undefined;\n\t\t\treturn runtime.flagValues.get(name);\n\t\t},\n\n\t\t// Action methods - delegate to shared runtime\n\t\tsendMessage(message, options): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.sendMessage(message, options);\n\t\t},\n\n\t\tsendUserMessage(content, options): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.sendUserMessage(content, options);\n\t\t},\n\n\t\tappendEntry(customType: string, data?: unknown): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.appendEntry(customType, data);\n\t\t},\n\n\t\tsetSessionName(name: string): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.setSessionName(name);\n\t\t},\n\n\t\tgetSessionName(): string | undefined {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.getSessionName();\n\t\t},\n\n\t\tsetLabel(entryId: string, label: string | undefined): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.setLabel(entryId, label);\n\t\t},\n\n\t\texec(command: string, args: string[], options?: ExecOptions) {\n\t\t\truntime.assertActive();\n\t\t\treturn execCommand(command, args, options?.cwd ?? cwd, options);\n\t\t},\n\n\t\tgetActiveTools(): string[] {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.getActiveTools();\n\t\t},\n\n\t\tgetAllTools() {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.getAllTools();\n\t\t},\n\n\t\tsetActiveTools(toolNames: string[]): void {\n\t\t\truntime.assertActive();\n\t\t\truntime.setActiveTools(toolNames);\n\t\t},\n\n\t\tgetCommands() {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.getCommands();\n\t\t},\n\n\t\tsetModel(model) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.setModel(model);\n\t\t},\n\n\t\tgetThinkingLevel() {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.getThinkingLevel();\n\t\t},\n\n\t\tsetThinkingLevel(level) {\n\t\t\truntime.assertActive();\n\t\t\truntime.setThinkingLevel(level);\n\t\t},\n\n\t\tregisterProvider(name: string, config: ProviderConfig) {\n\t\t\truntime.assertActive();\n\t\t\truntime.registerProvider(name, config, extension.path);\n\t\t},\n\n\t\tunregisterProvider(name: string) {\n\t\t\truntime.assertActive();\n\t\t\truntime.unregisterProvider(name, extension.path);\n\t\t},\n\n\t\tregisterChannel(name: string) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.registerChannel(name);\n\t\t},\n\n\t\tcallLLM(options) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.callLLM(options);\n\t\t},\n\n\t\tcallLLMStructured(options) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.callLLMStructured(options);\n\t\t},\n\n\t\tforkAgent(prompt, options) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.forkAgent(prompt, options);\n\t\t},\n\n\t\tbackground(fn) {\n\t\t\truntime.assertActive();\n\t\t\treturn runtime.background(fn);\n\t\t},\n\n\t\tfoldEntry(entryId, summary, originalTokens) {\n\t\t\truntime.assertActive();\n\t\t\truntime.foldEntry(entryId, summary, originalTokens);\n\t\t},\n\n\t\tsetName(name: string) {\n\t\t\textension.name = name;\n\t\t},\n\n\t\tget extensionName() {\n\t\t\treturn extension.name;\n\t\t},\n\n\t\tevents: eventBus,\n\t} as ExtensionAPI;\n\n\treturn api;\n}\n\nlet sharedJiti: ReturnType<typeof createJiti> | undefined;\n\nexport function invalidateExtensionModuleCache(): void {\n\tsharedJiti = undefined;\n}\n\nasync function loadExtensionModule(extensionPath: string) {\n\tif (!sharedJiti) {\n\t\tsharedJiti = createJiti(import.meta.url, {\n\t\t\tmoduleCache: true,\n\t\t\t...(isBunBinary ? { virtualModules: VIRTUAL_MODULES, tryNative: false } : { alias: getAliases() }),\n\t\t});\n\t}\n\n\tconst module = await sharedJiti.import(extensionPath, { default: true });\n\tconst factory = module as ExtensionFactory;\n\treturn typeof factory !== \"function\" ? undefined : factory;\n}\n\n/**\n * Create an Extension object with empty collections.\n */\nfunction createExtension(extensionPath: string, resolvedPath: string): Extension {\n\tconst source =\n\t\textensionPath.startsWith(\"<\") && extensionPath.endsWith(\">\")\n\t\t\t? extensionPath.slice(1, -1).split(\":\")[0] || \"temporary\"\n\t\t\t: \"local\";\n\tconst baseDir = extensionPath.startsWith(\"<\") ? undefined : path.dirname(resolvedPath);\n\tconst name = path.basename(extensionPath, path.extname(extensionPath));\n\n\treturn {\n\t\tname,\n\t\tpath: extensionPath,\n\t\tresolvedPath,\n\t\tsourceInfo: createSyntheticSourceInfo(extensionPath, { source, baseDir }),\n\t\thandlers: new Map(),\n\t\ttools: new Map(),\n\t\tmessageRenderers: new Map(),\n\t\tcommands: new Map(),\n\t\tflags: new Map(),\n\t\tshortcuts: new Map(),\n\t};\n}\n\nasync function loadExtension(\n\textensionPath: string,\n\tcwd: string,\n\teventBus: EventBus,\n\truntime: ExtensionRuntime,\n): Promise<{ extension: Extension | null; error: string | null }> {\n\tconst resolvedPath = resolvePath(extensionPath, cwd);\n\tconst extName = path.basename(extensionPath, path.extname(extensionPath));\n\n\ttry {\n\t\tconst factory = await loadExtensionModule(resolvedPath);\n\t\ttime(` jiti.import(${extName})`);\n\t\tif (!factory) {\n\t\t\treturn { extension: null, error: `Extension does not export a valid factory function: ${extensionPath}` };\n\t\t}\n\n\t\tconst extension = createExtension(extensionPath, resolvedPath);\n\t\tconst api = createExtensionAPI(extension, runtime, cwd, eventBus);\n\t\tawait factory(api);\n\t\ttime(` factory(${extName})`);\n\n\t\treturn { extension, error: null };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn { extension: null, error: `Failed to load extension: ${message}` };\n\t}\n}\n\n/**\n * Create an Extension from an inline factory function.\n */\nexport async function loadExtensionFromFactory(\n\tfactory: ExtensionFactory,\n\tcwd: string,\n\teventBus: EventBus,\n\truntime: ExtensionRuntime,\n\textensionPath = \"<inline>\",\n): Promise<Extension> {\n\tconst extension = createExtension(extensionPath, extensionPath);\n\tconst api = createExtensionAPI(extension, runtime, cwd, eventBus);\n\tawait factory(api);\n\treturn extension;\n}\n\n/**\n * Load extensions from paths.\n */\nexport async function loadExtensions(paths: string[], cwd: string, eventBus?: EventBus): Promise<LoadExtensionsResult> {\n\tconst extensions: Extension[] = [];\n\tconst errors: Array<{ path: string; error: string }> = [];\n\tconst resolvedEventBus = eventBus ?? createEventBus();\n\tconst runtime = createExtensionRuntime();\n\n\tif (paths.length <= 1) {\n\t\tfor (const extPath of paths) {\n\t\t\tconst { extension, error } = await loadExtension(extPath, cwd, resolvedEventBus, runtime);\n\t\t\tif (error) {\n\t\t\t\terrors.push({ path: extPath, error });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (extension) {\n\t\t\t\textensions.push(extension);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tconst results = await Promise.allSettled(\n\t\t\tpaths.map((extPath) => loadExtension(extPath, cwd, resolvedEventBus, runtime)),\n\t\t);\n\t\tfor (let i = 0; i < results.length; i++) {\n\t\t\tconst result = results[i];\n\t\t\tif (result.status === \"fulfilled\") {\n\t\t\t\tconst { extension, error } = result.value;\n\t\t\t\tif (error) {\n\t\t\t\t\terrors.push({ path: paths[i], error });\n\t\t\t\t} else if (extension) {\n\t\t\t\t\textensions.push(extension);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\terrors.push({ path: paths[i], error: `Failed to load extension: ${result.reason}` });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\textensions,\n\t\terrors,\n\t\truntime,\n\t};\n}\n\ninterface PiManifest {\n\textensions?: string[];\n\tthemes?: string[];\n\tskills?: string[];\n\tprompts?: string[];\n}\n\nfunction readPiManifest(packageJsonPath: string): PiManifest | null {\n\ttry {\n\t\tconst content = fs.readFileSync(packageJsonPath, \"utf-8\");\n\t\tconst pkg = JSON.parse(content);\n\t\tif (pkg.pi && typeof pkg.pi === \"object\") {\n\t\t\treturn pkg.pi as PiManifest;\n\t\t}\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isExtensionFile(name: string): boolean {\n\treturn name.endsWith(\".ts\") || name.endsWith(\".js\");\n}\n\n/**\n * Resolve extension entry points from a directory.\n *\n * Checks for:\n * 1. package.json with \"pi.extensions\" field -> returns declared paths\n * 2. index.ts or index.js -> returns the index file\n *\n * Returns resolved paths or null if no entry points found.\n */\nfunction resolveExtensionEntries(dir: string): string[] | null {\n\t// Check for package.json with \"pi\" field first\n\tconst packageJsonPath = path.join(dir, \"package.json\");\n\tif (fs.existsSync(packageJsonPath)) {\n\t\tconst manifest = readPiManifest(packageJsonPath);\n\t\tif (manifest?.extensions?.length) {\n\t\t\tconst entries: string[] = [];\n\t\t\tfor (const extPath of manifest.extensions) {\n\t\t\t\tconst resolvedExtPath = path.resolve(dir, extPath);\n\t\t\t\tif (fs.existsSync(resolvedExtPath)) {\n\t\t\t\t\tentries.push(resolvedExtPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (entries.length > 0) {\n\t\t\t\treturn entries;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check for index.ts or index.js\n\tconst indexTs = path.join(dir, \"index.ts\");\n\tconst indexJs = path.join(dir, \"index.js\");\n\tif (fs.existsSync(indexTs)) {\n\t\treturn [indexTs];\n\t}\n\tif (fs.existsSync(indexJs)) {\n\t\treturn [indexJs];\n\t}\n\n\treturn null;\n}\n\n/**\n * Discover extensions in a directory.\n *\n * Discovery rules:\n * 1. Direct files: `extensions/*.ts` or `*.js` → load\n * 2. Subdirectory with index: `extensions/* /index.ts` or `index.js` → load\n * 3. Subdirectory with package.json: `extensions/* /package.json` with \"pi\" field → load what it declares\n *\n * No recursion beyond one level. Complex packages must use package.json manifest.\n */\nfunction discoverExtensionsInDir(dir: string): string[] {\n\tif (!fs.existsSync(dir)) {\n\t\treturn [];\n\t}\n\n\tconst discovered: string[] = [];\n\n\ttry {\n\t\tconst entries = fs.readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst entryPath = path.join(dir, entry.name);\n\n\t\t\t// 1. Direct files: *.ts or *.js\n\t\t\tif ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {\n\t\t\t\tdiscovered.push(entryPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// 2 & 3. Subdirectories\n\t\t\tif (entry.isDirectory() || entry.isSymbolicLink()) {\n\t\t\t\tconst entries = resolveExtensionEntries(entryPath);\n\t\t\t\tif (entries) {\n\t\t\t\t\tdiscovered.push(...entries);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn [];\n\t}\n\n\treturn discovered;\n}\n\n/**\n * Discover and load extensions from standard locations.\n */\nexport async function discoverAndLoadExtensions(\n\tconfiguredPaths: string[],\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n\teventBus?: EventBus,\n): Promise<LoadExtensionsResult> {\n\tconst allPaths: string[] = [];\n\tconst seen = new Set<string>();\n\n\tconst addPaths = (paths: string[]) => {\n\t\tfor (const p of paths) {\n\t\t\tconst resolved = path.resolve(p);\n\t\t\tif (!seen.has(resolved)) {\n\t\t\t\tseen.add(resolved);\n\t\t\t\tallPaths.push(p);\n\t\t\t}\n\t\t}\n\t};\n\n\t// 1. Project-local extensions: cwd/${CONFIG_DIR_NAME}/extensions/\n\tconst localExtDir = path.join(cwd, CONFIG_DIR_NAME, \"extensions\");\n\taddPaths(discoverExtensionsInDir(localExtDir));\n\n\t// 2. Global extensions: agentDir/extensions/\n\tconst globalExtDir = path.join(agentDir, \"extensions\");\n\taddPaths(discoverExtensionsInDir(globalExtDir));\n\n\t// 3. Explicitly configured paths\n\tfor (const p of configuredPaths) {\n\t\tconst resolved = resolvePath(p, cwd);\n\t\tif (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {\n\t\t\t// Check for package.json with pi manifest or index.ts\n\t\t\tconst entries = resolveExtensionEntries(resolved);\n\t\t\tif (entries) {\n\t\t\t\taddPaths(entries);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// No explicit entries - discover individual files in directory\n\t\t\taddPaths(discoverExtensionsInDir(resolved));\n\t\t\tcontinue;\n\t\t}\n\n\t\taddPaths([resolved]);\n\t}\n\n\treturn loadExtensions(allPaths, cwd, eventBus);\n}\n"]}
|
|
@@ -12,7 +12,6 @@ import * as _bundledPiAi from "@dyyz1993/pi-ai";
|
|
|
12
12
|
import * as _bundledPiAiOauth from "@dyyz1993/pi-ai/oauth";
|
|
13
13
|
import * as _bundledPiTui from "@dyyz1993/pi-tui";
|
|
14
14
|
import { createJiti } from "jiti/static";
|
|
15
|
-
import { time } from "../timings.js";
|
|
16
15
|
// Static imports of packages that extensions may use.
|
|
17
16
|
// These MUST be static so Bun bundles them into the compiled binary.
|
|
18
17
|
// The virtualModules option then makes them available to extensions.
|
|
@@ -26,6 +25,7 @@ import * as _bundledPiCodingAgent from "../../index.js";
|
|
|
26
25
|
import { createEventBus } from "../event-bus.js";
|
|
27
26
|
import { execCommand } from "../exec.js";
|
|
28
27
|
import { createSyntheticSourceInfo } from "../source-info.js";
|
|
28
|
+
import { time } from "../timings.js";
|
|
29
29
|
/** Modules available to extensions via virtualModules (for compiled Bun binary) */
|
|
30
30
|
const VIRTUAL_MODULES = {
|
|
31
31
|
typebox: _bundledTypebox,
|
|
@@ -262,7 +262,12 @@ function createExtensionAPI(extension, runtime, cwd, eventBus) {
|
|
|
262
262
|
},
|
|
263
263
|
registerShortcut(shortcut, options) {
|
|
264
264
|
runtime.assertActive();
|
|
265
|
-
extension.shortcuts.set(shortcut, {
|
|
265
|
+
extension.shortcuts.set(shortcut, {
|
|
266
|
+
shortcut,
|
|
267
|
+
extensionPath: extension.path,
|
|
268
|
+
extensionName: extension.name,
|
|
269
|
+
...options,
|
|
270
|
+
});
|
|
266
271
|
},
|
|
267
272
|
registerFlag(name, options) {
|
|
268
273
|
runtime.assertActive();
|