@oisincoveney/pipeline 1.5.0 → 1.5.2

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 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 projections of the three config files; they are not
156
- separate sources of truth. The pipeline `orchestrator.profile` configures the
157
- host orchestrator surface, while workflow node `profile` values configure
158
- delegated agent boundaries.
159
-
160
- | Host | Generated files | Invocation |
161
- | --- | --- | --- |
162
- | Claude Code | `.claude/commands/pipe.md`, `.claude/agents/*.md` | `/pipe <task>` |
163
- | Codex | `.agents/skills/pipe/SKILL.md`, `.codex/agents/*.toml` | `$pipe <task>` |
164
- | OpenCode | `.opencode/commands/pipe.md`, `.opencode/agents/*.md` | `/pipe <task>` |
165
- | Kimi | `.kimi/commands/pipe.md`, `.kimi/agents/*.md` | `/pipe <task>` |
166
- | Pi | `.pi/extensions/pipe.ts`, `.pi/prompts/pipe.md` | `/pipe <task>` |
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 { loadPipelineConfig, parsePipelineConfigParts } from "@oisincoveney/pipeline/config";
206
+ import {
207
+ loadPipelineConfig,
208
+ parsePipelineConfigParts,
209
+ } from "@oisincoveney/pipeline/config";
193
210
  import { compileWorkflowPlan } from "@oisincoveney/pipeline/planner";
194
- import { runPipelineFromConfig, type PipelineRuntimeResult, type PipelineTaskContext } from "@oisincoveney/pipeline/runtime";
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",
@@ -28684,6 +28688,17 @@ function tsHeader(host) {
28684
28688
  ].join(`
28685
28689
  `);
28686
28690
  }
28691
+ function yamlHeader(host) {
28692
+ return [
28693
+ GENERATED_YAML_MARKER,
28694
+ `${OWNER_YAML_MARKER_PREFIX}host=${host}`,
28695
+ ""
28696
+ ].join(`
28697
+ `);
28698
+ }
28699
+ function hashHeader(host) {
28700
+ return yamlHeader(host);
28701
+ }
28687
28702
  function markdown(data, body) {
28688
28703
  return `${import_gray_matter.default.stringify(body.trimEnd(), data).trimEnd()}
28689
28704
  `;
@@ -28699,6 +28714,9 @@ function profileEntries(config2) {
28699
28714
  }
28700
28715
  return [...profileIds].sort((a, b) => a.localeCompare(b)).map((id) => [id, config2.profiles[id]]);
28701
28716
  }
28717
+ function nativeProfileEntries(host, config2) {
28718
+ return profileEntries(config2).filter(([_, profile]) => canRunNatively(host, config2, profile));
28719
+ }
28702
28720
  function hasAgentWorkflowNodes(config2) {
28703
28721
  return compileWorkflowPlan(config2).topologicalOrder.some((node) => node.kind === "agent");
28704
28722
  }
@@ -28726,6 +28744,7 @@ function workflowSummary(config2) {
28726
28744
  `- ${node.id}`,
28727
28745
  `kind=${node.kind}`,
28728
28746
  node.profile ? `profile=${node.profile}` : "",
28747
+ runnerForNode(config2, node.profile) ? `runner=${runnerForNode(config2, node.profile)}` : "",
28729
28748
  node.needs.length ? `needs=${node.needs.join(",")}` : "needs=none"
28730
28749
  ].filter(Boolean);
28731
28750
  return parts.join(" ");
@@ -28733,6 +28752,62 @@ function workflowSummary(config2) {
28733
28752
  ].join(`
28734
28753
  `);
28735
28754
  }
28755
+ function runnerForNode(config2, profileId) {
28756
+ return profileId ? config2.profiles[profileId]?.runner : undefined;
28757
+ }
28758
+ function resolvedHostModel(config2, host, profile) {
28759
+ const runner = config2.runners[profile.runner];
28760
+ return profile.host_models?.[host] ?? runner?.host_models?.[host] ?? profile.model ?? runner?.model;
28761
+ }
28762
+ function canRunNatively(host, config2, profile) {
28763
+ if (profile.runner === host) {
28764
+ return true;
28765
+ }
28766
+ return host === "opencode" && isModelRunner(profile.runner) && resolvedHostModel(config2, host, profile) !== undefined;
28767
+ }
28768
+ function isModelRunner(runnerId) {
28769
+ return COMMAND_HOSTS.some((host) => host === runnerId);
28770
+ }
28771
+ function runnerCliLabel(runnerId) {
28772
+ return `${runnerId} CLI`;
28773
+ }
28774
+ function dispatchSummary(host, config2) {
28775
+ const plan = compileWorkflowPlan(config2);
28776
+ const lines = plan.topologicalOrder.flatMap((node) => {
28777
+ if (!(node.kind === "agent" && node.profile)) {
28778
+ return [];
28779
+ }
28780
+ const profile = config2.profiles[node.profile];
28781
+ if (!profile) {
28782
+ return [];
28783
+ }
28784
+ return [dispatchLineForAgent(host, config2, node.id, node.profile, profile)];
28785
+ });
28786
+ return ["Node dispatch:", ...lines.length > 0 ? lines : ["- none"]].join(`
28787
+ `);
28788
+ }
28789
+ function dispatchLineForAgent(host, config2, nodeId, profileId, profile) {
28790
+ if (profile.runner === host) {
28791
+ return sameHostDispatchLine(host, nodeId, profileId, profile.runner);
28792
+ }
28793
+ if (host === "opencode" && isModelRunner(profile.runner)) {
28794
+ const model = resolvedHostModel(config2, host, profile);
28795
+ if (model) {
28796
+ return `- ${nodeId}: OpenCode native subagent profile=${profileId} model=${model} runner=${profile.runner}`;
28797
+ }
28798
+ }
28799
+ return `- ${nodeId}: ${runnerCliLabel(profile.runner)} profile=${profileId} runner=${profile.runner}`;
28800
+ }
28801
+ function sameHostDispatchLine(host, nodeId, profileId, runnerId) {
28802
+ const labels = {
28803
+ claude: `Claude native subagent subagent_type=${profileId}`,
28804
+ codex: `Codex native worker subagent profile=${profileId} agent_type=worker`,
28805
+ kimi: `Kimi native Agent subagent subagent_type=${profileId}`,
28806
+ opencode: `OpenCode native subagent subagent_type=${profileId}`,
28807
+ pi: `Pi native subagent chain profile=${profileId}`
28808
+ };
28809
+ return `- ${nodeId}: ${labels[host]} runner=${runnerId}`;
28810
+ }
28736
28811
  function entrypointSummary(config2) {
28737
28812
  const entries = Object.entries(config2.entrypoints);
28738
28813
  if (entries.length === 0) {
@@ -28772,9 +28847,36 @@ function nativeDelegationInstruction(host, config2) {
28772
28847
  return;
28773
28848
  }
28774
28849
  if (host === "opencode") {
28775
- return "Use OpenCode's task tool to delegate each agent workflow node to its configured profile. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28850
+ return "Dispatch each agent workflow node by its runner. For lines marked `OpenCode native subagent`, use OpenCode's task tool with `subagent_type` exactly equal to the configured profile id. For cross-runner nodes with an explicit resolved OpenCode model, use the generated OpenCode native subagent. For lines marked CLI, invoke that runner's CLI directly. Do not substitute a default subagent. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28851
+ }
28852
+ if (host === "codex") {
28853
+ return 'Dispatch each agent workflow node by its runner. For lines marked `Codex native worker subagent`, call `spawn_agent` with `agent_type: "worker"` and `fork_context: false`, then pass a node prompt containing the task, workflow id, node id, profile id, runner id, configured profile instructions, grants, and dependency outputs. Codex `exec` exposes native built-in agent types (`default`, `explorer`, `worker`) rather than arbitrary generated project profile ids, so do not call `spawn_agent` with the profile id as `agent_type`. Do not spawn the default agent for configured Codex nodes. For lines marked CLI, invoke that runner\'s CLI directly. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.';
28854
+ }
28855
+ if (host === "claude") {
28856
+ return "Dispatch each agent workflow node by its runner. For lines marked `Claude native subagent`, use Claude's Task tool with `subagent_type` exactly equal to the configured profile id. For lines marked CLI, invoke that runner's CLI directly. Do not substitute a default subagent. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28776
28857
  }
28777
- return "Use this host's native subagent mechanism for agent workflow nodes. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28858
+ if (host === "kimi") {
28859
+ return "Dispatch each agent workflow node by its runner. For lines marked `Kimi native Agent subagent`, use Kimi's Agent tool with `subagent_type` exactly equal to the configured profile id when the current Kimi root agent exposes that subagent type. If that native subagent type is not available, invoke Kimi CLI with the generated `.kimi/agents/<profile>.yaml` agent spec. For non-Kimi nodes, invoke that runner's CLI directly. Do not substitute a default subagent. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28860
+ }
28861
+ if (host === "pi") {
28862
+ return "Dispatch each agent workflow node by its runner. Use Pi's native subagent chain only when every agent node is a Pi runner node and the Pi subagent commands are available. If Pi native subagent commands are unavailable, invoke each configured runner through its CLI directly. For mixed-runner workflows, invoke each configured runner through its CLI directly. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28863
+ }
28864
+ return "Dispatch each agent workflow node by its runner. Use this host's native subagent mechanism only when it can actually run the requested runner/model. For cross-runner nodes that cannot be represented natively, invoke that runner's CLI directly. Do not use instruction-only translation. Do not invoke package scripts or the pipeline CLI to run this workflow.";
28865
+ }
28866
+ function cliDispatchInstructions(config2) {
28867
+ if (!hasAgentWorkflowNodes(config2)) {
28868
+ return;
28869
+ }
28870
+ return [
28871
+ "For CLI-dispatched nodes, construct the node prompt with the task, workflow id, node id, profile id, runner id, and dependency outputs.",
28872
+ "CLI dispatch command shapes:",
28873
+ "- codex: `codex exec --json -C <repo-root> --sandbox <mode> --config 'approval_policy=\"never\"' --skip-git-repo-check <node prompt>`",
28874
+ "- kimi: `kimi --print --agent-file .kimi/agents/<profile>.yaml --work-dir <repo-root> --final-message-only --prompt <node prompt>`",
28875
+ "- opencode: `opencode run --agent <profile> --format json --dir <repo-root> <node prompt>`",
28876
+ "- claude: `claude --print -p <node prompt>`",
28877
+ "- pi: `pi --print --no-session <node prompt>`"
28878
+ ].join(`
28879
+ `);
28778
28880
  }
28779
28881
  function compactLines(lines) {
28780
28882
  return lines.filter((line) => line !== undefined);
@@ -28805,10 +28907,14 @@ function claudeDefinitions(config2) {
28805
28907
  "",
28806
28908
  workflowSummary(config2),
28807
28909
  "",
28910
+ dispatchSummary("claude", config2),
28911
+ "",
28808
28912
  orchestratorBlock(config2),
28809
28913
  "",
28810
28914
  nativeDelegationInstruction("claude", config2),
28811
28915
  "",
28916
+ cliDispatchInstructions(config2),
28917
+ "",
28812
28918
  `Delegate work only to configured profiles: ${profileNames(config2)}.`
28813
28919
  ]).join(`
28814
28920
  `)),
@@ -28816,7 +28922,7 @@ function claudeDefinitions(config2) {
28816
28922
  invocation: "/pipe <task description>",
28817
28923
  path: ".claude/commands/pipe.md"
28818
28924
  },
28819
- ...profileEntries(config2).map(([id, profile]) => ({
28925
+ ...nativeProfileEntries("claude", config2).map(([id, profile]) => ({
28820
28926
  content: markdown({
28821
28927
  description: profile.description ?? id,
28822
28928
  name: id,
@@ -28863,10 +28969,14 @@ function opencodeDefinitions(config2) {
28863
28969
  "",
28864
28970
  workflowSummary(config2),
28865
28971
  "",
28972
+ dispatchSummary("opencode", config2),
28973
+ "",
28866
28974
  orchestratorBlock(config2),
28867
28975
  "",
28868
28976
  nativeDelegationInstruction("opencode", config2),
28869
28977
  "",
28978
+ cliDispatchInstructions(config2),
28979
+ "",
28870
28980
  `Delegate work only to configured profiles: ${profileNames(config2)}.`
28871
28981
  ]).join(`
28872
28982
  `)),
@@ -28886,17 +28996,22 @@ function opencodeDefinitions(config2) {
28886
28996
  "",
28887
28997
  orchestratorBlock(config2),
28888
28998
  "",
28889
- nativeDelegationInstruction("opencode", config2)
28999
+ dispatchSummary("opencode", config2),
29000
+ "",
29001
+ nativeDelegationInstruction("opencode", config2),
29002
+ "",
29003
+ cliDispatchInstructions(config2)
28890
29004
  ]).join(`
28891
29005
  `)),
28892
29006
  host: "opencode",
28893
29007
  invocation: "/pipe <task description>",
28894
29008
  path: ".opencode/agents/pipeline-orchestrator.md"
28895
29009
  },
28896
- ...profileEntries(config2).map(([id, profile]) => ({
29010
+ ...nativeProfileEntries("opencode", config2).map(([id, profile]) => ({
28897
29011
  content: markdown({
28898
29012
  description: profile.description ?? id,
28899
29013
  mode: "subagent",
29014
+ ...opencodeModelProjection(config2, profile),
28900
29015
  permission: opencodePermission(profile)
28901
29016
  }, [
28902
29017
  header("opencode").trimEnd(),
@@ -28928,10 +29043,14 @@ function codexDefinitions(config2) {
28928
29043
  "",
28929
29044
  workflowSummary(config2),
28930
29045
  "",
29046
+ dispatchSummary("codex", config2),
29047
+ "",
28931
29048
  orchestratorBlock(config2),
28932
29049
  "",
28933
29050
  nativeDelegationInstruction("codex", config2),
28934
29051
  "",
29052
+ cliDispatchInstructions(config2),
29053
+ "",
28935
29054
  `Use separate configured profiles: ${profileNames(config2)}.`
28936
29055
  ]).join(`
28937
29056
  `)),
@@ -28939,8 +29058,8 @@ function codexDefinitions(config2) {
28939
29058
  invocation: "$pipe <task description>",
28940
29059
  path: ".agents/skills/pipe/SKILL.md"
28941
29060
  },
28942
- ...profileEntries(config2).map(([id, profile]) => ({
28943
- content: `${stringify({
29061
+ ...nativeProfileEntries("codex", config2).map(([id, profile]) => ({
29062
+ content: `${hashHeader("codex")}${stringify({
28944
29063
  description: profile.description ?? id,
28945
29064
  developer_instructions: [
28946
29065
  profile.description ?? id,
@@ -28963,39 +29082,164 @@ function kimiDefinitions(config2) {
28963
29082
  return [
28964
29083
  {
28965
29084
  content: markdown({
29085
+ name: "pipe",
28966
29086
  description: "Run the configured pipeline workflow with Kimi agents"
28967
29087
  }, compactLines([
28968
29088
  header("kimi").trimEnd(),
28969
29089
  "",
28970
29090
  workflowSummary(config2),
28971
29091
  "",
29092
+ dispatchSummary("kimi", config2),
29093
+ "",
28972
29094
  orchestratorBlock(config2),
28973
29095
  "",
28974
29096
  nativeDelegationInstruction("kimi", config2),
28975
29097
  "",
29098
+ cliDispatchInstructions(config2),
29099
+ "",
28976
29100
  `Use separate configured profiles: ${profileNames(config2)}.`
28977
29101
  ]).join(`
28978
29102
  `)),
28979
29103
  host: "kimi",
28980
- invocation: "/pipe <task description>",
28981
- path: ".kimi/commands/pipe.md"
29104
+ invocation: "/skill:pipe <task description>",
29105
+ path: ".kimi/skills/pipe/SKILL.md"
28982
29106
  },
28983
- ...profileEntries(config2).map(([id, profile]) => ({
28984
- content: markdown({ description: profile.description ?? id, name: id }, [
29107
+ ...kimiOrchestratorAgentDefinitions(config2),
29108
+ ...nativeProfileEntries("kimi", config2).flatMap(([id, profile]) => kimiAgentDefinitions(id, profile))
29109
+ ];
29110
+ }
29111
+ function opencodeModelProjection(config2, profile) {
29112
+ const model = resolvedHostModel(config2, "opencode", profile);
29113
+ return model ? { model } : {};
29114
+ }
29115
+ function kimiAgentDefinitions(id, profile) {
29116
+ const agentPath = `.kimi/agents/${id}.yaml`;
29117
+ const promptPath = `.kimi/agents/${id}.prompt.md`;
29118
+ const promptDefinitions = profile.instructions.inline ? [
29119
+ {
29120
+ content: [
28985
29121
  header("kimi").trimEnd(),
28986
29122
  "",
28987
- profile.description ?? id,
29123
+ profile.instructions.inline,
29124
+ ""
29125
+ ].join(`
29126
+ `),
29127
+ host: "kimi",
29128
+ invocation: "/skill:pipe <task description>",
29129
+ path: promptPath
29130
+ }
29131
+ ] : [];
29132
+ const systemPromptPath = profile.instructions.path ? relative(dirname(agentPath), profile.instructions.path).replaceAll("\\", "/") : `./${id}.prompt.md`;
29133
+ return [
29134
+ {
29135
+ content: [
29136
+ yamlHeader("kimi").trimEnd(),
28988
29137
  "",
28989
- "Configured grants:",
28990
- grants(profile),
29138
+ $stringify({
29139
+ version: 1,
29140
+ agent: {
29141
+ allowed_tools: kimiAllowedTools(profile),
29142
+ extend: "default",
29143
+ name: id,
29144
+ system_prompt_path: systemPromptPath
29145
+ }
29146
+ }).trimEnd(),
29147
+ ""
29148
+ ].join(`
29149
+ `),
29150
+ host: "kimi",
29151
+ invocation: "/skill:pipe <task description>",
29152
+ path: agentPath
29153
+ },
29154
+ ...promptDefinitions
29155
+ ];
29156
+ }
29157
+ function kimiOrchestratorAgentDefinitions(config2) {
29158
+ const agentPath = ".kimi/agents/pipeline-orchestrator.yaml";
29159
+ const promptPath = ".kimi/agents/pipeline-orchestrator.prompt.md";
29160
+ const nativeKimiProfiles = nativeProfileEntries("kimi", config2);
29161
+ const subagents = Object.fromEntries(nativeKimiProfiles.map(([id, profile]) => [
29162
+ id,
29163
+ {
29164
+ description: profile.description ?? id,
29165
+ path: `./${id}.yaml`
29166
+ }
29167
+ ]));
29168
+ const plan = compileWorkflowPlan(config2);
29169
+ const hasKimiNodes = plan.topologicalOrder.some((node) => {
29170
+ if (!(node.kind === "agent" && node.profile)) {
29171
+ return false;
29172
+ }
29173
+ return config2.profiles[node.profile]?.runner === "kimi";
29174
+ });
29175
+ const hasNonKimiNodes = plan.topologicalOrder.some((node) => {
29176
+ if (!(node.kind === "agent" && node.profile)) {
29177
+ return false;
29178
+ }
29179
+ return config2.profiles[node.profile]?.runner !== "kimi";
29180
+ });
29181
+ const allowedTools = [
29182
+ ...hasKimiNodes ? ["kimi_cli.tools.agent:Agent"] : [],
29183
+ ...hasNonKimiNodes ? ["kimi_cli.tools.shell:Shell"] : []
29184
+ ];
29185
+ return [
29186
+ {
29187
+ content: [
29188
+ yamlHeader("kimi").trimEnd(),
28991
29189
  "",
28992
- instructionsPointer(profile)
29190
+ $stringify({
29191
+ version: 1,
29192
+ agent: {
29193
+ allowed_tools: allowedTools,
29194
+ extend: "default",
29195
+ name: "pipeline-orchestrator",
29196
+ ...Object.keys(subagents).length > 0 ? { subagents } : {},
29197
+ system_prompt_path: "./pipeline-orchestrator.prompt.md"
29198
+ }
29199
+ }).trimEnd(),
29200
+ ""
28993
29201
  ].join(`
28994
- `)),
29202
+ `),
28995
29203
  host: "kimi",
28996
- invocation: "/pipe <task description>",
28997
- path: `.kimi/agents/${id}.md`
28998
- }))
29204
+ invocation: 'kimi --agent-file .kimi/agents/pipeline-orchestrator.yaml --work-dir <repo-root> --prompt "/skill:pipe <task description>"',
29205
+ path: agentPath
29206
+ },
29207
+ {
29208
+ content: compactLines([
29209
+ header("kimi").trimEnd(),
29210
+ "",
29211
+ workflowSummary(config2),
29212
+ "",
29213
+ dispatchSummary("kimi", config2),
29214
+ "",
29215
+ orchestratorBlock(config2),
29216
+ "",
29217
+ nativeDelegationInstruction("kimi", config2),
29218
+ "",
29219
+ cliDispatchInstructions(config2),
29220
+ "",
29221
+ "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."
29222
+ ]).join(`
29223
+ `),
29224
+ host: "kimi",
29225
+ invocation: 'kimi --agent-file .kimi/agents/pipeline-orchestrator.yaml --work-dir <repo-root> --prompt "/skill:pipe <task description>"',
29226
+ path: promptPath
29227
+ }
29228
+ ];
29229
+ }
29230
+ function kimiAllowedTools(profile) {
29231
+ const mapped = new Map([
29232
+ ["bash", "kimi_cli.tools.shell:Shell"],
29233
+ ["edit", "kimi_cli.tools.file:StrReplaceFile"],
29234
+ ["glob", "kimi_cli.tools.file:Glob"],
29235
+ ["grep", "kimi_cli.tools.file:Grep"],
29236
+ ["list", "kimi_cli.tools.file:Glob"],
29237
+ ["read", "kimi_cli.tools.file:ReadFile"],
29238
+ ["task", "kimi_cli.tools.agent:Agent"],
29239
+ ["write", "kimi_cli.tools.file:WriteFile"]
29240
+ ]);
29241
+ return [
29242
+ ...new Set((profile.tools ?? []).flatMap((tool) => mapped.get(tool) ?? []))
28999
29243
  ];
29000
29244
  }
29001
29245
  function piDefinitions(config2) {
@@ -29009,9 +29253,13 @@ function piDefinitions(config2) {
29009
29253
  "",
29010
29254
  workflowSummary(config2),
29011
29255
  "",
29256
+ dispatchSummary("pi", config2),
29257
+ "",
29012
29258
  orchestratorBlock(config2),
29013
29259
  "",
29014
- nativeDelegationInstruction("pi", config2)
29260
+ nativeDelegationInstruction("pi", config2),
29261
+ "",
29262
+ cliDispatchInstructions(config2)
29015
29263
  ]).join(`
29016
29264
  `)),
29017
29265
  host: "pi",
@@ -29022,58 +29270,12 @@ function piDefinitions(config2) {
29022
29270
  content: [
29023
29271
  tsHeader("pi").trimEnd(),
29024
29272
  "",
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
- " });",
29273
+ "// Pi exposes project prompt templates as slash commands.",
29274
+ "// The real /pipe command is .pi/prompts/pipe.md.",
29275
+ "// This generated shim intentionally registers no command so it cannot",
29276
+ "// intercept /pipe in print mode before the prompt-template expansion runs.",
29277
+ "export default function pipelineWorkNext(): void {",
29278
+ " return;",
29077
29279
  "}",
29078
29280
  ""
29079
29281
  ].join(`
@@ -29084,30 +29286,6 @@ function piDefinitions(config2) {
29084
29286
  }
29085
29287
  ];
29086
29288
  }
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
29289
  function instructionsPointer(actor) {
29112
29290
  if (actor.instructions.path) {
29113
29291
  return `Instructions: ${actor.instructions.path}`;
@@ -29126,6 +29304,67 @@ function definitionsFor(host, config2) {
29126
29304
  const hosts = host === "all" ? COMMAND_HOSTS : [host];
29127
29305
  return hosts.flatMap((name) => definitions[name]());
29128
29306
  }
29307
+ function selectedHosts(host) {
29308
+ return host === "all" ? [...COMMAND_HOSTS] : [host];
29309
+ }
29310
+ var GENERATED_RESOURCE_ROOTS = {
29311
+ claude: [".claude/commands", ".claude/agents"],
29312
+ codex: [".agents/skills", ".codex/agents"],
29313
+ kimi: [".kimi/skills", ".kimi/agents", ".kimi/commands"],
29314
+ opencode: [".opencode/commands", ".opencode/agents"],
29315
+ pi: [".pi/prompts", ".pi/extensions"]
29316
+ };
29317
+ async function listFiles(root) {
29318
+ if (!existsSync2(root)) {
29319
+ return [];
29320
+ }
29321
+ const entries = await readdir(root, { withFileTypes: true });
29322
+ const files = await Promise.all(entries.map((entry) => {
29323
+ const path = join2(root, entry.name);
29324
+ if (entry.isDirectory()) {
29325
+ return listFiles(path);
29326
+ }
29327
+ return [path];
29328
+ }));
29329
+ return files.flat();
29330
+ }
29331
+ function generatedHostFor(content) {
29332
+ 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}`));
29333
+ }
29334
+ async function obsoleteGeneratedItems(cwd, host, wantedPaths) {
29335
+ const hosts = new Set(selectedHosts(host));
29336
+ const roots = selectedHosts(host).flatMap((selectedHost) => GENERATED_RESOURCE_ROOTS[selectedHost]);
29337
+ const files = await Promise.all(roots.map((root) => listFiles(join2(cwd, root))));
29338
+ return files.flat().flatMap((absolutePath) => {
29339
+ const content = readFileSync2(absolutePath, "utf8");
29340
+ const generatedHost = generatedHostFor(content);
29341
+ if (!(generatedHost && hosts.has(generatedHost))) {
29342
+ return [];
29343
+ }
29344
+ const path = relative(cwd, absolutePath).replaceAll("\\", "/");
29345
+ if (wantedPaths.has(path)) {
29346
+ return [];
29347
+ }
29348
+ return [
29349
+ {
29350
+ action: "delete",
29351
+ host: generatedHost,
29352
+ invocation: invocationForHost(generatedHost),
29353
+ path
29354
+ }
29355
+ ];
29356
+ }).sort((a, b) => a.path.localeCompare(b.path));
29357
+ }
29358
+ function invocationForHost(host) {
29359
+ const invocations = {
29360
+ claude: "/pipe <task description>",
29361
+ codex: "$pipe <task description>",
29362
+ kimi: "/skill:pipe <task description>",
29363
+ opencode: "/pipe <task description>",
29364
+ pi: "/pipe <task description>"
29365
+ };
29366
+ return invocations[host];
29367
+ }
29129
29368
  function actionFor(path, content, force) {
29130
29369
  if (!existsSync2(path)) {
29131
29370
  return "create";
@@ -29134,7 +29373,7 @@ function actionFor(path, content, force) {
29134
29373
  if (current === content) {
29135
29374
  return "unchanged";
29136
29375
  }
29137
- if (!(current.includes(GENERATED_MARKER) || current.includes(GENERATED_TS_MARKER) || force)) {
29376
+ if (!(current.includes(GENERATED_MARKER) || current.includes(GENERATED_TS_MARKER) || current.includes(GENERATED_YAML_MARKER) || force)) {
29138
29377
  return "conflict";
29139
29378
  }
29140
29379
  return "update";
@@ -29144,7 +29383,9 @@ async function installCommands(options2 = {}) {
29144
29383
  const host = options2.host ?? "all";
29145
29384
  const config2 = loadPipelineConfig(cwd);
29146
29385
  const items = [];
29147
- for (const definition of definitionsFor(host, config2)) {
29386
+ const definitions = definitionsFor(host, config2);
29387
+ const wantedPaths = new Set(definitions.map((definition) => definition.path));
29388
+ for (const definition of definitions) {
29148
29389
  const target = join2(cwd, definition.path);
29149
29390
  const action = actionFor(target, definition.content, Boolean(options2.force));
29150
29391
  items.push({
@@ -29162,6 +29403,13 @@ async function installCommands(options2 = {}) {
29162
29403
  await mkdir(dirname(target), { recursive: true });
29163
29404
  await writeFile(target, definition.content);
29164
29405
  }
29406
+ const obsoleteItems = await obsoleteGeneratedItems(cwd, host, wantedPaths);
29407
+ items.push(...obsoleteItems);
29408
+ if (!(options2.check || options2.dryRun)) {
29409
+ for (const item of obsoleteItems) {
29410
+ await rm(join2(cwd, item.path), { force: true });
29411
+ }
29412
+ }
29165
29413
  if (!options2.dryRun && items.some((item) => item.action === "conflict")) {
29166
29414
  throw new Error([
29167
29415
  "Refusing to overwrite manually edited command files.",
@@ -29619,7 +29867,7 @@ function hostResourceInput(host) {
29619
29867
  return [
29620
29868
  `# ${host} Resource Input`,
29621
29869
  "",
29622
- "This file is scaffolded input for host-specific resource projection.",
29870
+ "This file is scaffolded input for host-specific generated resources.",
29623
29871
  "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
29872
  ""
29625
29873
  ].join(`
@@ -36683,8 +36931,8 @@ async function runTests(worktreePath, signal) {
36683
36931
  }
36684
36932
  try {
36685
36933
  const result = await execa(projectCommand.command, projectCommand.args, {
36934
+ cancelSignal: signal,
36686
36935
  cwd: worktreePath,
36687
- signal,
36688
36936
  shell: projectCommand.shell
36689
36937
  });
36690
36938
  const output = [result.stdout, result.stderr].filter(Boolean).join(`
@@ -36708,8 +36956,8 @@ async function runTypecheck(worktreePath, signal) {
36708
36956
  }
36709
36957
  try {
36710
36958
  const result = await execa(projectCommand.command, projectCommand.args, {
36959
+ cancelSignal: signal,
36711
36960
  cwd: worktreePath,
36712
- signal,
36713
36961
  shell: projectCommand.shell
36714
36962
  });
36715
36963
  const output = [result.stdout, result.stderr].filter(Boolean).join(`
@@ -36741,8 +36989,8 @@ function parseJscpdOutput(output) {
36741
36989
  async function runJscpd(worktreePath, signal) {
36742
36990
  try {
36743
36991
  const result = await execa("bunx", ["jscpd", "--min-tokens", "50", "--reporters", "json", "."], {
36744
- cwd: worktreePath,
36745
- signal
36992
+ cancelSignal: signal,
36993
+ cwd: worktreePath
36746
36994
  });
36747
36995
  return parseJscpdOutput(result.stdout ?? "");
36748
36996
  } catch (err) {
@@ -36912,8 +37160,6 @@ function createActorLaunchPlan(config2, input, actor, runnerId) {
36912
37160
  function piArgv(prompt, config2, actor, worktreePath = process.cwd(), runner) {
36913
37161
  return [
36914
37162
  "--print",
36915
- "--mode",
36916
- "json",
36917
37163
  ...optionalModelArgs("pi", runner, actor),
36918
37164
  ...piToolArgs(actor?.tools ?? []),
36919
37165
  ...skillArgsFor("pi", config2, actor, worktreePath),
@@ -37059,9 +37305,9 @@ function renderArgv(args, prompt, cwd) {
37059
37305
  async function runLaunchPlan(plan, options2 = {}) {
37060
37306
  try {
37061
37307
  const result = await execa(plan.command, plan.args, {
37308
+ cancelSignal: options2.signal,
37062
37309
  cwd: plan.cwd,
37063
37310
  env: plan.env,
37064
- signal: options2.signal,
37065
37311
  stdin: "ignore",
37066
37312
  timeout: plan.timeoutMs
37067
37313
  });
@@ -37743,12 +37989,12 @@ async function executeCommand(command, context, options2 = {}) {
37743
37989
  }
37744
37990
  try {
37745
37991
  const result = await execa(command[0], command.slice(1), {
37992
+ cancelSignal: context.signal,
37746
37993
  cwd: context.worktreePath,
37747
37994
  ...options2.env ? { env: options2.env } : {},
37748
37995
  ...options2.extendEnv === false ? { extendEnv: false } : {},
37749
37996
  ...options2.input ? { input: options2.input } : {},
37750
37997
  ...options2.outputLimitBytes ? { maxBuffer: options2.outputLimitBytes } : {},
37751
- signal: context.signal,
37752
37998
  timeout: options2.timeout
37753
37999
  });
37754
38000
  const output = limitOutput([result.stdout, result.stderr].filter(Boolean).join(`
@@ -38642,7 +38888,10 @@ function createCliProgram() {
38642
38888
  console.log(formatPipelineInitResult(result));
38643
38889
  });
38644
38890
  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(flags);
38891
+ const result = await installCommands({
38892
+ ...flags,
38893
+ cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd()
38894
+ });
38646
38895
  console.log(formatInstallCommandsResult(result));
38647
38896
  });
38648
38897
  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;
@@ -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(),
@@ -29404,8 +29406,8 @@ async function runTests(worktreePath, signal) {
29404
29406
  }
29405
29407
  try {
29406
29408
  const result = await execa(projectCommand.command, projectCommand.args, {
29409
+ cancelSignal: signal,
29407
29410
  cwd: worktreePath,
29408
- signal,
29409
29411
  shell: projectCommand.shell
29410
29412
  });
29411
29413
  const output = [result.stdout, result.stderr].filter(Boolean).join(`
@@ -29429,8 +29431,8 @@ async function runTypecheck(worktreePath, signal) {
29429
29431
  }
29430
29432
  try {
29431
29433
  const result = await execa(projectCommand.command, projectCommand.args, {
29434
+ cancelSignal: signal,
29432
29435
  cwd: worktreePath,
29433
- signal,
29434
29436
  shell: projectCommand.shell
29435
29437
  });
29436
29438
  const output = [result.stdout, result.stderr].filter(Boolean).join(`
@@ -29462,8 +29464,8 @@ function parseJscpdOutput(output) {
29462
29464
  async function runJscpd(worktreePath, signal) {
29463
29465
  try {
29464
29466
  const result = await execa("bunx", ["jscpd", "--min-tokens", "50", "--reporters", "json", "."], {
29465
- cwd: worktreePath,
29466
- signal
29467
+ cancelSignal: signal,
29468
+ cwd: worktreePath
29467
29469
  });
29468
29470
  return parseJscpdOutput(result.stdout ?? "");
29469
29471
  } catch (err) {
@@ -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),
@@ -29774,9 +29774,9 @@ function renderArgv(args, prompt, cwd) {
29774
29774
  async function runLaunchPlan(plan, options = {}) {
29775
29775
  try {
29776
29776
  const result = await execa(plan.command, plan.args, {
29777
+ cancelSignal: options.signal,
29777
29778
  cwd: plan.cwd,
29778
29779
  env: plan.env,
29779
- signal: options.signal,
29780
29780
  stdin: "ignore",
29781
29781
  timeout: plan.timeoutMs
29782
29782
  });
@@ -30680,12 +30680,12 @@ async function executeCommand(command, context, options = {}) {
30680
30680
  }
30681
30681
  try {
30682
30682
  const result = await execa(command[0], command.slice(1), {
30683
+ cancelSignal: context.signal,
30683
30684
  cwd: context.worktreePath,
30684
30685
  ...options.env ? { env: options.env } : {},
30685
30686
  ...options.extendEnv === false ? { extendEnv: false } : {},
30686
30687
  ...options.input ? { input: options.input } : {},
30687
30688
  ...options.outputLimitBytes ? { maxBuffer: options.outputLimitBytes } : {},
30688
- signal: context.signal,
30689
30689
  timeout: options.timeout
30690
30690
  });
30691
30691
  const output = limitOutput([result.stdout, result.stderr].filter(Boolean).join(`
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),
@@ -7624,9 +7620,9 @@ function renderArgv(args, prompt, cwd) {
7624
7620
  async function runLaunchPlan(plan, options = {}) {
7625
7621
  try {
7626
7622
  const result = await execa(plan.command, plan.args, {
7623
+ cancelSignal: options.signal,
7627
7624
  cwd: plan.cwd,
7628
7625
  env: plan.env,
7629
- signal: options.signal,
7630
7626
  stdin: "ignore",
7631
7627
  timeout: plan.timeoutMs
7632
7628
  });
@@ -189,14 +189,14 @@ timeouts, output limits, sanitized env, and explicit trust flags.
189
189
 
190
190
  ## Host Support Matrix
191
191
 
192
- | Runner | Native subagents | Rules | Skills | MCP | Outputs | Generated resources |
193
- | --- | --- | --- | --- | --- | --- | --- |
194
- | Claude | yes | yes | projected as text when declared | 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 | projected as text when declared | yes | text, JSON, JSONL, schema | command plus `.opencode/agents` |
197
- | Kimi | yes | yes | projected as text when declared | no | text, JSON | command plus `.kimi/agents` |
198
- | Pi | yes, with pi-subagents | yes | projected as text when declared | no | text, JSON | extension plus prompt |
199
- | command | no | no | no | no | declared by runner | subprocess argv |
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 error: install and enable `pi-subagents`; generated Pi
216
- resources check for its commands before sending a chain.
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 projections of `.pipeline/runners.yaml`,
4
- `.pipeline/profiles.yaml`, and `.pipeline/pipeline.yaml`. They do not shell out
5
- to a legacy command and do not maintain independent profile definitions.
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 | Generated resources | Invocation | Mechanical path |
17
- | --- | --- | --- | --- |
18
- | Claude Code | `.claude/commands/pipe.md`, `.claude/agents/*.md` | `/pipe <task>` | Project command delegates to configured Claude agents. |
19
- | Codex | `.agents/skills/pipe/SKILL.md`, `.codex/agents/*.toml` | `$pipe <task>` | Skill instructs Codex to use generated Codex agents. |
20
- | OpenCode | `.opencode/commands/pipe.md`, `.opencode/agents/*.md` | `/pipe <task>` | Project command runs a primary orchestrator and configured subagents. |
21
- | Kimi | `.kimi/commands/pipe.md`, `.kimi/agents/*.md` | `/pipe <task>` | Project command and agent specs mirror YAML grants. |
22
- | Pi | `.pi/extensions/pipe.ts`, `.pi/prompts/pipe.md` | `/pipe <task>` | Extension requires `pi-subagents` before sending a chain. |
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 projections share the same workflow plan. Multi-agent
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.
package/package.json CHANGED
@@ -73,7 +73,7 @@
73
73
  "prepack": "bun run build:cli"
74
74
  },
75
75
  "type": "module",
76
- "version": "1.5.0",
76
+ "version": "1.5.2",
77
77
  "description": "",
78
78
  "main": "index.js",
79
79
  "keywords": [],