@dyyz1993/pi-coding-agent 0.74.39 → 0.74.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -7
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-types.d.ts.map +1 -1
- package/dist/core/agent-types.js +1 -0
- package/dist/core/agent-types.js.map +1 -1
- package/dist/extensions/agent-permissions/index.ts +32 -3
- package/dist/extensions/bash-ext/index.ts +7 -6
- package/dist/extensions/lsp/lsp/client/registry.ts +7 -0
- package/dist/extensions/lsp/lsp/client/runtime.ts +7 -0
- package/dist/extensions/lsp/lsp/diagnostics-refresh.test.ts +309 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.d.ts.map +1 -1
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.js +1 -1
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.js.map +1 -1
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.ts +1 -1
- package/dist/extensions/lsp/lsp/hooks/writethrough.ts +1 -0
- package/dist/extensions/lsp/lsp/lsp.test.ts +6 -6
- package/dist/extensions/lsp/lsp/utils/diagnostics-wait.ts +9 -7
- package/dist/modes/rpc/rpc-client.d.ts +10 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +8 -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 +20 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +28 -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 +1 -1
|
@@ -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;IAEnC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAExF,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;AAE/C,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,KAAK,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAuFD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,WAAW,EAAE,CAqEjF;AAsBD,wBAAgB,qBAAqB,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,WAAW,EAAE,CAQ/E;AAED,wBAAgB,gBAAgB,IAAI,WAAW,EAAE,CAsChD;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\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
|
+
{"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\tpermissionMode: \"plan\",\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
|
@@ -212,6 +212,7 @@ export function getBuiltinAgents() {
|
|
|
212
212
|
description: "Planning mode, output analysis and specs only",
|
|
213
213
|
tools: ["read", "grep", "find", "ls", "glob"],
|
|
214
214
|
disallowedTools: ["edit", "write", "bash"],
|
|
215
|
+
permissionMode: "plan",
|
|
215
216
|
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
217
|
source: "builtin",
|
|
217
218
|
filePath: "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-types.js","sourceRoot":"","sources":["../../src/core/agent-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAoE3D,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC;IAClD,aAAa;IACb,OAAO;IACP,gBAAgB;IAChB,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,WAAW;IACX,eAAe;IACf,MAAM;IACN,eAAe;IACf,MAAM;CACN,CAAC,CAAC;AAEH,MAAM,mBAAmB,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEjG,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE9E,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;AAEjE,SAAS,WAAW,CAAC,GAAW,EAAE,GAAY,EAAW;IACxD,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAExD,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC/C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC7B,OAAO,GAAG;iBACR,KAAK,CAAC,GAAG,CAAC;iBACV,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBAC5B,MAAM,CAAC,OAAO,CAAC,CAAC;QACnB,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,IAAI,OAAO,GAAG,KAAK,SAAS;YAAE,OAAO,GAAG,CAAC;QACzC,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC;QACpE,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC;QACxC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACnC,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3C,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,OAAO,GAAG,CAAC;AAAA,CACX;AAED,SAAS,UAAU,CAAC,GAAY,EAA0B;IACzD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACtD,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAA8B,CAAC,EAAE,CAAC;QAChF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;YAAE,SAAS;QACvC,MAAM,MAAM,GAAgB,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YAC1B,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,SAAS;YAC1C,MAAM,GAAG,GAAG,CAA4B,CAAC;YACzC,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAC/D,MAAM,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,SAAS;oBACf,OAAO,EAAE,GAAG,CAAC,OAAO;oBACpB,EAAE,EAAE,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;oBACnD,KAAK,EAAE,GAAG,CAAC,KAAK,KAAK,IAAI;iBACzB,CAAC,CAAC;YACJ,CAAC;iBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACpE,MAAM,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,QAAQ;oBACd,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,EAAE,EAAE,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;iBACnD,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;IAC9C,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CACzD;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,MAAmB,EAAiB;IAClF,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC;IACf,CAAC;IAED,IAAI,OAAoB,CAAC;IACzB,IAAI,CAAC;QACJ,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,MAAM,CAAC;IACf,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,SAAS;QAC1C,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE;YAAE,SAAS;QAEzD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACJ,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACR,SAAS;QACV,CAAC;QAED,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAA0B,OAAO,CAAC,CAAC;QAEjF,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;YACnD,SAAS;QACV,CAAC;QAED,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,CAAyB,CAAC;QAC9E,MAAM,eAAe,GAAG,WAAW,CAAC,iBAAiB,EAAE,WAAW,CAAC,eAAe,CAAyB,CAAC;QAC5G,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAyB,CAAC;QACjF,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,SAAS,GACd,WAAW,CAAC,SAAS,IAAI,OAAO,WAAW,CAAC,SAAS,KAAK,QAAQ;YACjE,CAAC,CAAE,WAAW,CAAC,SAAoC;YACnD,CAAC,CAAC,SAAS,CAAC;QAEd,MAAM,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,CAAW;YACrD,WAAW,EAAE,WAAW,CAAC,aAAa,EAAE,WAAW,CAAC,WAAW,CAAW;YAC1E,KAAK,EAAE,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;YACpD,eAAe,EAAE,eAAe,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS;YAC5F,KAAK,EAAE,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,CAAuB;YACpE,YAAY,EAAE,IAAI;YAClB,MAAM;YACN,QAAQ;YACR,cAAc,EAAE,WAAW,CAAC,gBAAgB,EAAE,WAAW,CAAC,cAAc,CAA+B;YACvG,QAAQ,EAAE,WAAW,CAAC,UAAU,EAAE,WAAW,CAAC,QAAQ,CAAuB;YAC7E,MAAM,EAAE,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAuB;YACvE,KAAK,EAAE,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,CAA2B;YACxE,UAAU,EAAE,WAAW,CAAC,YAAY,EAAE,WAAW,CAAC,UAAU,CAAwB;YACpF,MAAM,EAAE,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAA4B;YAC5E,SAAS,EAAE,WAAW,CAAC,WAAW,EAAE,WAAW,CAAC,SAAS,CAA8B;YACvF,aAAa,EAAE,WAAW,CAAC,eAAe,EAAE,WAAW,CAAC,aAAa,CAAuB;YAC5F,MAAM,EAAE,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;YACxD,KAAK;YACL,SAAS;YACT,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,CAA0B;YACpE,aAAa,EAAE,WAAW,CAAC,eAAe,EAAE,WAAW,CAAC,aAAa,CAAuB;YAC5F,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,CAA0B;YACpE,MAAM,EAAE,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAwB;SACxE,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd;AAED,SAAS,WAAW,CAAC,CAAS,EAAW;IACxC,IAAI,CAAC;QACJ,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AAAA,CACD;AAED,SAAS,2BAA2B,CAAC,GAAW,EAAiB;IAChE,IAAI,UAAU,GAAG,GAAG,CAAC;IACrB,OAAO,IAAI,EAAE,CAAC;QACb,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QACzD,IAAI,WAAW,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;QAE7C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3C,IAAI,SAAS,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC;QAC1C,UAAU,GAAG,SAAS,CAAC;IACxB,CAAC;AAAA,CACD;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAG,MAAuB,EAAiB;IAChF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;YAC3B,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;AAAA,CACrC;AAED,MAAM,UAAU,gBAAgB,GAAkB;IACjD,OAAO;QACN;YACC,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,0EAA0E;YACvF,YAAY,EAAE,EAAE;YAChB,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,EAAE;YACZ,IAAI,EAAE,SAAS;SACf;QACD;YACC,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,6CAA6C;YAC1D,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC;YACrD,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YAClC,YAAY,EACX,6hBAA6hB;YAC9hB,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,EAAE;YACZ,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,MAAM;SACb;QACD;YACC,IAAI,EAAE,MAAM;YACZ,WAAW,EAAE,+CAA+C;YAC5D,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC;YAC7C,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC;YAC1C,YAAY,EACX,sdAAod;YACrd,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,EAAE;YACZ,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,KAAK;YACX,aAAa,EAAE,MAAM;YACrB,KAAK,EAAE,QAAQ;SACf;KACD,CAAC;AAAA,CACF;AAED,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,KAAiB,EAAE,cAA8B,EAAwB;IACpH,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,QAAQ,CAAC,CAAC;IACnD,MAAM,gBAAgB,GAAG,2BAA2B,CAAC,GAAG,CAAC,CAAC;IAE1D,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAC;IACzC,MAAM,YAAY,GAAkB,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACjF,MAAM,aAAa,GAAG,KAAK,KAAK,MAAM,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;IAClH,MAAM,UAAU,GAAG,cAAc,IAAI,EAAE,CAAC;IACxC,MAAM,YAAY,GAAkB,EAAE,CAAC;IAEvC,MAAM,MAAM,GAAG,qBAAqB,CACnC,aAAa,EACb,YAAY,EACZ,UAAU,EACV,aAAa,EACb,UAAU,EACV,YAAY,CACZ,CAAC;IAEF,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;AAAA,CACpC;AAED,MAAM,UAAU,eAAe,CAAC,MAAqB,EAAE,QAAgB,EAAuC;IAC7G,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAChD,OAAO;QACN,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAC/E,SAAS;KACT,CAAC;AAAA,CACF","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
|
+
{"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\tpermissionMode: \"plan\",\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"]}
|
|
@@ -215,14 +215,43 @@ export default function agentPermissions(pi: ExtensionAPI, ctx: ExtensionContext
|
|
|
215
215
|
pi.on("tool_call", (event) => {
|
|
216
216
|
const vars = (event as { variables?: Record<string, string> }).variables;
|
|
217
217
|
const mode = vars?.["permissionMode"];
|
|
218
|
+
const agentName = vars?.["agentName"] ?? "unknown";
|
|
219
|
+
const allowedTools = vars?.["allowedTools"]?.split(",").filter(Boolean);
|
|
220
|
+
const disallowedTools = vars?.["allowedTools"] !== undefined
|
|
221
|
+
? vars?.["disallowedTools"]?.split(",").filter(Boolean) ?? []
|
|
222
|
+
: vars?.["disallowedTools"]?.split(",").filter(Boolean);
|
|
223
|
+
|
|
224
|
+
// Always check whitelist/blacklist regardless of permissionMode
|
|
225
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
226
|
+
const isAllowed = allowedTools.some((pattern) =>
|
|
227
|
+
matchesToolPattern(event.toolName, event.input as Record<string, unknown>, pattern),
|
|
228
|
+
);
|
|
229
|
+
if (!isAllowed) {
|
|
230
|
+
return {
|
|
231
|
+
block: true,
|
|
232
|
+
reason: `[agent:${agentName}] Tool "${event.toolName}" not in agent's tool whitelist. Allowed: ${allowedTools.join(", ")}`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (disallowedTools && disallowedTools.length > 0) {
|
|
238
|
+
if (matchesDisallowedTool(event.toolName, event.input as Record<string, unknown>, disallowedTools)) {
|
|
239
|
+
return {
|
|
240
|
+
block: true,
|
|
241
|
+
reason: `[agent:${agentName}] Tool "${event.toolName}" is explicitly disallowed.`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Permission mode-based rules (only for non-auto modes)
|
|
218
247
|
if (!mode || mode === "auto" || mode === "dontAsk" || mode === "always-allow") return undefined;
|
|
219
248
|
|
|
220
249
|
const handler = createPermissionHandler({
|
|
221
|
-
name:
|
|
250
|
+
name: agentName,
|
|
222
251
|
description: "",
|
|
223
252
|
permissionMode: mode as AgentConfig["permissionMode"],
|
|
224
|
-
disallowedTools
|
|
225
|
-
tools:
|
|
253
|
+
disallowedTools,
|
|
254
|
+
tools: allowedTools,
|
|
226
255
|
} as AgentConfig);
|
|
227
256
|
|
|
228
257
|
if (!handler) return undefined;
|
|
@@ -73,20 +73,21 @@ type BashToolDetails = _BashToolDetails & {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
const DEFAULT_TIMEOUT_SECONDS = 300;
|
|
76
|
-
const
|
|
76
|
+
const MAX_TIMEOUT_SECONDS = 14400; // 4 hours — foreground commands shouldn't run longer
|
|
77
|
+
const DEFAULT_BACKGROUND_AFTER_SECONDS = 600;
|
|
77
78
|
|
|
78
79
|
const bashSchema = Type.Object({
|
|
79
80
|
command: Type.String({ description: "Bash command to execute" }),
|
|
80
81
|
description: Type.String({ description: "Clear, concise description of what this command does in 5-10 words" }),
|
|
81
82
|
timeout: Type.Optional(
|
|
82
83
|
Type.Number({
|
|
83
|
-
description: `Hard timeout in seconds. Process is killed if still running after this duration. Defaults to ${DEFAULT_TIMEOUT_SECONDS}s (5 minutes). Acts as a safety net to prevent zombie processes.`,
|
|
84
|
+
description: `Hard timeout in seconds (max ${MAX_TIMEOUT_SECONDS}s = 4h). Process is killed if still running after this duration. Defaults to ${DEFAULT_TIMEOUT_SECONDS}s (5 minutes). Acts as a safety net to prevent zombie processes.`,
|
|
84
85
|
}),
|
|
85
86
|
),
|
|
86
87
|
backgroundAfter: Type.Optional(
|
|
87
88
|
Type.Number({
|
|
88
89
|
description:
|
|
89
|
-
|
|
90
|
+
`Soft limit in seconds. If the command runs longer than this, it is automatically moved to background instead of blocking the agent. Default: ${DEFAULT_BACKGROUND_AFTER_SECONDS}s (10 min). Must be less than timeout if set. Use for long-running tasks where the agent should stay productive.`,
|
|
90
91
|
}),
|
|
91
92
|
),
|
|
92
93
|
cwd: Type.Optional(
|
|
@@ -317,8 +318,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
317
318
|
`Execute a bash command. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file.`,
|
|
318
319
|
"",
|
|
319
320
|
"Timeout and background behavior:",
|
|
320
|
-
`- timeout: Hard limit in seconds. Process is killed after this duration. Default: ${DEFAULT_TIMEOUT_SECONDS}s (5 min). This is a safety net — always present.`,
|
|
321
|
-
"- backgroundAfter: Soft limit in seconds. If the command runs longer, it is automatically moved to background. The process keeps running, the agent receives a notification and can continue other work.",
|
|
321
|
+
`- timeout: Hard limit in seconds. Process is killed after this duration. Default: ${DEFAULT_TIMEOUT_SECONDS}s (5 min). Max: ${MAX_TIMEOUT_SECONDS}s (4h). This is a safety net — always present.`,
|
|
322
|
+
"- backgroundAfter: Soft limit in seconds. If the command runs longer, it is automatically moved to background. Default: ${DEFAULT_BACKGROUND_AFTER_SECONDS}s (10 min). The process keeps running, the agent receives a notification and can continue other work.",
|
|
322
323
|
"- If backgroundAfter < timeout: command goes to background first, then gets killed if it reaches timeout.",
|
|
323
324
|
"- If backgroundAfter >= timeout (or not set): command runs until timeout, then gets killed.",
|
|
324
325
|
"",
|
|
@@ -341,7 +342,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
341
342
|
_ctx?: ExtensionContext,
|
|
342
343
|
): Promise<AgentToolResult<BashToolDetails>> {
|
|
343
344
|
return new Promise((resolve, reject) => {
|
|
344
|
-
const effectiveTimeout = timeout ?? DEFAULT_TIMEOUT_SECONDS;
|
|
345
|
+
const effectiveTimeout = Math.min(timeout ?? DEFAULT_TIMEOUT_SECONDS, MAX_TIMEOUT_SECONDS);
|
|
345
346
|
const rawBackgroundAfter = backgroundAfter ?? DEFAULT_BACKGROUND_AFTER_SECONDS;
|
|
346
347
|
const effectiveBackgroundAfter = rawBackgroundAfter < effectiveTimeout ? rawBackgroundAfter : undefined;
|
|
347
348
|
const cwd = cwdParam ?? _ctx?.cwd ?? process.cwd();
|
|
@@ -35,6 +35,7 @@ export interface LspRuntimeRegistry {
|
|
|
35
35
|
requestAll(method: string, params: unknown, options?: LspRuntimeRegistryRequestOptions): Promise<unknown[]>;
|
|
36
36
|
notify(method: string, params: unknown, options?: LspRuntimeRegistryRequestOptions): void;
|
|
37
37
|
getPublishedDiagnostics(filePath?: string): LspDiagnostic[];
|
|
38
|
+
clearPublishedDiagnostics(filePath: string): void;
|
|
38
39
|
getStatus(): LspRuntimeRegistryStatus;
|
|
39
40
|
getStatusForPath(filePath: string): ReturnType<LspClientRuntime["getStatus"]> | undefined;
|
|
40
41
|
}
|
|
@@ -189,6 +190,12 @@ export function createLspRuntimeRegistry(options: LspRuntimeRegistryOptions = {}
|
|
|
189
190
|
return diagnostics;
|
|
190
191
|
},
|
|
191
192
|
|
|
193
|
+
clearPublishedDiagnostics(filePath: string): void {
|
|
194
|
+
for (const { runtime } of entries.values()) {
|
|
195
|
+
runtime.clearPublishedDiagnostics(filePath);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
|
|
192
199
|
getStatus(): LspRuntimeRegistryStatus {
|
|
193
200
|
syncLifecycle();
|
|
194
201
|
const servers = [...entries.values()].map((entry) => ({
|
|
@@ -92,6 +92,7 @@ export interface LspClientRuntime {
|
|
|
92
92
|
request(method: string, params: unknown, timeoutMs?: number): Promise<unknown>;
|
|
93
93
|
notify(method: string, params: unknown): void;
|
|
94
94
|
getPublishedDiagnostics(filePath?: string): LspDiagnostic[];
|
|
95
|
+
clearPublishedDiagnostics(filePath: string): void;
|
|
95
96
|
getStatus(): LspRuntimeStatus;
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -221,6 +222,12 @@ export function createLspClientRuntime(options: LspClientRuntimeOptions = {}): L
|
|
|
221
222
|
return allDiagnostics;
|
|
222
223
|
},
|
|
223
224
|
|
|
225
|
+
clearPublishedDiagnostics(filePath: string): void {
|
|
226
|
+
const uri = pathToFileURL(resolve(cwd, filePath)).href;
|
|
227
|
+
diagnosticsByUri.delete(uri);
|
|
228
|
+
status.diagnosticsCount = countDiagnostics(diagnosticsByUri);
|
|
229
|
+
},
|
|
230
|
+
|
|
224
231
|
getStatus(): LspRuntimeStatus {
|
|
225
232
|
return {
|
|
226
233
|
...status,
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for: edit tool not triggering LSP re-diagnosis
|
|
3
|
+
*
|
|
4
|
+
* Covers 4 fixes:
|
|
5
|
+
* 1. Default diagnostics mode changed from "agent_end" to "edit_write"
|
|
6
|
+
* 2. waitForPushDiagnostics no longer skips when previous diagnostics exist
|
|
7
|
+
* 3. clearPublishedDiagnostics clears stale cache
|
|
8
|
+
* 4. writethrough calls clearPublishedDiagnostics before didOpen
|
|
9
|
+
*/
|
|
10
|
+
import { describe, expect, it, vi } from "vitest";
|
|
11
|
+
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { createDiagnosticsMode } from "./hooks/diagnostics-mode.js";
|
|
15
|
+
import { waitForPushDiagnostics } from "./utils/diagnostics-wait.js";
|
|
16
|
+
import { createWriteThroughHooks, type WriteThroughOptions } from "./hooks/writethrough.js";
|
|
17
|
+
import { createFileTracker } from "./client/file-tracker.js";
|
|
18
|
+
import type { LspRuntimeRegistry } from "./client/registry.js";
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// 1. Default diagnostics mode
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
describe("diagnostics mode default", () => {
|
|
25
|
+
it("defaults to edit_write when no initial value is provided", () => {
|
|
26
|
+
const mode = createDiagnosticsMode();
|
|
27
|
+
expect(mode.get()).toBe("edit_write");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("uses provided initial value when valid", () => {
|
|
31
|
+
const mode = createDiagnosticsMode("agent_end");
|
|
32
|
+
expect(mode.get()).toBe("agent_end");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("falls back to edit_write when invalid initial value is provided", () => {
|
|
36
|
+
const mode = createDiagnosticsMode("invalid" as any);
|
|
37
|
+
expect(mode.get()).toBe("edit_write");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// 2. waitForPushDiagnostics does not skip when old diagnostics exist
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
describe("waitForPushDiagnostics", () => {
|
|
46
|
+
it("waits for new diagnostics even when old diagnostics exist for the file", async () => {
|
|
47
|
+
let callCount = 0;
|
|
48
|
+
const mockRuntime = {
|
|
49
|
+
getPublishedDiagnostics: vi.fn(() => {
|
|
50
|
+
callCount++;
|
|
51
|
+
// Return diagnostics after 2nd poll (simulating fresh push from LSP)
|
|
52
|
+
return callCount >= 3 ? [{ message: "new error", severity: 1, range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } } }] : [];
|
|
53
|
+
}),
|
|
54
|
+
} as unknown as LspRuntimeRegistry;
|
|
55
|
+
|
|
56
|
+
// Use short intervals for fast test
|
|
57
|
+
await waitForPushDiagnostics(mockRuntime, "test.ts", {
|
|
58
|
+
initialDelayMs: 10,
|
|
59
|
+
pollIntervalMs: 10,
|
|
60
|
+
maxWaitMs: 500,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Should have polled multiple times, not just returned immediately
|
|
64
|
+
expect(callCount).toBeGreaterThanOrEqual(3);
|
|
65
|
+
expect(mockRuntime.getPublishedDiagnostics).toHaveBeenCalledWith("test.ts");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns as soon as diagnostics arrive", async () => {
|
|
69
|
+
let callCount = 0;
|
|
70
|
+
const mockRuntime = {
|
|
71
|
+
getPublishedDiagnostics: vi.fn(() => {
|
|
72
|
+
callCount++;
|
|
73
|
+
// Return diagnostics immediately on first call after initial delay
|
|
74
|
+
return callCount >= 2
|
|
75
|
+
? [{ message: "error", severity: 1, range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } } }]
|
|
76
|
+
: [];
|
|
77
|
+
}),
|
|
78
|
+
} as unknown as LspRuntimeRegistry;
|
|
79
|
+
|
|
80
|
+
await waitForPushDiagnostics(mockRuntime, "test.ts", {
|
|
81
|
+
initialDelayMs: 10,
|
|
82
|
+
pollIntervalMs: 10,
|
|
83
|
+
maxWaitMs: 2000,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Should NOT have polled the full maxWait duration
|
|
87
|
+
expect(callCount).toBeLessThan(20);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// 3. clearPublishedDiagnostics
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
describe("clearPublishedDiagnostics", () => {
|
|
96
|
+
it("clears stored diagnostics for a specific file", () => {
|
|
97
|
+
// We test the contract: after clearPublishedDiagnostics, getPublishedDiagnostics returns []
|
|
98
|
+
const diagnosticsMap = new Map<string, unknown[]>();
|
|
99
|
+
diagnosticsMap.set("file:///test.ts", [{ message: "old error" }]);
|
|
100
|
+
|
|
101
|
+
// Simulate what clearPublishedDiagnostics does
|
|
102
|
+
diagnosticsMap.delete("file:///test.ts");
|
|
103
|
+
|
|
104
|
+
expect(diagnosticsMap.has("file:///test.ts")).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("does not affect diagnostics for other files", () => {
|
|
108
|
+
const diagnosticsMap = new Map<string, unknown[]>();
|
|
109
|
+
diagnosticsMap.set("file:///test.ts", [{ message: "ts error" }]);
|
|
110
|
+
diagnosticsMap.set("file:///other.ts", [{ message: "other error" }]);
|
|
111
|
+
|
|
112
|
+
diagnosticsMap.delete("file:///test.ts");
|
|
113
|
+
|
|
114
|
+
expect(diagnosticsMap.has("file:///test.ts")).toBe(false);
|
|
115
|
+
expect(diagnosticsMap.get("file:///other.ts")).toEqual([{ message: "other error" }]);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// 4. writethrough clears stale diagnostics before didOpen
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
describe("writethrough diagnostics refresh", () => {
|
|
124
|
+
function createMockRuntime() {
|
|
125
|
+
return {
|
|
126
|
+
notify: vi.fn(),
|
|
127
|
+
request: vi.fn(async () => []),
|
|
128
|
+
requestAll: vi.fn(async () => []),
|
|
129
|
+
getPublishedDiagnostics: vi.fn(() => []),
|
|
130
|
+
clearPublishedDiagnostics: vi.fn(),
|
|
131
|
+
getStatusForPath: vi.fn(() => ({ state: "ready" })),
|
|
132
|
+
getStatus: vi.fn(() => ({
|
|
133
|
+
state: "ready",
|
|
134
|
+
servers: [],
|
|
135
|
+
configuredServers: 0,
|
|
136
|
+
activeServers: 0,
|
|
137
|
+
})),
|
|
138
|
+
start: vi.fn(async () => {}),
|
|
139
|
+
stop: vi.fn(async () => {}),
|
|
140
|
+
reload: vi.fn(async () => {}),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function createMockPi() {
|
|
145
|
+
const handlers: Record<string, Array<(event: any, ctx: any) => any>> = {};
|
|
146
|
+
return {
|
|
147
|
+
pi: {
|
|
148
|
+
on: vi.fn((event: string, handler: any) => {
|
|
149
|
+
if (!handlers[event]) handlers[event] = [];
|
|
150
|
+
handlers[event].push(handler);
|
|
151
|
+
}),
|
|
152
|
+
registerTool: vi.fn(),
|
|
153
|
+
registerCommand: vi.fn(),
|
|
154
|
+
callLLM: vi.fn(),
|
|
155
|
+
callLLMStructured: vi.fn(),
|
|
156
|
+
forkAgent: vi.fn(),
|
|
157
|
+
once: vi.fn(),
|
|
158
|
+
emit: vi.fn(),
|
|
159
|
+
off: vi.fn(),
|
|
160
|
+
sendMessage: vi.fn(),
|
|
161
|
+
appendEntry: vi.fn(),
|
|
162
|
+
setStatus: vi.fn(),
|
|
163
|
+
registerProvider: vi.fn(),
|
|
164
|
+
unregisterProvider: vi.fn(),
|
|
165
|
+
events: { on: vi.fn(), off: vi.fn(), emit: vi.fn(), once: vi.fn() },
|
|
166
|
+
registerChannel: vi.fn(),
|
|
167
|
+
} as any,
|
|
168
|
+
handlers,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
it("calls clearPublishedDiagnostics before sending didOpen", async () => {
|
|
173
|
+
const testDir = mkdtempSync(join(tmpdir(), "lsp-test-"));
|
|
174
|
+
const testFile = join(testDir, "test.ts");
|
|
175
|
+
writeFileSync(testFile, "const x: number = 1;\n");
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const runtime = createMockRuntime();
|
|
179
|
+
const mode = createDiagnosticsMode("edit_write");
|
|
180
|
+
const fileTracker = createFileTracker({ maxOpenFiles: 30 });
|
|
181
|
+
const { pi, handlers } = createMockPi();
|
|
182
|
+
|
|
183
|
+
const hooks = createWriteThroughHooks(runtime, {
|
|
184
|
+
cwd: testDir,
|
|
185
|
+
formatOnWrite: false,
|
|
186
|
+
diagnosticsOnWrite: false,
|
|
187
|
+
}, mode, fileTracker);
|
|
188
|
+
hooks.register(pi);
|
|
189
|
+
|
|
190
|
+
// Fire tool_result for an edit
|
|
191
|
+
const toolResultHandlers = handlers.tool_result ?? [];
|
|
192
|
+
expect(toolResultHandlers.length).toBe(1);
|
|
193
|
+
|
|
194
|
+
await toolResultHandlers[0](
|
|
195
|
+
{
|
|
196
|
+
toolName: "edit",
|
|
197
|
+
isError: false,
|
|
198
|
+
input: { path: "test.ts" },
|
|
199
|
+
content: [{ type: "text", text: "done" }],
|
|
200
|
+
},
|
|
201
|
+
{ cwd: testDir },
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// clearPublishedDiagnostics should have been called
|
|
205
|
+
expect(runtime.clearPublishedDiagnostics).toHaveBeenCalledWith("test.ts");
|
|
206
|
+
|
|
207
|
+
// didOpen should have been sent after clearing
|
|
208
|
+
expect(runtime.notify).toHaveBeenCalledWith(
|
|
209
|
+
"textDocument/didOpen",
|
|
210
|
+
expect.objectContaining({
|
|
211
|
+
textDocument: expect.objectContaining({ uri: expect.stringContaining("test.ts") }),
|
|
212
|
+
}),
|
|
213
|
+
expect.any(Object),
|
|
214
|
+
);
|
|
215
|
+
} finally {
|
|
216
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("in agent_end mode, does not run diagnostics but still tracks file", async () => {
|
|
221
|
+
const testDir = mkdtempSync(join(tmpdir(), "lsp-test-"));
|
|
222
|
+
const testFile = join(testDir, "test.ts");
|
|
223
|
+
writeFileSync(testFile, "const x: number = 1;\n");
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const runtime = createMockRuntime();
|
|
227
|
+
const mode = createDiagnosticsMode("agent_end");
|
|
228
|
+
const fileTracker = createFileTracker({ maxOpenFiles: 30 });
|
|
229
|
+
const { pi, handlers } = createMockPi();
|
|
230
|
+
|
|
231
|
+
const hooks = createWriteThroughHooks(runtime, {
|
|
232
|
+
cwd: testDir,
|
|
233
|
+
formatOnWrite: false,
|
|
234
|
+
diagnosticsOnWrite: true,
|
|
235
|
+
}, mode, fileTracker);
|
|
236
|
+
hooks.register(pi);
|
|
237
|
+
|
|
238
|
+
const toolResultHandlers = handlers.tool_result ?? [];
|
|
239
|
+
await toolResultHandlers[0](
|
|
240
|
+
{
|
|
241
|
+
toolName: "edit",
|
|
242
|
+
isError: false,
|
|
243
|
+
input: { path: "test.ts" },
|
|
244
|
+
content: [{ type: "text", text: "done" }],
|
|
245
|
+
},
|
|
246
|
+
{ cwd: testDir },
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// File should be tracked as touched
|
|
250
|
+
expect(mode.getTouchedFiles()).toContain("test.ts");
|
|
251
|
+
|
|
252
|
+
// In agent_end mode, should NOT pull diagnostics
|
|
253
|
+
expect(runtime.requestAll).not.toHaveBeenCalled();
|
|
254
|
+
} finally {
|
|
255
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("in edit_write mode, pulls diagnostics after edit", async () => {
|
|
260
|
+
const testDir = mkdtempSync(join(tmpdir(), "lsp-test-"));
|
|
261
|
+
const testFile = join(testDir, "test.ts");
|
|
262
|
+
writeFileSync(testFile, "const x: number = 1;\n");
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const runtime = createMockRuntime();
|
|
266
|
+
runtime.getPublishedDiagnostics = vi.fn(() => [
|
|
267
|
+
{ message: "type error", severity: 1, range: { start: { line: 5, character: 0 }, end: { line: 5, character: 10 } }, source: "ts", code: 2322 },
|
|
268
|
+
]);
|
|
269
|
+
const mode = createDiagnosticsMode("edit_write");
|
|
270
|
+
const fileTracker = createFileTracker({ maxOpenFiles: 30 });
|
|
271
|
+
const { pi, handlers } = createMockPi();
|
|
272
|
+
|
|
273
|
+
const hooks = createWriteThroughHooks(runtime, {
|
|
274
|
+
cwd: testDir,
|
|
275
|
+
formatOnWrite: false,
|
|
276
|
+
diagnosticsOnWrite: true,
|
|
277
|
+
}, mode, fileTracker);
|
|
278
|
+
hooks.register(pi);
|
|
279
|
+
|
|
280
|
+
const toolResultHandlers = handlers.tool_result ?? [];
|
|
281
|
+
const result = await toolResultHandlers[0](
|
|
282
|
+
{
|
|
283
|
+
toolName: "edit",
|
|
284
|
+
isError: false,
|
|
285
|
+
input: { path: "test.ts" },
|
|
286
|
+
content: [{ type: "text", text: "done" }],
|
|
287
|
+
},
|
|
288
|
+
{ cwd: testDir },
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Should have pulled diagnostics
|
|
292
|
+
expect(runtime.requestAll).toHaveBeenCalledWith(
|
|
293
|
+
"textDocument/diagnostic",
|
|
294
|
+
expect.objectContaining({ textDocument: expect.objectContaining({ uri: expect.stringContaining("test.ts") }) }),
|
|
295
|
+
expect.objectContaining({ path: "test.ts" }),
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// Result should include diagnostics info
|
|
299
|
+
expect(result).toBeDefined();
|
|
300
|
+
expect(result.content).toBeDefined();
|
|
301
|
+
expect(result.details).toBeDefined();
|
|
302
|
+
expect(result.details.files).toHaveLength(1);
|
|
303
|
+
expect(result.details.files[0].issues).toHaveLength(1);
|
|
304
|
+
expect(result.details.files[0].issues[0].message).toBe("type error");
|
|
305
|
+
} finally {
|
|
306
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics-mode.d.ts","sourceRoot":"","sources":["diagnostics-mode.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,YAAY,GAAG,UAAU,CAAC;AAI1E,MAAM,WAAW,eAAe;IAC/B,GAAG,IAAI,mBAAmB,CAAC;IAC3B,GAAG,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,eAAe,IAAI,MAAM,EAAE,CAAC;IAC5B,iBAAiB,IAAI,IAAI,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,eAAe,CA4BpF","sourcesContent":["export type DiagnosticsModeName = \"agent_end\" | \"edit_write\" | \"disabled\";\n\nconst VALID_MODES: DiagnosticsModeName[] = [\"agent_end\", \"edit_write\", \"disabled\"];\n\nexport interface DiagnosticsMode {\n\tget(): DiagnosticsModeName;\n\tset(mode: DiagnosticsModeName): void;\n\taddTouchedFile(filePath: string): void;\n\tgetTouchedFiles(): string[];\n\tclearTouchedFiles(): void;\n}\n\nexport function createDiagnosticsMode(initial?: DiagnosticsModeName): DiagnosticsMode {\n\tlet current: DiagnosticsModeName = VALID_MODES.includes(initial!) ? initial! : \"
|
|
1
|
+
{"version":3,"file":"diagnostics-mode.d.ts","sourceRoot":"","sources":["diagnostics-mode.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,YAAY,GAAG,UAAU,CAAC;AAI1E,MAAM,WAAW,eAAe;IAC/B,GAAG,IAAI,mBAAmB,CAAC;IAC3B,GAAG,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,eAAe,IAAI,MAAM,EAAE,CAAC;IAC5B,iBAAiB,IAAI,IAAI,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,eAAe,CA4BpF","sourcesContent":["export type DiagnosticsModeName = \"agent_end\" | \"edit_write\" | \"disabled\";\n\nconst VALID_MODES: DiagnosticsModeName[] = [\"agent_end\", \"edit_write\", \"disabled\"];\n\nexport interface DiagnosticsMode {\n\tget(): DiagnosticsModeName;\n\tset(mode: DiagnosticsModeName): void;\n\taddTouchedFile(filePath: string): void;\n\tgetTouchedFiles(): string[];\n\tclearTouchedFiles(): void;\n}\n\nexport function createDiagnosticsMode(initial?: DiagnosticsModeName): DiagnosticsMode {\n\tlet current: DiagnosticsModeName = VALID_MODES.includes(initial!) ? initial! : \"edit_write\";\n\tconst touchedFiles: string[] = [];\n\tconst touchedSet = new Set<string>();\n\n\treturn {\n\t\tget(): DiagnosticsModeName {\n\t\t\treturn current;\n\t\t},\n\t\tset(mode: DiagnosticsModeName): void {\n\t\t\tif (VALID_MODES.includes(mode)) {\n\t\t\t\tcurrent = mode;\n\t\t\t}\n\t\t},\n\t\taddTouchedFile(filePath: string): void {\n\t\t\tif (!touchedSet.has(filePath)) {\n\t\t\t\ttouchedSet.add(filePath);\n\t\t\t\ttouchedFiles.push(filePath);\n\t\t\t}\n\t\t},\n\t\tgetTouchedFiles(): string[] {\n\t\t\treturn [...touchedFiles];\n\t\t},\n\t\tclearTouchedFiles(): void {\n\t\t\ttouchedFiles.length = 0;\n\t\t\ttouchedSet.clear();\n\t\t},\n\t};\n}\n"]}
|