@oisincoveney/pipeline 1.5.1 → 1.5.3
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/README.md +35 -14
- package/dist/config.d.ts +2 -0
- package/dist/config.js +2 -0
- package/dist/index.js +411 -177
- package/dist/install-commands.d.ts +1 -1
- package/dist/pipeline-runtime.js +2 -2
- package/dist/runner.js +0 -4
- package/docs/config-architecture.md +20 -10
- package/docs/slash-command-adapter-contract.md +19 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,22 +152,36 @@ Generate native host files from the YAML config:
|
|
|
152
152
|
pipe install-commands --host all
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
Generated resources are
|
|
156
|
-
separate sources of truth.
|
|
157
|
-
host
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
155
|
+
Generated resources are derived from the three config files; they are not
|
|
156
|
+
separate sources of truth. Host resources use exact native agents when the node
|
|
157
|
+
runner matches the host. Cross-runner nodes use host-native execution only when
|
|
158
|
+
the host can run the requested model explicitly, such as OpenCode subagents with
|
|
159
|
+
a resolved `model:`; otherwise generated instructions dispatch to that runner's
|
|
160
|
+
CLI instead of doing instruction-only translation.
|
|
161
|
+
|
|
162
|
+
| Host | Generated files | Invocation |
|
|
163
|
+
| ----------- | ------------------------------------------------------ | -------------------- |
|
|
164
|
+
| Claude Code | `.claude/commands/pipe.md`, `.claude/agents/*.md` | `/pipe <task>` |
|
|
165
|
+
| Codex | `.agents/skills/pipe/SKILL.md`, `.codex/agents/*.toml` | `$pipe <task>` |
|
|
166
|
+
| OpenCode | `.opencode/commands/pipe.md`, `.opencode/agents/*.md` | `/pipe <task>` |
|
|
167
|
+
| Kimi | `.kimi/skills/pipe/SKILL.md`, `.kimi/agents/*.yaml` | `/skill:pipe <task>` |
|
|
168
|
+
| Pi | `.pi/prompts/pipe.md`, `.pi/extensions/pipe.ts` | `/pipe <task>` |
|
|
167
169
|
|
|
168
170
|
The installer is idempotent, supports `--check` and `--dry-run`, and refuses to
|
|
169
171
|
overwrite manually edited files unless `--force` is supplied.
|
|
170
172
|
|
|
173
|
+
Runner `model` is the canonical model id. Optional
|
|
174
|
+
`host_models.<host>` entries are only needed when a host uses a different model
|
|
175
|
+
identifier:
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
runners:
|
|
179
|
+
kimi:
|
|
180
|
+
type: kimi
|
|
181
|
+
command: kimi
|
|
182
|
+
model: moonshot/kimi-k2.6
|
|
183
|
+
```
|
|
184
|
+
|
|
171
185
|
## Runtime Guarantees
|
|
172
186
|
|
|
173
187
|
- `pipe run` fails without `.pipeline/pipeline.yaml`,
|
|
@@ -189,9 +203,16 @@ External apps can import the stable config, planner, and runtime surfaces
|
|
|
189
203
|
without deep-importing private source paths:
|
|
190
204
|
|
|
191
205
|
```ts
|
|
192
|
-
import {
|
|
206
|
+
import {
|
|
207
|
+
loadPipelineConfig,
|
|
208
|
+
parsePipelineConfigParts,
|
|
209
|
+
} from "@oisincoveney/pipeline/config";
|
|
193
210
|
import { compileWorkflowPlan } from "@oisincoveney/pipeline/planner";
|
|
194
|
-
import {
|
|
211
|
+
import {
|
|
212
|
+
runPipelineFromConfig,
|
|
213
|
+
type PipelineRuntimeResult,
|
|
214
|
+
type PipelineTaskContext,
|
|
215
|
+
} from "@oisincoveney/pipeline/runtime";
|
|
195
216
|
```
|
|
196
217
|
|
|
197
218
|
## Verification
|
package/dist/config.d.ts
CHANGED
|
@@ -74,6 +74,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
74
74
|
"workspace-write": "workspace-write";
|
|
75
75
|
}>;
|
|
76
76
|
}, z.core.$strict>>;
|
|
77
|
+
host_models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
77
78
|
instructions: z.ZodObject<{
|
|
78
79
|
inline: z.ZodOptional<z.ZodString>;
|
|
79
80
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -150,6 +151,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
150
151
|
}>>>;
|
|
151
152
|
}, z.core.$strict>;
|
|
152
153
|
command: z.ZodOptional<z.ZodString>;
|
|
154
|
+
host_models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
153
155
|
model: z.ZodOptional<z.ZodString>;
|
|
154
156
|
type: z.ZodEnum<{
|
|
155
157
|
claude: "claude";
|
package/dist/config.js
CHANGED
|
@@ -21357,6 +21357,7 @@ var runnerSchema = exports_external.object({
|
|
|
21357
21357
|
args: exports_external.array(exports_external.string()).optional(),
|
|
21358
21358
|
capabilities: runnerCapabilitiesSchema,
|
|
21359
21359
|
command: exports_external.string().optional(),
|
|
21360
|
+
host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
|
|
21360
21361
|
model: exports_external.string().optional(),
|
|
21361
21362
|
type: exports_external.enum(RUNNER_TYPES)
|
|
21362
21363
|
}).strict();
|
|
@@ -21422,6 +21423,7 @@ var retriesSchema = exports_external.object({
|
|
|
21422
21423
|
var profileSchema = exports_external.object({
|
|
21423
21424
|
description: exports_external.string().optional(),
|
|
21424
21425
|
filesystem: filesystemSchema.optional(),
|
|
21426
|
+
host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
|
|
21425
21427
|
instructions: instructionsSchema,
|
|
21426
21428
|
mcp_servers: exports_external.array(exports_external.string()).optional(),
|
|
21427
21429
|
model: exports_external.string().optional(),
|
package/dist/index.js
CHANGED
|
@@ -27430,6 +27430,7 @@ var runnerSchema = exports_external.object({
|
|
|
27430
27430
|
args: exports_external.array(exports_external.string()).optional(),
|
|
27431
27431
|
capabilities: runnerCapabilitiesSchema,
|
|
27432
27432
|
command: exports_external.string().optional(),
|
|
27433
|
+
host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
|
|
27433
27434
|
model: exports_external.string().optional(),
|
|
27434
27435
|
type: exports_external.enum(RUNNER_TYPES)
|
|
27435
27436
|
}).strict();
|
|
@@ -27495,6 +27496,7 @@ var retriesSchema = exports_external.object({
|
|
|
27495
27496
|
var profileSchema = exports_external.object({
|
|
27496
27497
|
description: exports_external.string().optional(),
|
|
27497
27498
|
filesystem: filesystemSchema.optional(),
|
|
27499
|
+
host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
|
|
27498
27500
|
instructions: instructionsSchema,
|
|
27499
27501
|
mcp_servers: exports_external.array(exports_external.string()).optional(),
|
|
27500
27502
|
model: exports_external.string().optional(),
|
|
@@ -27938,8 +27940,8 @@ function validationError(issues) {
|
|
|
27938
27940
|
// src/install-commands.ts
|
|
27939
27941
|
var import_gray_matter = __toESM(require_gray_matter(), 1);
|
|
27940
27942
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
27941
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
27942
|
-
import { dirname, join as join2 } from "node:path";
|
|
27943
|
+
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
27944
|
+
import { dirname, join as join2, relative } from "node:path";
|
|
27943
27945
|
|
|
27944
27946
|
// node_modules/smol-toml/dist/error.js
|
|
27945
27947
|
/*!
|
|
@@ -28663,8 +28665,10 @@ function codeForIssue(message) {
|
|
|
28663
28665
|
// src/install-commands.ts
|
|
28664
28666
|
var GENERATED_MARKER = "<!-- Generated by @oisincoveney/pipeline. -->";
|
|
28665
28667
|
var GENERATED_TS_MARKER = "// Generated by @oisincoveney/pipeline.";
|
|
28668
|
+
var GENERATED_YAML_MARKER = "# Generated by @oisincoveney/pipeline.";
|
|
28666
28669
|
var OWNER_MARKER_PREFIX = "<!-- @oisincoveney/pipeline:";
|
|
28667
28670
|
var OWNER_TS_MARKER_PREFIX = "// @oisincoveney/pipeline:";
|
|
28671
|
+
var OWNER_YAML_MARKER_PREFIX = "# @oisincoveney/pipeline:";
|
|
28668
28672
|
var COMMAND_HOSTS = [
|
|
28669
28673
|
"claude",
|
|
28670
28674
|
"opencode",
|
|
@@ -28676,14 +28680,17 @@ function header(host) {
|
|
|
28676
28680
|
return [GENERATED_MARKER, `${OWNER_MARKER_PREFIX}host=${host} -->`, ""].join(`
|
|
28677
28681
|
`);
|
|
28678
28682
|
}
|
|
28679
|
-
function
|
|
28683
|
+
function yamlHeader(host) {
|
|
28680
28684
|
return [
|
|
28681
|
-
|
|
28682
|
-
`${
|
|
28685
|
+
GENERATED_YAML_MARKER,
|
|
28686
|
+
`${OWNER_YAML_MARKER_PREFIX}host=${host}`,
|
|
28683
28687
|
""
|
|
28684
28688
|
].join(`
|
|
28685
28689
|
`);
|
|
28686
28690
|
}
|
|
28691
|
+
function hashHeader(host) {
|
|
28692
|
+
return yamlHeader(host);
|
|
28693
|
+
}
|
|
28687
28694
|
function markdown(data, body) {
|
|
28688
28695
|
return `${import_gray_matter.default.stringify(body.trimEnd(), data).trimEnd()}
|
|
28689
28696
|
`;
|
|
@@ -28699,11 +28706,8 @@ function profileEntries(config2) {
|
|
|
28699
28706
|
}
|
|
28700
28707
|
return [...profileIds].sort((a, b) => a.localeCompare(b)).map((id) => [id, config2.profiles[id]]);
|
|
28701
28708
|
}
|
|
28702
|
-
function
|
|
28703
|
-
return
|
|
28704
|
-
}
|
|
28705
|
-
function profileNames(config2) {
|
|
28706
|
-
return profileEntries(config2).map(([name]) => `\`${name}\``).join(", ");
|
|
28709
|
+
function nativeProfileEntries(host, config2) {
|
|
28710
|
+
return profileEntries(config2).filter(([_, profile]) => canRunNatively(host, config2, profile));
|
|
28707
28711
|
}
|
|
28708
28712
|
function orchestratorProfile(config2) {
|
|
28709
28713
|
const profile = config2.profiles[config2.orchestrator.profile];
|
|
@@ -28715,34 +28719,69 @@ function orchestratorProfile(config2) {
|
|
|
28715
28719
|
hooks: config2.orchestrator.hooks
|
|
28716
28720
|
};
|
|
28717
28721
|
}
|
|
28718
|
-
function
|
|
28722
|
+
function resolvedHostModel(config2, host, profile) {
|
|
28723
|
+
const runner = config2.runners[profile.runner];
|
|
28724
|
+
return profile.host_models?.[host] ?? runner?.host_models?.[host] ?? profile.model ?? runner?.model;
|
|
28725
|
+
}
|
|
28726
|
+
function canRunNatively(host, config2, profile) {
|
|
28727
|
+
if (host === "pi") {
|
|
28728
|
+
return false;
|
|
28729
|
+
}
|
|
28730
|
+
if (profile.runner === host) {
|
|
28731
|
+
return true;
|
|
28732
|
+
}
|
|
28733
|
+
return host === "opencode" && isModelRunner(profile.runner) && resolvedHostModel(config2, host, profile) !== undefined;
|
|
28734
|
+
}
|
|
28735
|
+
function isModelRunner(runnerId) {
|
|
28736
|
+
return COMMAND_HOSTS.some((host) => host === runnerId);
|
|
28737
|
+
}
|
|
28738
|
+
function agentDispatchRoutes(host, config2) {
|
|
28719
28739
|
const plan = compileWorkflowPlan(config2);
|
|
28720
|
-
return
|
|
28721
|
-
|
|
28722
|
-
|
|
28723
|
-
|
|
28724
|
-
|
|
28725
|
-
|
|
28726
|
-
|
|
28727
|
-
|
|
28728
|
-
|
|
28729
|
-
|
|
28730
|
-
|
|
28731
|
-
|
|
28732
|
-
|
|
28733
|
-
|
|
28734
|
-
|
|
28740
|
+
return plan.topologicalOrder.flatMap((node) => {
|
|
28741
|
+
if (!(node.kind === "agent" && node.profile)) {
|
|
28742
|
+
return [];
|
|
28743
|
+
}
|
|
28744
|
+
const profile = config2.profiles[node.profile];
|
|
28745
|
+
if (!profile) {
|
|
28746
|
+
return [];
|
|
28747
|
+
}
|
|
28748
|
+
return [
|
|
28749
|
+
dispatchRouteForAgent(host, config2, {
|
|
28750
|
+
needs: node.needs,
|
|
28751
|
+
nodeId: node.id,
|
|
28752
|
+
profile,
|
|
28753
|
+
profileId: node.profile
|
|
28754
|
+
})
|
|
28755
|
+
];
|
|
28756
|
+
});
|
|
28735
28757
|
}
|
|
28736
|
-
function
|
|
28737
|
-
const
|
|
28738
|
-
if (
|
|
28739
|
-
return
|
|
28758
|
+
function dispatchRouteForAgent(host, config2, route) {
|
|
28759
|
+
const runnerId = route.profile.runner;
|
|
28760
|
+
if (host !== "pi" && runnerId === host) {
|
|
28761
|
+
return {
|
|
28762
|
+
...route,
|
|
28763
|
+
kind: "native-named-agent",
|
|
28764
|
+
nativeAgentId: route.profileId,
|
|
28765
|
+
runnerId
|
|
28766
|
+
};
|
|
28740
28767
|
}
|
|
28741
|
-
|
|
28742
|
-
|
|
28743
|
-
|
|
28744
|
-
|
|
28745
|
-
|
|
28768
|
+
if (host === "opencode" && isModelRunner(runnerId)) {
|
|
28769
|
+
const model = resolvedHostModel(config2, host, route.profile);
|
|
28770
|
+
if (model) {
|
|
28771
|
+
return {
|
|
28772
|
+
...route,
|
|
28773
|
+
kind: "native-model-agent",
|
|
28774
|
+
model,
|
|
28775
|
+
nativeAgentId: route.profileId,
|
|
28776
|
+
runnerId
|
|
28777
|
+
};
|
|
28778
|
+
}
|
|
28779
|
+
}
|
|
28780
|
+
return {
|
|
28781
|
+
...route,
|
|
28782
|
+
kind: "cli",
|
|
28783
|
+
runnerId
|
|
28784
|
+
};
|
|
28746
28785
|
}
|
|
28747
28786
|
function grants(actor) {
|
|
28748
28787
|
return [
|
|
@@ -28767,14 +28806,126 @@ function orchestratorBlock(config2) {
|
|
|
28767
28806
|
].join(`
|
|
28768
28807
|
`);
|
|
28769
28808
|
}
|
|
28770
|
-
function
|
|
28771
|
-
|
|
28809
|
+
function dispatchBlock(host, config2) {
|
|
28810
|
+
const routes = agentDispatchRoutes(host, config2);
|
|
28811
|
+
if (routes.length === 0) {
|
|
28772
28812
|
return;
|
|
28773
28813
|
}
|
|
28814
|
+
const plan = compileWorkflowPlan(config2);
|
|
28815
|
+
const nativeRoutes = routes.filter((route) => route.kind !== "cli");
|
|
28816
|
+
const cliRoutes = routes.filter((route) => route.kind === "cli");
|
|
28817
|
+
return [
|
|
28818
|
+
`Run workflow \`${plan.workflowId}\` for the user task.`,
|
|
28819
|
+
"",
|
|
28820
|
+
nativeDispatchBlock(host, nativeRoutes),
|
|
28821
|
+
cliDispatchBlock(host, cliRoutes),
|
|
28822
|
+
nodePromptContract(plan.workflowId, routes),
|
|
28823
|
+
"Do not use `pipe`, `oisin-pipeline`, or package scripts to execute workflow nodes.",
|
|
28824
|
+
hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes)
|
|
28825
|
+
].filter((line) => Boolean(line)).join(`
|
|
28826
|
+
`);
|
|
28827
|
+
}
|
|
28828
|
+
function nativeDispatchBlock(host, routes) {
|
|
28829
|
+
if (routes.length === 0) {
|
|
28830
|
+
return;
|
|
28831
|
+
}
|
|
28832
|
+
return [
|
|
28833
|
+
`${hostDisplayName(host)} native routes:`,
|
|
28834
|
+
...routes.map((route) => nativeDispatchLine(host, route)),
|
|
28835
|
+
""
|
|
28836
|
+
].join(`
|
|
28837
|
+
`);
|
|
28838
|
+
}
|
|
28839
|
+
function nativeDispatchLine(host, route) {
|
|
28840
|
+
const needs = needsSummary(route.needs);
|
|
28841
|
+
if (host === "codex") {
|
|
28842
|
+
return `- ${route.nodeId}: spawn_agent agent_type=${route.nativeAgentId} runner=${route.runnerId} needs=${needs}`;
|
|
28843
|
+
}
|
|
28844
|
+
if (host === "claude") {
|
|
28845
|
+
return `- ${route.nodeId}: Agent tool subagent_type=${route.nativeAgentId} runner=${route.runnerId} needs=${needs}`;
|
|
28846
|
+
}
|
|
28847
|
+
if (host === "kimi") {
|
|
28848
|
+
return `- ${route.nodeId}: Agent tool subagent_type=${route.nativeAgentId} runner=${route.runnerId} needs=${needs}`;
|
|
28849
|
+
}
|
|
28774
28850
|
if (host === "opencode") {
|
|
28775
|
-
|
|
28851
|
+
const model = route.model ? ` model=${route.model}` : "";
|
|
28852
|
+
return `- ${route.nodeId}: Task tool subagent_type=${route.nativeAgentId}${model} runner=${route.runnerId} needs=${needs}`;
|
|
28853
|
+
}
|
|
28854
|
+
return `- ${route.nodeId}: native agent ${route.nativeAgentId} runner=${route.runnerId} needs=${needs}`;
|
|
28855
|
+
}
|
|
28856
|
+
function cliDispatchBlock(host, routes) {
|
|
28857
|
+
if (routes.length === 0) {
|
|
28858
|
+
return;
|
|
28859
|
+
}
|
|
28860
|
+
const nativeNotice = host === "pi" ? "Pi native dispatch is not enabled for this generated workflow." : `These nodes are not ${hostDisplayName(host)} native routes.`;
|
|
28861
|
+
return [nativeNotice, "CLI routes:", ...routes.map(cliDispatchLine), ""].join(`
|
|
28862
|
+
`);
|
|
28863
|
+
}
|
|
28864
|
+
function cliDispatchLine(route) {
|
|
28865
|
+
return `- ${route.nodeId}: ${route.runnerId} CLI profile=${route.profileId} command=\`${runnerCliCommand(route)}\` needs=${needsSummary(route.needs)}`;
|
|
28866
|
+
}
|
|
28867
|
+
function runnerCliCommand(route) {
|
|
28868
|
+
if (route.runnerId === "codex") {
|
|
28869
|
+
return `codex exec --json -C <repo-root> --sandbox ${codexSandbox(route.profile)} --skip-git-repo-check <node prompt>`;
|
|
28870
|
+
}
|
|
28871
|
+
if (route.runnerId === "kimi") {
|
|
28872
|
+
return `kimi --print --agent-file .kimi/agents/${route.profileId}.yaml --work-dir <repo-root> --final-message-only --prompt <node prompt>`;
|
|
28873
|
+
}
|
|
28874
|
+
if (route.runnerId === "opencode") {
|
|
28875
|
+
return `opencode run --agent ${route.profileId} --format json --dir <repo-root> <node prompt>`;
|
|
28876
|
+
}
|
|
28877
|
+
if (route.runnerId === "claude") {
|
|
28878
|
+
return `claude --agent ${route.profileId} --print -p <node prompt>`;
|
|
28879
|
+
}
|
|
28880
|
+
if (route.runnerId === "pi") {
|
|
28881
|
+
return "pi --print --no-session <node prompt>";
|
|
28776
28882
|
}
|
|
28777
|
-
return
|
|
28883
|
+
return `${route.runnerId} <node prompt>`;
|
|
28884
|
+
}
|
|
28885
|
+
function codexSandbox(profile) {
|
|
28886
|
+
return profile.filesystem?.mode === "workspace-write" ? "workspace-write" : "read-only";
|
|
28887
|
+
}
|
|
28888
|
+
function nodePromptContract(workflowId, routes) {
|
|
28889
|
+
const hasCliRoutes = routes.some((route) => route.kind === "cli");
|
|
28890
|
+
const lead = hasCliRoutes ? "For each CLI node prompt include:" : "For each native node prompt include:";
|
|
28891
|
+
return [
|
|
28892
|
+
lead,
|
|
28893
|
+
"- user task",
|
|
28894
|
+
`- workflow id: ${workflowId}`,
|
|
28895
|
+
"- node id",
|
|
28896
|
+
"- profile id",
|
|
28897
|
+
"- runner id",
|
|
28898
|
+
"- profile instructions reference",
|
|
28899
|
+
"- profile grants",
|
|
28900
|
+
"- dependency outputs",
|
|
28901
|
+
""
|
|
28902
|
+
].join(`
|
|
28903
|
+
`);
|
|
28904
|
+
}
|
|
28905
|
+
function hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes) {
|
|
28906
|
+
if (host === "codex" && nativeRoutes.length > 0) {
|
|
28907
|
+
return "Do not substitute the generic Codex worker for configured profiles.";
|
|
28908
|
+
}
|
|
28909
|
+
if (cliRoutes.length > 0 && nativeRoutes.length > 0) {
|
|
28910
|
+
return `Do not claim CLI routes are ${hostDisplayName(host)} native routes.`;
|
|
28911
|
+
}
|
|
28912
|
+
if (cliRoutes.length > 0 && nativeRoutes.length === 0 && host !== "pi") {
|
|
28913
|
+
return `Do not claim these nodes are ${hostDisplayName(host)} subagents.`;
|
|
28914
|
+
}
|
|
28915
|
+
return;
|
|
28916
|
+
}
|
|
28917
|
+
function hostDisplayName(host) {
|
|
28918
|
+
const names = {
|
|
28919
|
+
claude: "Claude",
|
|
28920
|
+
codex: "Codex",
|
|
28921
|
+
kimi: "Kimi",
|
|
28922
|
+
opencode: "OpenCode",
|
|
28923
|
+
pi: "Pi"
|
|
28924
|
+
};
|
|
28925
|
+
return names[host];
|
|
28926
|
+
}
|
|
28927
|
+
function needsSummary(needs) {
|
|
28928
|
+
return needs.length > 0 ? needs.join(",") : "none";
|
|
28778
28929
|
}
|
|
28779
28930
|
function compactLines(lines) {
|
|
28780
28931
|
return lines.filter((line) => line !== undefined);
|
|
@@ -28803,20 +28954,16 @@ function claudeDefinitions(config2) {
|
|
|
28803
28954
|
}, compactLines([
|
|
28804
28955
|
header("claude").trimEnd(),
|
|
28805
28956
|
"",
|
|
28806
|
-
workflowSummary(config2),
|
|
28807
|
-
"",
|
|
28808
28957
|
orchestratorBlock(config2),
|
|
28809
28958
|
"",
|
|
28810
|
-
|
|
28811
|
-
"",
|
|
28812
|
-
`Delegate work only to configured profiles: ${profileNames(config2)}.`
|
|
28959
|
+
dispatchBlock("claude", config2)
|
|
28813
28960
|
]).join(`
|
|
28814
28961
|
`)),
|
|
28815
28962
|
host: "claude",
|
|
28816
28963
|
invocation: "/pipe <task description>",
|
|
28817
28964
|
path: ".claude/commands/pipe.md"
|
|
28818
28965
|
},
|
|
28819
|
-
...
|
|
28966
|
+
...nativeProfileEntries("claude", config2).map(([id, profile]) => ({
|
|
28820
28967
|
content: markdown({
|
|
28821
28968
|
description: profile.description ?? id,
|
|
28822
28969
|
name: id,
|
|
@@ -28856,18 +29003,13 @@ function opencodeDefinitions(config2) {
|
|
|
28856
29003
|
{
|
|
28857
29004
|
content: markdown({
|
|
28858
29005
|
agent: "pipeline-orchestrator",
|
|
28859
|
-
description: "Run the configured pipeline workflow"
|
|
28860
|
-
subtask: true
|
|
29006
|
+
description: "Run the configured pipeline workflow"
|
|
28861
29007
|
}, compactLines([
|
|
28862
29008
|
header("opencode").trimEnd(),
|
|
28863
29009
|
"",
|
|
28864
|
-
workflowSummary(config2),
|
|
28865
|
-
"",
|
|
28866
29010
|
orchestratorBlock(config2),
|
|
28867
29011
|
"",
|
|
28868
|
-
|
|
28869
|
-
"",
|
|
28870
|
-
`Delegate work only to configured profiles: ${profileNames(config2)}.`
|
|
29012
|
+
dispatchBlock("opencode", config2)
|
|
28871
29013
|
]).join(`
|
|
28872
29014
|
`)),
|
|
28873
29015
|
host: "opencode",
|
|
@@ -28879,24 +29021,25 @@ function opencodeDefinitions(config2) {
|
|
|
28879
29021
|
description: "Orchestrate the configured pipeline and enforce gates.",
|
|
28880
29022
|
mode: "primary",
|
|
28881
29023
|
permission: opencodePermission(orchestratorProfile(config2), {
|
|
28882
|
-
forceTask:
|
|
29024
|
+
forceTask: agentDispatchRoutes("opencode", config2).some((route) => route.kind !== "cli")
|
|
28883
29025
|
})
|
|
28884
29026
|
}, compactLines([
|
|
28885
29027
|
header("opencode").trimEnd(),
|
|
28886
29028
|
"",
|
|
28887
29029
|
orchestratorBlock(config2),
|
|
28888
29030
|
"",
|
|
28889
|
-
|
|
29031
|
+
dispatchBlock("opencode", config2)
|
|
28890
29032
|
]).join(`
|
|
28891
29033
|
`)),
|
|
28892
29034
|
host: "opencode",
|
|
28893
29035
|
invocation: "/pipe <task description>",
|
|
28894
29036
|
path: ".opencode/agents/pipeline-orchestrator.md"
|
|
28895
29037
|
},
|
|
28896
|
-
...
|
|
29038
|
+
...nativeProfileEntries("opencode", config2).map(([id, profile]) => ({
|
|
28897
29039
|
content: markdown({
|
|
28898
29040
|
description: profile.description ?? id,
|
|
28899
29041
|
mode: "subagent",
|
|
29042
|
+
...opencodeModelProjection(config2, profile),
|
|
28900
29043
|
permission: opencodePermission(profile)
|
|
28901
29044
|
}, [
|
|
28902
29045
|
header("opencode").trimEnd(),
|
|
@@ -28926,21 +29069,17 @@ function codexDefinitions(config2) {
|
|
|
28926
29069
|
"",
|
|
28927
29070
|
"Invoke this skill with `$pipe <task description>`.",
|
|
28928
29071
|
"",
|
|
28929
|
-
workflowSummary(config2),
|
|
28930
|
-
"",
|
|
28931
29072
|
orchestratorBlock(config2),
|
|
28932
29073
|
"",
|
|
28933
|
-
|
|
28934
|
-
"",
|
|
28935
|
-
`Use separate configured profiles: ${profileNames(config2)}.`
|
|
29074
|
+
dispatchBlock("codex", config2)
|
|
28936
29075
|
]).join(`
|
|
28937
29076
|
`)),
|
|
28938
29077
|
host: "codex",
|
|
28939
29078
|
invocation: "$pipe <task description>",
|
|
28940
29079
|
path: ".agents/skills/pipe/SKILL.md"
|
|
28941
29080
|
},
|
|
28942
|
-
...
|
|
28943
|
-
content: `${stringify({
|
|
29081
|
+
...nativeProfileEntries("codex", config2).map(([id, profile]) => ({
|
|
29082
|
+
content: `${hashHeader("codex")}${stringify({
|
|
28944
29083
|
description: profile.description ?? id,
|
|
28945
29084
|
developer_instructions: [
|
|
28946
29085
|
profile.description ?? id,
|
|
@@ -28963,39 +29102,153 @@ function kimiDefinitions(config2) {
|
|
|
28963
29102
|
return [
|
|
28964
29103
|
{
|
|
28965
29104
|
content: markdown({
|
|
29105
|
+
name: "pipe",
|
|
28966
29106
|
description: "Run the configured pipeline workflow with Kimi agents"
|
|
28967
29107
|
}, compactLines([
|
|
28968
29108
|
header("kimi").trimEnd(),
|
|
28969
29109
|
"",
|
|
28970
|
-
workflowSummary(config2),
|
|
28971
|
-
"",
|
|
28972
29110
|
orchestratorBlock(config2),
|
|
28973
29111
|
"",
|
|
28974
|
-
|
|
28975
|
-
"",
|
|
28976
|
-
`Use separate configured profiles: ${profileNames(config2)}.`
|
|
29112
|
+
dispatchBlock("kimi", config2)
|
|
28977
29113
|
]).join(`
|
|
28978
29114
|
`)),
|
|
28979
29115
|
host: "kimi",
|
|
28980
|
-
invocation: "/pipe <task description>",
|
|
28981
|
-
path: ".kimi/
|
|
29116
|
+
invocation: "/skill:pipe <task description>",
|
|
29117
|
+
path: ".kimi/skills/pipe/SKILL.md"
|
|
28982
29118
|
},
|
|
28983
|
-
...
|
|
28984
|
-
|
|
29119
|
+
...kimiOrchestratorAgentDefinitions(config2),
|
|
29120
|
+
...nativeProfileEntries("kimi", config2).flatMap(([id, profile]) => kimiAgentDefinitions(id, profile))
|
|
29121
|
+
];
|
|
29122
|
+
}
|
|
29123
|
+
function opencodeModelProjection(config2, profile) {
|
|
29124
|
+
const model = resolvedHostModel(config2, "opencode", profile);
|
|
29125
|
+
return model ? { model } : {};
|
|
29126
|
+
}
|
|
29127
|
+
function kimiAgentDefinitions(id, profile) {
|
|
29128
|
+
const agentPath = `.kimi/agents/${id}.yaml`;
|
|
29129
|
+
const promptPath = `.kimi/agents/${id}.prompt.md`;
|
|
29130
|
+
const promptDefinitions = profile.instructions.inline ? [
|
|
29131
|
+
{
|
|
29132
|
+
content: [
|
|
28985
29133
|
header("kimi").trimEnd(),
|
|
28986
29134
|
"",
|
|
28987
|
-
profile.
|
|
29135
|
+
profile.instructions.inline,
|
|
29136
|
+
""
|
|
29137
|
+
].join(`
|
|
29138
|
+
`),
|
|
29139
|
+
host: "kimi",
|
|
29140
|
+
invocation: "/skill:pipe <task description>",
|
|
29141
|
+
path: promptPath
|
|
29142
|
+
}
|
|
29143
|
+
] : [];
|
|
29144
|
+
const systemPromptPath = profile.instructions.path ? relative(dirname(agentPath), profile.instructions.path).replaceAll("\\", "/") : `./${id}.prompt.md`;
|
|
29145
|
+
return [
|
|
29146
|
+
{
|
|
29147
|
+
content: [
|
|
29148
|
+
yamlHeader("kimi").trimEnd(),
|
|
28988
29149
|
"",
|
|
28989
|
-
|
|
28990
|
-
|
|
29150
|
+
$stringify({
|
|
29151
|
+
version: 1,
|
|
29152
|
+
agent: {
|
|
29153
|
+
allowed_tools: kimiAllowedTools(profile),
|
|
29154
|
+
extend: "default",
|
|
29155
|
+
name: id,
|
|
29156
|
+
system_prompt_path: systemPromptPath
|
|
29157
|
+
}
|
|
29158
|
+
}).trimEnd(),
|
|
29159
|
+
""
|
|
29160
|
+
].join(`
|
|
29161
|
+
`),
|
|
29162
|
+
host: "kimi",
|
|
29163
|
+
invocation: "/skill:pipe <task description>",
|
|
29164
|
+
path: agentPath
|
|
29165
|
+
},
|
|
29166
|
+
...promptDefinitions
|
|
29167
|
+
];
|
|
29168
|
+
}
|
|
29169
|
+
function kimiOrchestratorAgentDefinitions(config2) {
|
|
29170
|
+
if (agentDispatchRoutes("kimi", config2).every((route) => route.kind !== "native-named-agent")) {
|
|
29171
|
+
return [];
|
|
29172
|
+
}
|
|
29173
|
+
const agentPath = ".kimi/agents/pipeline-orchestrator.yaml";
|
|
29174
|
+
const promptPath = ".kimi/agents/pipeline-orchestrator.prompt.md";
|
|
29175
|
+
const nativeKimiProfiles = nativeProfileEntries("kimi", config2);
|
|
29176
|
+
const subagents = Object.fromEntries(nativeKimiProfiles.map(([id, profile]) => [
|
|
29177
|
+
id,
|
|
29178
|
+
{
|
|
29179
|
+
description: profile.description ?? id,
|
|
29180
|
+
path: `./${id}.yaml`
|
|
29181
|
+
}
|
|
29182
|
+
]));
|
|
29183
|
+
const plan = compileWorkflowPlan(config2);
|
|
29184
|
+
const hasKimiNodes = plan.topologicalOrder.some((node) => {
|
|
29185
|
+
if (!(node.kind === "agent" && node.profile)) {
|
|
29186
|
+
return false;
|
|
29187
|
+
}
|
|
29188
|
+
return config2.profiles[node.profile]?.runner === "kimi";
|
|
29189
|
+
});
|
|
29190
|
+
const hasNonKimiNodes = plan.topologicalOrder.some((node) => {
|
|
29191
|
+
if (!(node.kind === "agent" && node.profile)) {
|
|
29192
|
+
return false;
|
|
29193
|
+
}
|
|
29194
|
+
return config2.profiles[node.profile]?.runner !== "kimi";
|
|
29195
|
+
});
|
|
29196
|
+
const allowedTools = [
|
|
29197
|
+
...hasKimiNodes ? ["kimi_cli.tools.agent:Agent"] : [],
|
|
29198
|
+
...hasNonKimiNodes ? ["kimi_cli.tools.shell:Shell"] : []
|
|
29199
|
+
];
|
|
29200
|
+
return [
|
|
29201
|
+
{
|
|
29202
|
+
content: [
|
|
29203
|
+
yamlHeader("kimi").trimEnd(),
|
|
28991
29204
|
"",
|
|
28992
|
-
|
|
29205
|
+
$stringify({
|
|
29206
|
+
version: 1,
|
|
29207
|
+
agent: {
|
|
29208
|
+
allowed_tools: allowedTools,
|
|
29209
|
+
extend: "default",
|
|
29210
|
+
name: "pipeline-orchestrator",
|
|
29211
|
+
...Object.keys(subagents).length > 0 ? { subagents } : {},
|
|
29212
|
+
system_prompt_path: "./pipeline-orchestrator.prompt.md"
|
|
29213
|
+
}
|
|
29214
|
+
}).trimEnd(),
|
|
29215
|
+
""
|
|
28993
29216
|
].join(`
|
|
28994
|
-
`)
|
|
29217
|
+
`),
|
|
28995
29218
|
host: "kimi",
|
|
28996
|
-
invocation: "/pipe <task description>",
|
|
28997
|
-
path:
|
|
28998
|
-
}
|
|
29219
|
+
invocation: 'kimi --agent-file .kimi/agents/pipeline-orchestrator.yaml --work-dir <repo-root> --prompt "/skill:pipe <task description>"',
|
|
29220
|
+
path: agentPath
|
|
29221
|
+
},
|
|
29222
|
+
{
|
|
29223
|
+
content: compactLines([
|
|
29224
|
+
header("kimi").trimEnd(),
|
|
29225
|
+
"",
|
|
29226
|
+
orchestratorBlock(config2),
|
|
29227
|
+
"",
|
|
29228
|
+
dispatchBlock("kimi", config2),
|
|
29229
|
+
"",
|
|
29230
|
+
"This agent file is the Kimi-native orchestrator surface. Launch Kimi with `--agent-file .kimi/agents/pipeline-orchestrator.yaml` before using `/skill:pipe` when you want Kimi runner nodes to run through Kimi's native Agent tool."
|
|
29231
|
+
]).join(`
|
|
29232
|
+
`),
|
|
29233
|
+
host: "kimi",
|
|
29234
|
+
invocation: 'kimi --agent-file .kimi/agents/pipeline-orchestrator.yaml --work-dir <repo-root> --prompt "/skill:pipe <task description>"',
|
|
29235
|
+
path: promptPath
|
|
29236
|
+
}
|
|
29237
|
+
];
|
|
29238
|
+
}
|
|
29239
|
+
function kimiAllowedTools(profile) {
|
|
29240
|
+
const mapped = new Map([
|
|
29241
|
+
["bash", "kimi_cli.tools.shell:Shell"],
|
|
29242
|
+
["edit", "kimi_cli.tools.file:StrReplaceFile"],
|
|
29243
|
+
["glob", "kimi_cli.tools.file:Glob"],
|
|
29244
|
+
["grep", "kimi_cli.tools.file:Grep"],
|
|
29245
|
+
["list", "kimi_cli.tools.file:Glob"],
|
|
29246
|
+
["read", "kimi_cli.tools.file:ReadFile"],
|
|
29247
|
+
["task", "kimi_cli.tools.agent:Agent"],
|
|
29248
|
+
["write", "kimi_cli.tools.file:WriteFile"]
|
|
29249
|
+
]);
|
|
29250
|
+
return [
|
|
29251
|
+
...new Set((profile.tools ?? []).flatMap((tool) => mapped.get(tool) ?? []))
|
|
28999
29252
|
];
|
|
29000
29253
|
}
|
|
29001
29254
|
function piDefinitions(config2) {
|
|
@@ -29003,111 +29256,21 @@ function piDefinitions(config2) {
|
|
|
29003
29256
|
{
|
|
29004
29257
|
content: markdown({
|
|
29005
29258
|
"argument-hint": "<task description>",
|
|
29006
|
-
description: "Run the configured pipeline workflow
|
|
29259
|
+
description: "Run the configured pipeline workflow"
|
|
29007
29260
|
}, compactLines([
|
|
29008
29261
|
header("pi").trimEnd(),
|
|
29009
29262
|
"",
|
|
29010
|
-
workflowSummary(config2),
|
|
29011
|
-
"",
|
|
29012
29263
|
orchestratorBlock(config2),
|
|
29013
29264
|
"",
|
|
29014
|
-
|
|
29265
|
+
dispatchBlock("pi", config2)
|
|
29015
29266
|
]).join(`
|
|
29016
29267
|
`)),
|
|
29017
29268
|
host: "pi",
|
|
29018
29269
|
invocation: "/pipe <task description>",
|
|
29019
29270
|
path: ".pi/prompts/pipe.md"
|
|
29020
|
-
},
|
|
29021
|
-
{
|
|
29022
|
-
content: [
|
|
29023
|
-
tsHeader("pi").trimEnd(),
|
|
29024
|
-
"",
|
|
29025
|
-
"interface PiCommand {",
|
|
29026
|
-
" name: string;",
|
|
29027
|
-
"}",
|
|
29028
|
-
"interface PiCommandContext {",
|
|
29029
|
-
" sendUserMessage(message: string): Promise<void> | void;",
|
|
29030
|
-
" ui: { notify(message: string, type?: string): void };",
|
|
29031
|
-
"}",
|
|
29032
|
-
"interface PiExtensionApi {",
|
|
29033
|
-
" getCommands?(): PiCommand[];",
|
|
29034
|
-
" registerCommand(",
|
|
29035
|
-
" name: string,",
|
|
29036
|
-
" command: {",
|
|
29037
|
-
" description: string;",
|
|
29038
|
-
" handler(args: string, ctx: PiCommandContext): Promise<void> | void;",
|
|
29039
|
-
" }",
|
|
29040
|
-
" ): void;",
|
|
29041
|
-
"}",
|
|
29042
|
-
"",
|
|
29043
|
-
...piOrchestratorComment(config2),
|
|
29044
|
-
"",
|
|
29045
|
-
...piWorkflowNodesLiteral(config2),
|
|
29046
|
-
"",
|
|
29047
|
-
"function renderSubagentCommand(task: string): string {",
|
|
29048
|
-
' const chain = WORKFLOW_NODES.filter((node) => node.kind === "agent")',
|
|
29049
|
-
" .map((node) => `" + "$" + "{String(node.profile)}: " + "$" + "{node.id} " + "$" + "{task}" + "`)",
|
|
29050
|
-
' .join(" -> ");',
|
|
29051
|
-
[" return `/chain ", "$", "{JSON.stringify(chain)}`;"].join(""),
|
|
29052
|
-
"}",
|
|
29053
|
-
"",
|
|
29054
|
-
"export default function pipelineWorkNext(pi: PiExtensionApi): void {",
|
|
29055
|
-
' pi.registerCommand("pipe", {',
|
|
29056
|
-
' description: "Run the configured pipeline with Pi subagents",',
|
|
29057
|
-
" handler: async (args: string, ctx) => {",
|
|
29058
|
-
' const task = String(args ?? "").trim();',
|
|
29059
|
-
" if (!task) {",
|
|
29060
|
-
' ctx.ui.notify("Usage: /pipe <task description>", "error");',
|
|
29061
|
-
" return;",
|
|
29062
|
-
" }",
|
|
29063
|
-
" const commands =",
|
|
29064
|
-
' typeof pi.getCommands === "function" ? pi.getCommands() : [];',
|
|
29065
|
-
" const hasPiSubagents = commands.some((command) =>",
|
|
29066
|
-
' ["run", "chain", "parallel", "run-chain", "subagents-doctor"].some(',
|
|
29067
|
-
" (name) => command.name === name || command.name.startsWith(`" + "$" + "{name}:" + "`)",
|
|
29068
|
-
" )",
|
|
29069
|
-
" );",
|
|
29070
|
-
" if (!hasPiSubagents) {",
|
|
29071
|
-
' ctx.ui.notify("Install pi-subagents before running /pipe.", "error");',
|
|
29072
|
-
" return;",
|
|
29073
|
-
" }",
|
|
29074
|
-
" await ctx.sendUserMessage(renderSubagentCommand(task));",
|
|
29075
|
-
" },",
|
|
29076
|
-
" });",
|
|
29077
|
-
"}",
|
|
29078
|
-
""
|
|
29079
|
-
].join(`
|
|
29080
|
-
`),
|
|
29081
|
-
host: "pi",
|
|
29082
|
-
invocation: "/pipe <task description>",
|
|
29083
|
-
path: ".pi/extensions/pipe.ts"
|
|
29084
29271
|
}
|
|
29085
29272
|
];
|
|
29086
29273
|
}
|
|
29087
|
-
function piWorkflowNodesLiteral(config2) {
|
|
29088
|
-
const plan = compileWorkflowPlan(config2);
|
|
29089
|
-
return [
|
|
29090
|
-
"const WORKFLOW_NODES = [",
|
|
29091
|
-
...plan.topologicalOrder.flatMap((node) => [
|
|
29092
|
-
" {",
|
|
29093
|
-
` id: ${JSON.stringify(node.id)},`,
|
|
29094
|
-
` kind: ${JSON.stringify(node.kind)},`,
|
|
29095
|
-
` needs: ${JSON.stringify(node.needs)},`,
|
|
29096
|
-
` profile: ${JSON.stringify(node.profile ?? null)},`,
|
|
29097
|
-
" },"
|
|
29098
|
-
]),
|
|
29099
|
-
"] as const;"
|
|
29100
|
-
];
|
|
29101
|
-
}
|
|
29102
|
-
function piOrchestratorComment(config2) {
|
|
29103
|
-
return compactLines([
|
|
29104
|
-
"/*",
|
|
29105
|
-
orchestratorBlock(config2),
|
|
29106
|
-
"",
|
|
29107
|
-
nativeDelegationInstruction("pi", config2),
|
|
29108
|
-
"*/"
|
|
29109
|
-
]);
|
|
29110
|
-
}
|
|
29111
29274
|
function instructionsPointer(actor) {
|
|
29112
29275
|
if (actor.instructions.path) {
|
|
29113
29276
|
return `Instructions: ${actor.instructions.path}`;
|
|
@@ -29126,6 +29289,67 @@ function definitionsFor(host, config2) {
|
|
|
29126
29289
|
const hosts = host === "all" ? COMMAND_HOSTS : [host];
|
|
29127
29290
|
return hosts.flatMap((name) => definitions[name]());
|
|
29128
29291
|
}
|
|
29292
|
+
function selectedHosts(host) {
|
|
29293
|
+
return host === "all" ? [...COMMAND_HOSTS] : [host];
|
|
29294
|
+
}
|
|
29295
|
+
var GENERATED_RESOURCE_ROOTS = {
|
|
29296
|
+
claude: [".claude/commands", ".claude/agents"],
|
|
29297
|
+
codex: [".agents/skills", ".codex/agents"],
|
|
29298
|
+
kimi: [".kimi/skills", ".kimi/agents", ".kimi/commands"],
|
|
29299
|
+
opencode: [".opencode/commands", ".opencode/agents"],
|
|
29300
|
+
pi: [".pi/prompts", ".pi/extensions"]
|
|
29301
|
+
};
|
|
29302
|
+
async function listFiles(root) {
|
|
29303
|
+
if (!existsSync2(root)) {
|
|
29304
|
+
return [];
|
|
29305
|
+
}
|
|
29306
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
29307
|
+
const files = await Promise.all(entries.map((entry) => {
|
|
29308
|
+
const path = join2(root, entry.name);
|
|
29309
|
+
if (entry.isDirectory()) {
|
|
29310
|
+
return listFiles(path);
|
|
29311
|
+
}
|
|
29312
|
+
return [path];
|
|
29313
|
+
}));
|
|
29314
|
+
return files.flat();
|
|
29315
|
+
}
|
|
29316
|
+
function generatedHostFor(content) {
|
|
29317
|
+
return COMMAND_HOSTS.find((host) => content.includes(`${OWNER_MARKER_PREFIX}host=${host} -->`) || content.includes(`${OWNER_TS_MARKER_PREFIX}host=${host}`) || content.includes(`${OWNER_YAML_MARKER_PREFIX}host=${host}`));
|
|
29318
|
+
}
|
|
29319
|
+
async function obsoleteGeneratedItems(cwd, host, wantedPaths) {
|
|
29320
|
+
const hosts = new Set(selectedHosts(host));
|
|
29321
|
+
const roots = selectedHosts(host).flatMap((selectedHost) => GENERATED_RESOURCE_ROOTS[selectedHost]);
|
|
29322
|
+
const files = await Promise.all(roots.map((root) => listFiles(join2(cwd, root))));
|
|
29323
|
+
return files.flat().flatMap((absolutePath) => {
|
|
29324
|
+
const content = readFileSync2(absolutePath, "utf8");
|
|
29325
|
+
const generatedHost = generatedHostFor(content);
|
|
29326
|
+
if (!(generatedHost && hosts.has(generatedHost))) {
|
|
29327
|
+
return [];
|
|
29328
|
+
}
|
|
29329
|
+
const path = relative(cwd, absolutePath).replaceAll("\\", "/");
|
|
29330
|
+
if (wantedPaths.has(path)) {
|
|
29331
|
+
return [];
|
|
29332
|
+
}
|
|
29333
|
+
return [
|
|
29334
|
+
{
|
|
29335
|
+
action: "delete",
|
|
29336
|
+
host: generatedHost,
|
|
29337
|
+
invocation: invocationForHost(generatedHost),
|
|
29338
|
+
path
|
|
29339
|
+
}
|
|
29340
|
+
];
|
|
29341
|
+
}).sort((a, b) => a.path.localeCompare(b.path));
|
|
29342
|
+
}
|
|
29343
|
+
function invocationForHost(host) {
|
|
29344
|
+
const invocations = {
|
|
29345
|
+
claude: "/pipe <task description>",
|
|
29346
|
+
codex: "$pipe <task description>",
|
|
29347
|
+
kimi: "/skill:pipe <task description>",
|
|
29348
|
+
opencode: "/pipe <task description>",
|
|
29349
|
+
pi: "/pipe <task description>"
|
|
29350
|
+
};
|
|
29351
|
+
return invocations[host];
|
|
29352
|
+
}
|
|
29129
29353
|
function actionFor(path, content, force) {
|
|
29130
29354
|
if (!existsSync2(path)) {
|
|
29131
29355
|
return "create";
|
|
@@ -29134,7 +29358,7 @@ function actionFor(path, content, force) {
|
|
|
29134
29358
|
if (current === content) {
|
|
29135
29359
|
return "unchanged";
|
|
29136
29360
|
}
|
|
29137
|
-
if (!(current.includes(GENERATED_MARKER) || current.includes(GENERATED_TS_MARKER) || force)) {
|
|
29361
|
+
if (!(current.includes(GENERATED_MARKER) || current.includes(GENERATED_TS_MARKER) || current.includes(GENERATED_YAML_MARKER) || force)) {
|
|
29138
29362
|
return "conflict";
|
|
29139
29363
|
}
|
|
29140
29364
|
return "update";
|
|
@@ -29144,7 +29368,9 @@ async function installCommands(options2 = {}) {
|
|
|
29144
29368
|
const host = options2.host ?? "all";
|
|
29145
29369
|
const config2 = loadPipelineConfig(cwd);
|
|
29146
29370
|
const items = [];
|
|
29147
|
-
|
|
29371
|
+
const definitions = definitionsFor(host, config2);
|
|
29372
|
+
const wantedPaths = new Set(definitions.map((definition) => definition.path));
|
|
29373
|
+
for (const definition of definitions) {
|
|
29148
29374
|
const target = join2(cwd, definition.path);
|
|
29149
29375
|
const action = actionFor(target, definition.content, Boolean(options2.force));
|
|
29150
29376
|
items.push({
|
|
@@ -29162,6 +29388,13 @@ async function installCommands(options2 = {}) {
|
|
|
29162
29388
|
await mkdir(dirname(target), { recursive: true });
|
|
29163
29389
|
await writeFile(target, definition.content);
|
|
29164
29390
|
}
|
|
29391
|
+
const obsoleteItems = await obsoleteGeneratedItems(cwd, host, wantedPaths);
|
|
29392
|
+
items.push(...obsoleteItems);
|
|
29393
|
+
if (!(options2.check || options2.dryRun)) {
|
|
29394
|
+
for (const item of obsoleteItems) {
|
|
29395
|
+
await rm(join2(cwd, item.path), { force: true });
|
|
29396
|
+
}
|
|
29397
|
+
}
|
|
29165
29398
|
if (!options2.dryRun && items.some((item) => item.action === "conflict")) {
|
|
29166
29399
|
throw new Error([
|
|
29167
29400
|
"Refusing to overwrite manually edited command files.",
|
|
@@ -29619,7 +29852,7 @@ function hostResourceInput(host) {
|
|
|
29619
29852
|
return [
|
|
29620
29853
|
`# ${host} Resource Input`,
|
|
29621
29854
|
"",
|
|
29622
|
-
"This file is scaffolded input for host-specific
|
|
29855
|
+
"This file is scaffolded input for host-specific generated resources.",
|
|
29623
29856
|
"The source of truth is `.pipeline/pipeline.yaml` plus `.pipeline/profiles.yaml` and `.pipeline/runners.yaml`; generated host resources must preserve the profiles, prompts, rules, tools, filesystem policy, network policy, and output contracts declared there.",
|
|
29624
29857
|
""
|
|
29625
29858
|
].join(`
|
|
@@ -36912,8 +37145,6 @@ function createActorLaunchPlan(config2, input, actor, runnerId) {
|
|
|
36912
37145
|
function piArgv(prompt, config2, actor, worktreePath = process.cwd(), runner) {
|
|
36913
37146
|
return [
|
|
36914
37147
|
"--print",
|
|
36915
|
-
"--mode",
|
|
36916
|
-
"json",
|
|
36917
37148
|
...optionalModelArgs("pi", runner, actor),
|
|
36918
37149
|
...piToolArgs(actor?.tools ?? []),
|
|
36919
37150
|
...skillArgsFor("pi", config2, actor, worktreePath),
|
|
@@ -38642,7 +38873,10 @@ function createCliProgram() {
|
|
|
38642
38873
|
console.log(formatPipelineInitResult(result));
|
|
38643
38874
|
});
|
|
38644
38875
|
program2.command("install-commands").description("Install generated slash-command adapters into this repository").addOption(new Option("--host <host>", "host command set to install").choices(["all", "claude", "opencode", "codex", "kimi", "pi"]).default("all").argParser(parseCommandHost)).option("--dry-run", "show planned changes without writing files").option("--check", "fail if generated command files are missing or stale").option("--force", "overwrite manually edited command files").action(async (flags) => {
|
|
38645
|
-
const result = await installCommands(
|
|
38876
|
+
const result = await installCommands({
|
|
38877
|
+
...flags,
|
|
38878
|
+
cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd()
|
|
38879
|
+
});
|
|
38646
38880
|
console.log(formatInstallCommandsResult(result));
|
|
38647
38881
|
});
|
|
38648
38882
|
return program2;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const COMMAND_HOSTS: readonly ["claude", "opencode", "codex", "kimi", "pi"];
|
|
2
2
|
export type CommandHost = (typeof COMMAND_HOSTS)[number];
|
|
3
3
|
export type CommandHostSelection = CommandHost | "all";
|
|
4
|
-
export type InstallAction = "conflict" | "create" | "unchanged" | "update";
|
|
4
|
+
export type InstallAction = "conflict" | "create" | "delete" | "unchanged" | "update";
|
|
5
5
|
export interface CommandInstallPlanItem {
|
|
6
6
|
action: InstallAction;
|
|
7
7
|
host: CommandHost;
|
package/dist/pipeline-runtime.js
CHANGED
|
@@ -28560,6 +28560,7 @@ var runnerSchema = exports_external.object({
|
|
|
28560
28560
|
args: exports_external.array(exports_external.string()).optional(),
|
|
28561
28561
|
capabilities: runnerCapabilitiesSchema,
|
|
28562
28562
|
command: exports_external.string().optional(),
|
|
28563
|
+
host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
|
|
28563
28564
|
model: exports_external.string().optional(),
|
|
28564
28565
|
type: exports_external.enum(RUNNER_TYPES)
|
|
28565
28566
|
}).strict();
|
|
@@ -28625,6 +28626,7 @@ var retriesSchema = exports_external.object({
|
|
|
28625
28626
|
var profileSchema = exports_external.object({
|
|
28626
28627
|
description: exports_external.string().optional(),
|
|
28627
28628
|
filesystem: filesystemSchema.optional(),
|
|
28629
|
+
host_models: exports_external.record(exports_external.string(), exports_external.string().min(1)).optional(),
|
|
28628
28630
|
instructions: instructionsSchema,
|
|
28629
28631
|
mcp_servers: exports_external.array(exports_external.string()).optional(),
|
|
28630
28632
|
model: exports_external.string().optional(),
|
|
@@ -29627,8 +29629,6 @@ function createActorLaunchPlan(config2, input, actor, runnerId) {
|
|
|
29627
29629
|
function piArgv(prompt, config2, actor, worktreePath = process.cwd(), runner) {
|
|
29628
29630
|
return [
|
|
29629
29631
|
"--print",
|
|
29630
|
-
"--mode",
|
|
29631
|
-
"json",
|
|
29632
29632
|
...optionalModelArgs("pi", runner, actor),
|
|
29633
29633
|
...piToolArgs(actor?.tools ?? []),
|
|
29634
29634
|
...skillArgsFor("pi", config2, actor, worktreePath),
|
package/dist/runner.js
CHANGED
|
@@ -7381,8 +7381,6 @@ async function execaHarnessPi(prompt, contextFile, worktreePath) {
|
|
|
7381
7381
|
${prompt}` : prompt;
|
|
7382
7382
|
const argv = [
|
|
7383
7383
|
"--print",
|
|
7384
|
-
"--mode",
|
|
7385
|
-
"json",
|
|
7386
7384
|
...optionalModelArgs("pi"),
|
|
7387
7385
|
"--no-session",
|
|
7388
7386
|
effectivePrompt
|
|
@@ -7477,8 +7475,6 @@ function createActorLaunchPlan(config, input, actor, runnerId) {
|
|
|
7477
7475
|
function piArgv(prompt, config, actor, worktreePath = process.cwd(), runner) {
|
|
7478
7476
|
return [
|
|
7479
7477
|
"--print",
|
|
7480
|
-
"--mode",
|
|
7481
|
-
"json",
|
|
7482
7478
|
...optionalModelArgs("pi", runner, actor),
|
|
7483
7479
|
...piToolArgs(actor?.tools ?? []),
|
|
7484
7480
|
...skillArgsFor("pi", config, actor, worktreePath),
|
|
@@ -189,14 +189,14 @@ timeouts, output limits, sanitized env, and explicit trust flags.
|
|
|
189
189
|
|
|
190
190
|
## Host Support Matrix
|
|
191
191
|
|
|
192
|
-
| Runner
|
|
193
|
-
|
|
|
194
|
-
| Claude
|
|
195
|
-
| Codex
|
|
196
|
-
| OpenCode | yes
|
|
197
|
-
| Kimi
|
|
198
|
-
| Pi
|
|
199
|
-
| command
|
|
192
|
+
| Runner | Native subagents | Rules | Skills | MCP | Outputs | Generated resources |
|
|
193
|
+
| -------- | ---------------------- | ----- | --------------------------------------------- | --- | ------------------------- | -------------------------------- |
|
|
194
|
+
| Claude | yes | yes | included in generated profile text | yes | text, JSON, schema | command plus `.claude/agents` |
|
|
195
|
+
| Codex | yes | yes | yes | yes | text, JSON, JSONL, schema | skill plus `.codex/agents` |
|
|
196
|
+
| OpenCode | yes | yes | included in generated profile text | yes | text, JSON, JSONL, schema | command plus `.opencode/agents` |
|
|
197
|
+
| Kimi | yes | yes | surfaced through project skills when declared | no | text, JSON | skill plus `.kimi/agents/*.yaml` |
|
|
198
|
+
| Pi | yes, with pi-subagents | yes | included in generated prompt text | no | text, JSON | prompt plus no-op extension shim |
|
|
199
|
+
| command | no | no | no | no | declared by runner | subprocess argv |
|
|
200
200
|
|
|
201
201
|
The runtime prefers native subagents when the runner advertises
|
|
202
202
|
`native_subagents: true` and the configured permissions, runner, output, and
|
|
@@ -204,6 +204,15 @@ resource grants can be represented safely. Otherwise it uses a subprocess for
|
|
|
204
204
|
the agent node. In both cases each agent node records a separate invocation
|
|
205
205
|
boundary; multi-agent workflows are never collapsed into one prompt.
|
|
206
206
|
|
|
207
|
+
Generated host resources follow a native-first, runner-correct rule. Same-host
|
|
208
|
+
agent nodes use exact native subagents. Cross-runner nodes use host-native
|
|
209
|
+
execution only when the host can explicitly run the requested model; OpenCode
|
|
210
|
+
does this through per-agent `model:` values resolved from profile or runner
|
|
211
|
+
`model`, with optional `host_models.opencode` overrides when model ids differ.
|
|
212
|
+
If native execution cannot represent the requested runner/model, generated
|
|
213
|
+
instructions dispatch to that runner's CLI instead of doing instruction-only
|
|
214
|
+
translation.
|
|
215
|
+
|
|
207
216
|
## Troubleshooting
|
|
208
217
|
|
|
209
218
|
- Missing config: run `pipe init`; `pipe run` requires
|
|
@@ -212,8 +221,9 @@ boundary; multi-agent workflows are never collapsed into one prompt.
|
|
|
212
221
|
- Capability error: reduce the profile grants or choose a runner whose declared
|
|
213
222
|
capabilities include the requested tools, filesystem, network, output, rules,
|
|
214
223
|
skills, or MCP access.
|
|
215
|
-
- Pi native execution
|
|
216
|
-
|
|
224
|
+
- Pi native execution unavailable: install and enable `pi-subagents` if you want
|
|
225
|
+
Pi-native chains. Otherwise `/pipe` uses the CLI dispatch instructions in the
|
|
226
|
+
generated Pi prompt.
|
|
217
227
|
- Gate failure: inspect `pipe run` output for node, gate, reason, and evidence.
|
|
218
228
|
Dependent nodes are not executed after a required gate fails.
|
|
219
229
|
- Schema failure: ensure the agent emits valid JSON and that `schema_path`
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Host Resource Adapter Contract
|
|
2
2
|
|
|
3
|
-
Generated host resources are
|
|
4
|
-
`.pipeline/profiles.yaml`, and `.pipeline/pipeline.yaml`. They do not
|
|
5
|
-
|
|
3
|
+
Generated host resources are derived from `.pipeline/runners.yaml`,
|
|
4
|
+
`.pipeline/profiles.yaml`, and `.pipeline/pipeline.yaml`. They do not maintain
|
|
5
|
+
independent profile definitions or silently translate one runner into another
|
|
6
|
+
host's default agent.
|
|
6
7
|
|
|
7
8
|
Install or check generated resources with:
|
|
8
9
|
|
|
@@ -13,23 +14,30 @@ pipe install-commands --host all --check
|
|
|
13
14
|
|
|
14
15
|
## Host Mappings
|
|
15
16
|
|
|
16
|
-
| Host
|
|
17
|
-
|
|
|
18
|
-
| Claude Code | `.claude/commands/pipe.md`, `.claude/agents/*.md`
|
|
19
|
-
| Codex
|
|
20
|
-
| OpenCode
|
|
21
|
-
| Kimi
|
|
22
|
-
| Pi
|
|
17
|
+
| Host | Generated resources | Invocation | Mechanical path |
|
|
18
|
+
| ----------- | ------------------------------------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
19
|
+
| Claude Code | `.claude/commands/pipe.md`, `.claude/agents/*.md` | `/pipe <task>` | Project command delegates to configured Claude agents. |
|
|
20
|
+
| Codex | `.agents/skills/pipe/SKILL.md`, `.codex/agents/*.toml` | `$pipe <task>` | Skill instructs Codex to use generated Codex agents for Codex runner nodes. |
|
|
21
|
+
| OpenCode | `.opencode/commands/pipe.md`, `.opencode/agents/*.md` | `/pipe <task>` | Project command runs a primary orchestrator and native subagents when the requested model is resolved. |
|
|
22
|
+
| Kimi | `.kimi/skills/pipe/SKILL.md`, `.kimi/agents/*.yaml` | `/skill:pipe <task>` | Kimi discovers project skills as `/skill:<name>` commands; Kimi agents are generated as YAML specs. |
|
|
23
|
+
| Pi | `.pi/prompts/pipe.md`, `.pi/extensions/pipe.ts` | `/pipe <task>` | Pi discovers project prompt templates as slash commands; the generated extension is a no-op shim. |
|
|
23
24
|
|
|
24
25
|
## Projection Rules
|
|
25
26
|
|
|
26
27
|
- Profile names, descriptions, instructions, tools, rules, skills, MCP servers,
|
|
27
28
|
filesystem mode, network mode, and output contracts are read from YAML.
|
|
29
|
+
- Exact native dispatch is used when a node runner matches the host.
|
|
30
|
+
- OpenCode can run mixed native subagents when the node runner has a resolved
|
|
31
|
+
model from `profile.host_models.opencode`, `runner.host_models.opencode`,
|
|
32
|
+
`profile.model`, or `runner.model`.
|
|
33
|
+
- Cross-runner nodes that cannot be represented natively are dispatched through
|
|
34
|
+
that runner's CLI. Instruction-only translation is not runner-correct and is
|
|
35
|
+
not used as an implicit fallback.
|
|
28
36
|
- Host-specific formats can omit unsupported capabilities, but they must not
|
|
29
37
|
grant broader access than requested.
|
|
30
38
|
- Regeneration is idempotent for generated files. Manual edits are protected
|
|
31
39
|
unless `--force` is supplied.
|
|
32
40
|
|
|
33
|
-
The CLI runtime and host
|
|
41
|
+
The CLI runtime and generated host resources share the same workflow plan. Multi-agent
|
|
34
42
|
workflows require separate agent boundaries; host resources must not collapse
|
|
35
43
|
the workflow into a single prompt.
|