{"version":3,"file":"prompt-workflows.d.ts","sourceRoot":"","sources":["../../../src/slash/prompt-workflows.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEhF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAGlF,UAAU,cAAc;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,KAAK,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,KAAK,oBAAoB,GAAG,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAiIjG,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,EAAE,CAOrE;AAuCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAsBvE;AAqID,wBAAgB,8BAA8B,CAAC,KAAK,EAAE;IACrD,EAAE,EAAE,YAAY,CAAC;IACjB,GAAG,EAAE,oBAAoB,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB,GAAG,IAAI,CAkDP","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { ExtensionAPI, ExtensionContext } from \"@lpb-work/pi-coding-agent\";\nimport { parseFrontmatter } from \"../agents/frontmatter.ts\";\nimport type { SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { getAgentDir, getProjectConfigDir } from \"../shared/utils.ts\";\n\ninterface PromptWorkflow {\n\tname: string;\n\tdescription: string;\n\targumentHint?: string;\n\tbody: string;\n\tfilePath: string;\n\tagent: string;\n\tcontext?: \"fresh\" | \"fork\";\n\tmodel?: string;\n\tskill?: string | string[] | false;\n\tcwd?: string;\n\tchain?: string;\n}\n\ntype PromptWorkflowRunner = (params: SubagentParamsLike, ctx: ExtensionContext) => Promise<void>;\n\nconst RESERVED_COMMAND_NAMES = new Set([\n\t\"chain-prompts\",\n\t\"prompt-workflow\",\n\t\"run\",\n\t\"chain\",\n\t\"parallel\",\n\t\"run-chain\",\n\t\"subagents-doctor\",\n\t\"subagents-models\",\n]);\n\nfunction packagePromptsDir(): string {\n\treturn path.resolve(path.dirname(fileURLToPath(import.meta.url)), \"..\", \"..\", \"prompts\");\n}\n\nfunction isPackagedPromptWorkflow(workflow: PromptWorkflow): boolean {\n\tconst relative = path.relative(packagePromptsDir(), workflow.filePath);\n\treturn relative !== \"\" && relative !== \"..\" && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);\n}\n\nfunction promptDirs(cwd: string): string[] {\n\treturn [packagePromptsDir(), path.join(getAgentDir(), \"prompts\"), path.join(getProjectConfigDir(cwd), \"prompts\")];\n}\n\nfunction readPromptFiles(cwd: string): string[] {\n\tconst files: string[] = [];\n\tfor (const dir of promptDirs(cwd)) {\n\t\tlet entries: fs.Dirent[];\n\t\ttry {\n\t\t\tentries = fs.readdirSync(dir, { withFileTypes: true });\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && \"code\" in error && (error as NodeJS.ErrnoException).code === \"ENOENT\") continue;\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to read prompt workflow directory '${dir}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.isFile() && entry.name.endsWith(\".md\")) files.push(path.join(dir, entry.name));\n\t\t}\n\t}\n\treturn files;\n}\n\nfunction firstNonEmptyLine(value: string): string {\n\treturn (\n\t\tvalue\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((line) => line.trim())\n\t\t\t.find(Boolean) ?? \"Prompt workflow\"\n\t);\n}\n\nfunction stringField(frontmatter: Record<string, string>, key: string): string | undefined {\n\tconst value = frontmatter[key]?.trim();\n\treturn value ? value : undefined;\n}\n\nfunction booleanField(frontmatter: Record<string, string>, key: string): boolean | undefined {\n\tconst value = frontmatter[key]?.trim().toLowerCase();\n\tif (value === \"true\" || value === \"yes\" || value === \"1\") return true;\n\tif (value === \"false\" || value === \"no\" || value === \"0\") return false;\n\treturn undefined;\n}\n\nfunction parseSkill(value: string | undefined): string | string[] | false | undefined {\n\tif (!value) return undefined;\n\tif (value === \"false\") return false;\n\tconst parts = value\n\t\t.split(\",\")\n\t\t.map((part) => part.trim())\n\t\t.filter(Boolean);\n\treturn parts.length > 1 ? parts : parts[0];\n}\n\nfunction parseAgent(frontmatter: Record<string, string>): string {\n\tconst subagent = stringField(frontmatter, \"subagent\");\n\tif (!subagent || subagent === \"true\") return \"delegate\";\n\treturn subagent;\n}\n\nfunction loadPromptWorkflow(filePath: string): PromptWorkflow | undefined {\n\tlet content: string;\n\ttry {\n\t\tcontent = fs.readFileSync(filePath, \"utf-8\");\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to read prompt workflow '${filePath}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\tconst normalized = content.replace(/\\r\\n/g, \"\\n\");\n\tif (normalized.startsWith(\"---\\n\") && !/\\n---(?:\\n|$)/.test(normalized.slice(4))) {\n\t\tthrow new Error(`Invalid frontmatter in prompt workflow '${filePath}': missing closing '---'.`);\n\t}\n\tlet frontmatter: Record<string, string>;\n\tlet body: string;\n\ttry {\n\t\t({ frontmatter, body } = parseFrontmatter(content));\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Invalid frontmatter in prompt workflow '${filePath}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\tconst name = path.basename(filePath, \".md\");\n\tif (!name || RESERVED_COMMAND_NAMES.has(name)) return undefined;\n\tconst model = stringField(frontmatter, \"model\");\n\tconst skill = parseSkill(stringField(frontmatter, \"skill\"));\n\tconst cwd = stringField(frontmatter, \"cwd\");\n\tconst chain = stringField(frontmatter, \"chain\");\n\tconst argumentHint = stringField(frontmatter, \"argument-hint\");\n\treturn {\n\t\tname,\n\t\tdescription: stringField(frontmatter, \"description\") ?? firstNonEmptyLine(body),\n\t\t...(argumentHint ? { argumentHint } : {}),\n\t\tbody,\n\t\tfilePath,\n\t\tagent: parseAgent(frontmatter),\n\t\t...(booleanField(frontmatter, \"inheritContext\") === true || booleanField(frontmatter, \"fork\") === true\n\t\t\t? { context: \"fork\" as const }\n\t\t\t: {}),\n\t\t...(booleanField(frontmatter, \"fresh\") === true ? { context: \"fresh\" as const } : {}),\n\t\t...(model ? { model } : {}),\n\t\t...(skill !== undefined ? { skill } : {}),\n\t\t...(cwd ? { cwd } : {}),\n\t\t...(chain ? { chain } : {}),\n\t};\n}\n\nexport function discoverPromptWorkflows(cwd: string): PromptWorkflow[] {\n\tconst workflows = new Map<string, PromptWorkflow>();\n\tfor (const file of readPromptFiles(cwd)) {\n\t\tconst workflow = loadPromptWorkflow(file);\n\t\tif (workflow) workflows.set(workflow.name, workflow);\n\t}\n\treturn [...workflows.values()].sort((a, b) => a.name.localeCompare(b.name));\n}\n\nfunction shellWords(input: string): string[] {\n\tconst words: string[] = [];\n\tlet current = \"\";\n\tlet quote: \"'\" | '\"' | undefined;\n\tlet escaped = false;\n\tfor (const ch of input) {\n\t\tif (escaped) {\n\t\t\tcurrent += ch;\n\t\t\tescaped = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\\\") {\n\t\t\tescaped = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote) {\n\t\t\tif (ch === quote) quote = undefined;\n\t\t\telse current += ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"'\" || ch === '\"') {\n\t\t\tquote = ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (/\\s/.test(ch)) {\n\t\t\tif (current) {\n\t\t\t\twords.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tcurrent += ch;\n\t}\n\tif (current) words.push(current);\n\treturn words;\n}\n\n/**\n * Substitute prompt arguments using the same one-pass syntax as coding-agent.\n * Defaults and argument values are emitted literally; they are never recursively expanded.\n */\nexport function substituteArgs(template: string, args: string[]): string {\n\tconst allArgs = args.join(\" \");\n\treturn template.replace(\n\t\t/\\$\\{(\\d+|ARGUMENTS|@):-([^}]*)\\}|\\$\\{@:(\\d+)(?::(\\d+))?\\}|\\$(ARGUMENTS|@|\\d+)/g,\n\t\t(_match, defaultTarget, defaultValue, sliceStart, sliceLength, simple) => {\n\t\t\tif (defaultTarget) {\n\t\t\t\tconst value =\n\t\t\t\t\tdefaultTarget === \"@\" || defaultTarget === \"ARGUMENTS\"\n\t\t\t\t\t\t? allArgs\n\t\t\t\t\t\t: args[Number(defaultTarget) - 1];\n\t\t\t\treturn value ? value : defaultValue;\n\t\t\t}\n\t\t\tif (sliceStart) {\n\t\t\t\tlet start = Number(sliceStart) - 1;\n\t\t\t\tif (start < 0) start = 0;\n\t\t\t\tif (sliceLength) return args.slice(start, start + Number(sliceLength)).join(\" \");\n\t\t\t\treturn args.slice(start).join(\" \");\n\t\t\t}\n\t\t\tif (simple === \"ARGUMENTS\" || simple === \"@\") return allArgs;\n\t\t\treturn args[Number(simple) - 1] ?? \"\";\n\t\t},\n\t);\n}\n\nfunction parseRuntimeOptions(words: string[]): {\n\targs: string[];\n\tagentOverride?: string;\n\tfork?: boolean;\n\tfresh?: boolean;\n\tbg?: boolean;\n\tautofix?: boolean;\n} {\n\tconst args: string[] = [];\n\tlet agentOverride: string | undefined;\n\tlet fork = false;\n\tlet fresh = false;\n\tlet bg = false;\n\tlet autofix = false;\n\tfor (let i = 0; i < words.length; i++) {\n\t\tconst word = words[i]!;\n\t\tif (word === \"autofix\") {\n\t\t\tautofix = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (word === \"--fork\") {\n\t\t\tfork = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (word === \"--fresh\") {\n\t\t\tfresh = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (word === \"--bg\" || word === \"--async\") {\n\t\t\tbg = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (word === \"--subagent\") {\n\t\t\tagentOverride = words[++i];\n\t\t\tcontinue;\n\t\t}\n\t\tconst eq = word.match(/^--subagent(?:=|:)(.+)$/);\n\t\tif (eq) {\n\t\t\tagentOverride = eq[1];\n\t\t\tcontinue;\n\t\t}\n\t\targs.push(word);\n\t}\n\treturn { args, agentOverride, fork, fresh, bg, autofix };\n}\n\nfunction splitPromptChain(input: string): string[] {\n\treturn input\n\t\t.split(\" -> \")\n\t\t.map((part) => part.trim())\n\t\t.filter(Boolean);\n}\n\nfunction workflowParams(\n\tworkflow: PromptWorkflow,\n\targs: string[],\n\truntime: ReturnType<typeof parseRuntimeOptions>,\n): SubagentParamsLike {\n\tconst task = substituteArgs(workflow.body, args).trim();\n\tconst context = runtime.fork ? \"fork\" : runtime.fresh ? \"fresh\" : workflow.context;\n\tconst autofixAgent =\n\t\truntime.autofix &&\n\t\tisPackagedPromptWorkflow(workflow) &&\n\t\t(workflow.name === \"parallel-review\" || workflow.name === \"parallel-cleanup\")\n\t\t\t? \"workflow-worker\"\n\t\t\t: workflow.agent;\n\treturn {\n\t\tagent: runtime.agentOverride ?? autofixAgent,\n\t\ttask,\n\t\tagentScope: \"both\",\n\t\t...(context ? { context } : {}),\n\t\t...(workflow.model ? { model: workflow.model } : {}),\n\t\t...(workflow.skill !== undefined ? { skill: workflow.skill } : {}),\n\t\t...(workflow.cwd ? { cwd: workflow.cwd } : {}),\n\t};\n}\n\nfunction promptWorkflowExecutionParams(\n\tworkflows: PromptWorkflow[],\n\targs: string[],\n\truntime: ReturnType<typeof parseRuntimeOptions>,\n): SubagentParamsLike {\n\treturn {\n\t\tworkflowScript: promptWorkflowScript(workflows, args, runtime),\n\t\tagentScope: \"both\",\n\t\tasync: !!runtime.bg,\n\t};\n}\n\nfunction promptWorkflowScript(\n\tworkflows: PromptWorkflow[],\n\targs: string[],\n\truntime: ReturnType<typeof parseRuntimeOptions>,\n): string {\n\tconst launches = workflows.map((workflow, index) => {\n\t\tconst params = workflowParams(workflow, args, runtime);\n\t\tconst task = params.task ?? \"\";\n\t\tconst child = {\n\t\t\tagent: params.agent ?? \"delegate\",\n\t\t\ttask,\n\t\t\t...(params.model ? { model: params.model } : {}),\n\t\t\t...(params.skill !== undefined ? { skill: params.skill } : {}),\n\t\t\t...(params.cwd ? { cwd: params.cwd } : {}),\n\t\t\t...(params.context ? { context: params.context } : {}),\n\t\t};\n\t\treturn `const step${index} = await runs.run(${JSON.stringify(`prompt-${index + 1}-${workflow.name}`)}, { ...${JSON.stringify(child)}, task: ${JSON.stringify(task)}.replaceAll(\"{previous}\", previous) });\\nprevious = step${index}.output;`;\n\t});\n\treturn `let previous = \"\";\\n${launches.join(\"\\n\")}\\nreturn previous;`;\n}\n\nfunction findWorkflow(workflows: PromptWorkflow[], name: string): PromptWorkflow | undefined {\n\treturn workflows.find((workflow) => workflow.name === name);\n}\n\nfunction formatWorkflowList(workflows: PromptWorkflow[]): string {\n\tconst usage = [\n\t\t\"Usage: /prompt-workflow <name> [args...] [autofix] [--fork|--fresh] [--bg] [--subagent <agent>]\",\n\t\t\"Template arguments: $1/$2, $@/$ARGUMENTS, ${N:-default}, ${@:-default}/${ARGUMENTS:-default}, ${@:N}, ${@:N:L}.\",\n\t];\n\tif (workflows.length === 0) return [\"No prompt workflows found in package, user, or project prompts.\", ...usage].join(\"\\n\");\n\treturn [\n\t\t\"Prompt workflows:\",\n\t\t...workflows.map(\n\t\t\t(workflow) =>\n\t\t\t\t`- ${workflow.name}${workflow.argumentHint ? ` ${workflow.argumentHint}` : \"\"}: ${workflow.description} (${workflow.filePath})`,\n\t\t),\n\t\t\"\",\n\t\t...usage,\n\t].join(\"\\n\");\n}\n\nexport function registerPromptWorkflowCommands(input: {\n\tpi: ExtensionAPI;\n\trun: PromptWorkflowRunner;\n\tgetCwd?: () => string;\n}): void {\n\tconst { pi, run, getCwd } = input;\n\tlet lastCwd = getCwd?.() || process.cwd();\n\tpi.registerCommand(\"prompt-workflow\", {\n\t\tdescription: \"Run a prompt template through native pi-subagents: /prompt-workflow <name> [args]\",\n\t\tgetArgumentCompletions: (prefix) => {\n\t\t\tconst namePrefix = prefix.trimStart().split(/\\s/u)[0] ?? \"\";\n\t\t\treturn discoverPromptWorkflows(getCwd?.() || lastCwd)\n\t\t\t\t.filter((workflow) => workflow.name.startsWith(namePrefix))\n\t\t\t\t.map((workflow) => ({\n\t\t\t\t\tvalue: workflow.name,\n\t\t\t\t\tlabel: workflow.name,\n\t\t\t\t\tdescription: workflow.argumentHint\n\t\t\t\t\t\t? `${workflow.argumentHint} — ${workflow.description}`\n\t\t\t\t\t\t: workflow.description,\n\t\t\t\t}));\n\t\t},\n\t\thandler: async (rawArgs, ctx) => {\n\t\t\tlastCwd = ctx.cwd;\n\t\t\ttry {\n\t\t\t\tconst words = shellWords(rawArgs);\n\t\t\t\tconst name = words.shift();\n\t\t\t\tconst workflows = discoverPromptWorkflows(ctx.cwd);\n\t\t\t\tif (!name || name === \"list\") {\n\t\t\t\t\tpi.sendMessage({ content: formatWorkflowList(workflows), display: true } as Parameters<\n\t\t\t\t\t\ttypeof pi.sendMessage\n\t\t\t\t\t>[0]);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst workflow = findWorkflow(workflows, name);\n\t\t\t\tif (!workflow) {\n\t\t\t\t\tctx.ui.notify(`Unknown prompt workflow: ${name}. Use /prompt-workflow list for available templates.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst runtime = parseRuntimeOptions(words);\n\t\t\t\tif (workflow.chain) {\n\t\t\t\t\tconst chain = splitPromptChain(workflow.chain).map((stepName) => {\n\t\t\t\t\t\tconst step = findWorkflow(workflows, stepName);\n\t\t\t\t\t\tif (!step) throw new Error(`Unknown prompt workflow in chain '${workflow.name}': ${stepName}`);\n\t\t\t\t\t\treturn step;\n\t\t\t\t\t});\n\t\t\t\t\tawait run(promptWorkflowExecutionParams(chain, runtime.args, runtime), ctx);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tawait run(promptWorkflowExecutionParams([workflow], runtime.args, runtime), ctx);\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t}\n\t\t},\n\t});\n}\n"]}