@dyyz1993/pi-coding-agent 0.74.39 → 0.74.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
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"]}
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"]}
@@ -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":"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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
+ {"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: vars["agentName"] ?? "unknown",
250
+ name: agentName,
222
251
  description: "",
223
252
  permissionMode: mode as AgentConfig["permissionMode"],
224
- disallowedTools: vars["disallowedTools"]?.split(",").filter(Boolean),
225
- tools: vars["allowedTools"]?.split(",").filter(Boolean),
253
+ disallowedTools,
254
+ tools: allowedTools,
226
255
  } as AgentConfig);
227
256
 
228
257
  if (!handler) return undefined;
@@ -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! : \"agent_end\";\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"]}
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"]}
@@ -1,6 +1,6 @@
1
1
  const VALID_MODES = ["agent_end", "edit_write", "disabled"];
2
2
  export function createDiagnosticsMode(initial) {
3
- let current = VALID_MODES.includes(initial) ? initial : "agent_end";
3
+ let current = VALID_MODES.includes(initial) ? initial : "edit_write";
4
4
  const touchedFiles = [];
5
5
  const touchedSet = new Set();
6
6
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"diagnostics-mode.js","sourceRoot":"","sources":["diagnostics-mode.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAA0B,CAAC,WAAW,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;AAUnF,MAAM,UAAU,qBAAqB,CAAC,OAA6B,EAAmB;IACrF,IAAI,OAAO,GAAwB,WAAW,CAAC,QAAQ,CAAC,OAAQ,CAAC,CAAC,CAAC,CAAC,OAAQ,CAAC,CAAC,CAAC,WAAW,CAAC;IAC3F,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IAErC,OAAO;QACN,GAAG,GAAwB;YAC1B,OAAO,OAAO,CAAC;QAAA,CACf;QACD,GAAG,CAAC,IAAyB,EAAQ;YACpC,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChC,OAAO,GAAG,IAAI,CAAC;YAChB,CAAC;QAAA,CACD;QACD,cAAc,CAAC,QAAgB,EAAQ;YACtC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC/B,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACzB,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7B,CAAC;QAAA,CACD;QACD,eAAe,GAAa;YAC3B,OAAO,CAAC,GAAG,YAAY,CAAC,CAAC;QAAA,CACzB;QACD,iBAAiB,GAAS;YACzB,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;YACxB,UAAU,CAAC,KAAK,EAAE,CAAC;QAAA,CACnB;KACD,CAAC;AAAA,CACF","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! : \"agent_end\";\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"]}
1
+ {"version":3,"file":"diagnostics-mode.js","sourceRoot":"","sources":["diagnostics-mode.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAA0B,CAAC,WAAW,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;AAUnF,MAAM,UAAU,qBAAqB,CAAC,OAA6B,EAAmB;IACrF,IAAI,OAAO,GAAwB,WAAW,CAAC,QAAQ,CAAC,OAAQ,CAAC,CAAC,CAAC,CAAC,OAAQ,CAAC,CAAC,CAAC,YAAY,CAAC;IAC5F,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IAErC,OAAO;QACN,GAAG,GAAwB;YAC1B,OAAO,OAAO,CAAC;QAAA,CACf;QACD,GAAG,CAAC,IAAyB,EAAQ;YACpC,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChC,OAAO,GAAG,IAAI,CAAC;YAChB,CAAC;QAAA,CACD;QACD,cAAc,CAAC,QAAgB,EAAQ;YACtC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC/B,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACzB,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7B,CAAC;QAAA,CACD;QACD,eAAe,GAAa;YAC3B,OAAO,CAAC,GAAG,YAAY,CAAC,CAAC;QAAA,CACzB;QACD,iBAAiB,GAAS;YACzB,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;YACxB,UAAU,CAAC,KAAK,EAAE,CAAC;QAAA,CACnB;KACD,CAAC;AAAA,CACF","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"]}
@@ -11,7 +11,7 @@ export interface DiagnosticsMode {
11
11
  }
12
12
 
13
13
  export function createDiagnosticsMode(initial?: DiagnosticsModeName): DiagnosticsMode {
14
- let current: DiagnosticsModeName = VALID_MODES.includes(initial!) ? initial! : "agent_end";
14
+ let current: DiagnosticsModeName = VALID_MODES.includes(initial!) ? initial! : "edit_write";
15
15
  const touchedFiles: string[] = [];
16
16
  const touchedSet = new Set<string>();
17
17
 
@@ -91,6 +91,7 @@ export function createWriteThroughHooks(
91
91
  }
92
92
 
93
93
  try {
94
+ runtime.clearPublishedDiagnostics(filePath);
94
95
  const fileContent = await fsReadFile(resolve(cwd, filePath), "utf8");
95
96
  runtime.notify(
96
97
  "textDocument/didOpen",