pi-subagents 0.80.0 → 0.81.0

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/bundle/agents.js +18 -0
  3. package/bundle/background-work.js +16 -0
  4. package/bundle/capability-ceiling.js +22 -0
  5. package/bundle/child-tool-plan.js +11 -0
  6. package/bundle/chunk-26C3WLYQ.js +8388 -0
  7. package/bundle/chunk-2XYWN7QJ.js +128 -0
  8. package/bundle/chunk-2ZSNH26H.js +4164 -0
  9. package/bundle/chunk-3DC5KBVP.js +235 -0
  10. package/bundle/chunk-3IF2PGW7.js +380 -0
  11. package/bundle/chunk-4QFZZV3O.js +57 -0
  12. package/bundle/chunk-563BHAZ5.js +1365 -0
  13. package/bundle/chunk-5LFLVSTM.js +260 -0
  14. package/bundle/chunk-62OCY2S4.js +14 -0
  15. package/bundle/chunk-74RT5YQE.js +326 -0
  16. package/bundle/chunk-7F6RKT6W.js +132 -0
  17. package/bundle/chunk-7J4ZXMY7.js +18 -0
  18. package/bundle/chunk-ARRKBB2B.js +179 -0
  19. package/bundle/chunk-BJN5LUQY.js +458 -0
  20. package/bundle/chunk-BPS2AGBZ.js +5421 -0
  21. package/bundle/chunk-CEKS3TFO.js +1337 -0
  22. package/bundle/chunk-CTPAE36Y.js +94 -0
  23. package/bundle/chunk-D4EHT5SJ.js +14 -0
  24. package/bundle/chunk-EYNJ6VL6.js +526 -0
  25. package/bundle/chunk-G5DKFLLI.js +1185 -0
  26. package/bundle/chunk-KSWQVGDW.js +1027 -0
  27. package/bundle/chunk-L5ZHE4L4.js +946 -0
  28. package/bundle/chunk-LSRUTHT7.js +57 -0
  29. package/bundle/chunk-N2BN7BF5.js +159 -0
  30. package/bundle/chunk-NF7IEKBH.js +490 -0
  31. package/bundle/chunk-NIXWDZZV.js +9 -0
  32. package/bundle/chunk-NNLASTQE.js +4082 -0
  33. package/bundle/chunk-TUWLXTAM.js +935 -0
  34. package/bundle/chunk-UKG4V45O.js +228 -0
  35. package/bundle/chunk-V2JWDB5U.js +753 -0
  36. package/bundle/chunk-V3BIUPLU.js +9163 -0
  37. package/bundle/chunk-V6MR5TOQ.js +270 -0
  38. package/bundle/chunk-VJHOELQP.js +8245 -0
  39. package/bundle/chunk-WSBNENFS.js +732 -0
  40. package/bundle/chunk-XMFLCKMQ.js +16664 -0
  41. package/bundle/chunk-YFCK7H2S.js +129 -0
  42. package/bundle/chunk-YYREO77V.js +137 -0
  43. package/bundle/chunk-Z7KVHQHL.js +173 -0
  44. package/bundle/chunk-ZBC44JJ3.js +8606 -0
  45. package/bundle/chunk-ZP3HODFH.js +175 -0
  46. package/bundle/control-channel.js +11 -0
  47. package/bundle/delegation.js +14 -0
  48. package/bundle/external-job-provider.js +20 -0
  49. package/bundle/external-runs.js +21 -0
  50. package/bundle/index.js +30 -0
  51. package/bundle/inspectors.js +8 -0
  52. package/bundle/intercom-bridge.js +11 -0
  53. package/bundle/preflight.js +425 -0
  54. package/bundle/project-panes.js +28 -0
  55. package/bundle/required-child-extensions.js +6 -0
  56. package/bundle/shared-types.js +8 -0
  57. package/bundle/workflow-resources.js +7 -0
  58. package/docs/extension-api.md +27 -0
  59. package/docs/observability.md +6 -2
  60. package/package.json +18 -18
  61. package/src/agents/frontmatter.d.ts +2 -0
  62. package/src/agents/frontmatter.js +9 -2
  63. package/src/api/delegation.d.ts +20 -0
  64. package/src/extension/index.js +27 -7
  65. package/src/runs/background/run-child-session.js +18 -0
  66. package/src/runs/background/stale-run-reconciler.js +2 -0
  67. package/src/runs/foreground/execution.js +39 -2
  68. package/src/runs/shared/structured-output.d.ts +1 -0
  69. package/src/runs/shared/structured-output.js +1 -0
  70. package/src/shared/accessible-dir.js +4 -1
  71. package/src/shared/provider-diagnostics.d.ts +9 -0
  72. package/src/shared/provider-diagnostics.js +75 -0
  73. package/src/shared/types.d.ts +3 -0
  74. package/src/slash/delegation-adapters.d.ts +3 -1
  75. package/src/slash/delegation-adapters.js +7 -0
  76. package/src/slash/prompt-template-bridge.js +3 -1
  77. package/src/slash/subagents-admin.d.ts +1 -0
  78. package/src/slash/subagents-admin.js +1 -1
  79. package/src/tui/fleet-layout.d.ts +20 -0
  80. package/src/tui/fleet-layout.js +17 -0
  81. package/src/tui/fleet-viewport.d.ts +12 -0
  82. package/src/tui/fleet-viewport.js +35 -0
  83. package/src/tui/fleet.d.ts +5 -6
  84. package/src/tui/fleet.js +69 -51
  85. package/src/workflows/structured-workflow-scripts.d.ts +1 -0
  86. package/src/workflows/structured-workflow-scripts.js +5 -4
  87. package/src/workflows/workflow-resources.js +2 -2
package/src/tui/fleet.js CHANGED
@@ -2,6 +2,8 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
4
4
  import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
5
+ import { fleetContains, fleetLayout } from "./fleet-layout.js";
6
+ import { FleetViewport } from "./fleet-viewport.js";
5
7
  import { snapshotExternalRuns } from "../api/external-runs.js";
6
8
  import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.js";
7
9
  import { formatDuration, formatModelThinking, formatTokens, formatTokenUsage, shortenPath } from "../shared/formatters.js";
@@ -746,11 +748,8 @@ export class SubagentFleetComponent {
746
748
  snapshot = { items: [] };
747
749
  selected = 0;
748
750
  selectedKey;
749
- detailScroll = 0;
750
- detailAutoFollow = true;
751
- detailLineCount = 0;
752
- detailViewportHeight = 8;
753
- bodyHeight = 8;
751
+ viewport;
752
+ renderedLayout;
754
753
  expandedTools = false;
755
754
  promptAuditOpen = false;
756
755
  promptAuditView = "authored";
@@ -773,6 +772,7 @@ export class SubagentFleetComponent {
773
772
  keybindings;
774
773
  constructor(tui, theme, state, done, options = {}) {
775
774
  this.tui = tui;
775
+ this.viewport = new FleetViewport(() => this.tui.requestRender());
776
776
  this.theme = theme;
777
777
  this.markdownTheme = options.markdownTheme ?? getMarkdownTheme();
778
778
  this.state = state;
@@ -820,11 +820,14 @@ export class SubagentFleetComponent {
820
820
  this.selectedKey = this.snapshot.items[this.selected]?.key;
821
821
  }
822
822
  moveSelection(delta) {
823
- if (this.snapshot.items.length === 0)
823
+ this.selectItem(Math.max(0, Math.min(this.snapshot.items.length - 1, this.selected + delta)));
824
+ }
825
+ selectItem(index) {
826
+ if (!this.snapshot.items[index])
824
827
  return;
825
- this.selected = Math.max(0, Math.min(this.snapshot.items.length - 1, this.selected + delta));
828
+ this.selected = index;
826
829
  this.selectedKey = this.snapshot.items[this.selected]?.key;
827
- this.detailAutoFollow = true;
830
+ this.viewport.follow();
828
831
  this.resetActionInput();
829
832
  this.tui.requestRender();
830
833
  }
@@ -862,8 +865,7 @@ export class SubagentFleetComponent {
862
865
  return;
863
866
  this.selected = this.snapshot.items.findIndex((item) => item.key === target.key);
864
867
  this.selectedKey = target.key;
865
- this.detailScroll = 0;
866
- this.detailAutoFollow = false;
868
+ this.viewport.resetTop();
867
869
  this.tui.requestRender();
868
870
  }
869
871
  resetActionInput() {
@@ -951,8 +953,7 @@ export class SubagentFleetComponent {
951
953
  setActionNotice(result) {
952
954
  this.actionNotice = result;
953
955
  this.resetActionInput();
954
- this.detailAutoFollow = false;
955
- this.detailScroll = 0;
956
+ this.viewport.resetTop();
956
957
  this.refresh();
957
958
  this.tui.requestRender();
958
959
  }
@@ -972,10 +973,35 @@ export class SubagentFleetComponent {
972
973
  });
973
974
  }
974
975
  scrollDetail(delta) {
975
- const maxScroll = Math.max(0, this.detailLineCount - this.detailViewportHeight);
976
- this.detailScroll = Math.max(0, Math.min(maxScroll, this.detailScroll + delta));
977
- this.detailAutoFollow = this.detailScroll >= maxScroll;
978
- this.tui.requestRender();
976
+ this.viewport.scroll.scrollBy(delta);
977
+ }
978
+ handleMouse(event) {
979
+ // Block pointer fallback as well as inspector actions while a text/confirmation modal owns input.
980
+ if (this.steerDraft !== undefined || this.redoGuidanceDraft !== undefined || this.stopConfirming || this.actionBusy) {
981
+ return { handled: true, render: false };
982
+ }
983
+ const layout = this.renderedLayout;
984
+ if (!layout || layout.width !== event.width || layout.rows !== (this.tui.terminal?.rows ?? 32)) {
985
+ // Never use hit regions from a different size; wheel must not escape to the transcript.
986
+ return event.type === "wheel" ? { handled: true, render: false } : undefined;
987
+ }
988
+ if (event.type === "wheel" && fleetContains(layout.output, event.x, event.y)) {
989
+ this.scrollDetail(event.wheelDelta ?? 0);
990
+ return { handled: true, render: false };
991
+ }
992
+ if (this.promptAuditOpen)
993
+ return { handled: true, render: false };
994
+ if (event.type === "click" && event.button === "left" && !event.shift && !event.alt && !event.ctrl
995
+ && fleetContains(layout.roster, event.x, event.y)) {
996
+ const key = layout.rosterKeys[event.y - layout.roster.y];
997
+ const index = key === undefined ? -1 : this.snapshot.items.findIndex((item) => item.key === key);
998
+ if (index < 0)
999
+ return undefined;
1000
+ this.selectItem(index);
1001
+ return { handled: true, render: false };
1002
+ }
1003
+ // Unowned wheel inside the overlay would otherwise scroll the transcript behind it.
1004
+ return event.type === "wheel" ? { handled: true, render: false } : undefined;
979
1005
  }
980
1006
  handleInput(data) {
981
1007
  if (this.promptAuditOpen && this.redoGuidanceDraft !== undefined) {
@@ -1012,7 +1038,7 @@ export class SubagentFleetComponent {
1012
1038
  if (this.promptAuditOpen) {
1013
1039
  if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
1014
1040
  this.promptAuditOpen = false;
1015
- this.detailAutoFollow = true;
1041
+ this.viewport.follow();
1016
1042
  this.tui.requestRender();
1017
1043
  return;
1018
1044
  }
@@ -1022,7 +1048,7 @@ export class SubagentFleetComponent {
1022
1048
  return this.movePromptSelection(-1);
1023
1049
  if (data === "1" || data === "2" || data === "3") {
1024
1050
  this.promptAuditView = data === "1" ? "authored" : data === "2" ? "runtime" : "effective";
1025
- this.detailScroll = 0;
1051
+ this.viewport.resetTop();
1026
1052
  this.tui.requestRender();
1027
1053
  return;
1028
1054
  }
@@ -1037,8 +1063,7 @@ export class SubagentFleetComponent {
1037
1063
  else {
1038
1064
  this.actionNotice = undefined;
1039
1065
  this.redoGuidanceDraft = "";
1040
- this.detailAutoFollow = false;
1041
- this.detailScroll = 0;
1066
+ this.viewport.resetTop();
1042
1067
  this.tui.requestRender();
1043
1068
  }
1044
1069
  return;
@@ -1061,9 +1086,9 @@ export class SubagentFleetComponent {
1061
1086
  if (matchesFleetAction(data, this.keybindings, "scrollDown"))
1062
1087
  return this.scrollDetail(1);
1063
1088
  if (matchesFleetAction(data, this.keybindings, "pageUp"))
1064
- return this.scrollDetail(-this.detailViewportHeight);
1089
+ return this.scrollDetail(-this.viewport.scroll.viewportHeight);
1065
1090
  if (matchesFleetAction(data, this.keybindings, "pageDown"))
1066
- return this.scrollDetail(this.detailViewportHeight);
1091
+ return this.scrollDetail(this.viewport.scroll.viewportHeight);
1067
1092
  return;
1068
1093
  }
1069
1094
  if (this.steerDraft !== undefined) {
@@ -1137,9 +1162,9 @@ export class SubagentFleetComponent {
1137
1162
  if (matchesFleetAction(data, this.keybindings, "selectLast"))
1138
1163
  return this.moveSelection(this.snapshot.items.length);
1139
1164
  if (matchesFleetAction(data, this.keybindings, "pageUp"))
1140
- return this.scrollDetail(-this.detailViewportHeight);
1165
+ return this.scrollDetail(-this.viewport.scroll.viewportHeight);
1141
1166
  if (matchesFleetAction(data, this.keybindings, "pageDown"))
1142
- return this.scrollDetail(this.detailViewportHeight);
1167
+ return this.scrollDetail(this.viewport.scroll.viewportHeight);
1143
1168
  if (matchesFleetAction(data, this.keybindings, "refresh")) {
1144
1169
  this.transcriptCache = undefined;
1145
1170
  this.refresh();
@@ -1152,8 +1177,7 @@ export class SubagentFleetComponent {
1152
1177
  else {
1153
1178
  this.promptAuditOpen = true;
1154
1179
  this.promptAuditView = "authored";
1155
- this.detailScroll = 0;
1156
- this.detailAutoFollow = false;
1180
+ this.viewport.resetTop();
1157
1181
  this.actionNotice = undefined;
1158
1182
  this.tui.requestRender();
1159
1183
  }
@@ -1166,8 +1190,7 @@ export class SubagentFleetComponent {
1166
1190
  else {
1167
1191
  this.actionNotice = undefined;
1168
1192
  this.steerDraft = "";
1169
- this.detailAutoFollow = false;
1170
- this.detailScroll = 0;
1193
+ this.viewport.resetTop();
1171
1194
  this.tui.requestRender();
1172
1195
  }
1173
1196
  return;
@@ -1179,8 +1202,7 @@ export class SubagentFleetComponent {
1179
1202
  else {
1180
1203
  this.actionNotice = undefined;
1181
1204
  this.stopConfirming = true;
1182
- this.detailAutoFollow = false;
1183
- this.detailScroll = 0;
1205
+ this.viewport.resetTop();
1184
1206
  this.tui.requestRender();
1185
1207
  }
1186
1208
  return;
@@ -1196,18 +1218,18 @@ export class SubagentFleetComponent {
1196
1218
  return;
1197
1219
  }
1198
1220
  }
1199
- rosterLines(width) {
1221
+ rosterLines(layout) {
1200
1222
  if (this.snapshot.items.length === 0)
1201
1223
  return [this.theme.fg("dim", "No tracked children")];
1202
- const start = Math.max(0, Math.min(this.selected - this.bodyHeight + 1, Math.max(0, this.snapshot.items.length - this.bodyHeight)));
1203
- return this.snapshot.items.slice(start, start + this.bodyHeight).map((item, offset) => {
1204
- const index = start + offset;
1224
+ return layout.rosterKeys.map((_key, offset) => {
1225
+ const index = layout.rosterStart + offset;
1226
+ const item = this.snapshot.items[index];
1205
1227
  const marker = index === this.selected ? this.theme.fg("accent", "›") : " ";
1206
1228
  const context = item.kind === "async" ? contextModeBadge(this.theme, item.step?.context ?? item.run.context) : item.kind === "foreground-recent" ? contextModeBadge(this.theme, item.child.context) : "";
1207
1229
  const agent = index === this.selected ? this.theme.bold(item.agent) : item.agent;
1208
1230
  const identity = item.runId.slice(0, 8);
1209
1231
  const left = `${marker} ${statusGlyph(item, this.theme)} ${agent}${context} ${this.theme.fg("dim", `· ${identity}`)}`;
1210
- return rightAligned(left, this.theme.fg("dim", item.state), width);
1232
+ return rightAligned(left, this.theme.fg("dim", item.state), layout.roster.width);
1211
1233
  });
1212
1234
  }
1213
1235
  renderedTranscript(target, width) {
@@ -1305,26 +1327,21 @@ export class SubagentFleetComponent {
1305
1327
  return { header: [], body: this.withActionLines(lines) };
1306
1328
  }
1307
1329
  render(width) {
1308
- if (width < 36)
1330
+ const layout = fleetLayout(width, this.tui.terminal?.rows ?? 32, this.selected, this.snapshot.items);
1331
+ this.renderedLayout = layout;
1332
+ if (!layout)
1309
1333
  return [truncateToWidth("Subagent fleet needs at least 36 columns. Esc closes.", width)];
1310
1334
  const innerWidth = width - 2;
1311
- const rows = this.tui.terminal?.rows ?? 32;
1312
- this.bodyHeight = Math.max(2, Math.floor(rows * 0.85) - 6);
1313
- const rosterWidth = Math.max(22, Math.min(46, Math.floor((innerWidth - 1) * 0.38)));
1314
- const detailWidth = Math.max(1, innerWidth - rosterWidth - 1);
1315
- const roster = this.rosterLines(rosterWidth);
1335
+ const { bodyHeight } = layout;
1336
+ const rosterWidth = layout.roster.width;
1337
+ const detailWidth = layout.output.width;
1338
+ const roster = this.rosterLines(layout);
1316
1339
  const detail = this.wrappedDetail(detailWidth);
1317
- const detailHeader = detail.header.slice(0, Math.max(0, this.bodyHeight - 1));
1318
- this.detailViewportHeight = Math.max(1, this.bodyHeight - detailHeader.length);
1319
- this.detailLineCount = detail.body.length;
1320
- const maxDetailScroll = Math.max(0, detail.body.length - this.detailViewportHeight);
1321
- if (this.detailAutoFollow)
1322
- this.detailScroll = maxDetailScroll;
1323
- else if (this.detailScroll > maxDetailScroll)
1324
- this.detailScroll = maxDetailScroll;
1340
+ const detailHeader = detail.header.slice(0, Math.max(0, bodyHeight - 1));
1341
+ const outputBodyHeight = bodyHeight - detailHeader.length;
1325
1342
  const visibleDetails = [
1326
1343
  ...detailHeader,
1327
- ...detail.body.slice(this.detailScroll, this.detailScroll + this.detailViewportHeight),
1344
+ ...this.viewport.render(detail.body, detailWidth, outputBodyHeight),
1328
1345
  ];
1329
1346
  const lines = [this.theme.fg("border", `╭${"─".repeat(innerWidth)}╮`)];
1330
1347
  const selected = this.snapshot.items[this.selected];
@@ -1336,7 +1353,7 @@ export class SubagentFleetComponent {
1336
1353
  : this.theme.fg("dim", "no children ");
1337
1354
  lines.push(this.theme.fg("border", "│") + rightAligned(title, selectedStatus, innerWidth) + this.theme.fg("border", "│"));
1338
1355
  lines.push(this.theme.fg("border", `├${"─".repeat(rosterWidth)}┬${"─".repeat(detailWidth)}┤`));
1339
- for (let index = 0; index < this.bodyHeight; index++) {
1356
+ for (let index = 0; index < bodyHeight; index++) {
1340
1357
  lines.push(this.theme.fg("border", "│")
1341
1358
  + fit(roster[index] ?? "", rosterWidth)
1342
1359
  + this.theme.fg("border", "│")
@@ -1355,6 +1372,7 @@ export class SubagentFleetComponent {
1355
1372
  return lines.map((line) => truncateToWidth(line, width));
1356
1373
  }
1357
1374
  invalidate() {
1375
+ this.renderedLayout = undefined;
1358
1376
  this.transcriptCache = undefined;
1359
1377
  this.refresh();
1360
1378
  }
@@ -4,6 +4,7 @@
4
4
  * and never becomes code. Scripts run on the ordinary workflow runtime.
5
5
  */
6
6
  export type StructuredWorkflowKind = "tasks" | "chain";
7
+ export declare const TOP_LEVEL_CONTROLS_HINT = " Put worktree and other child controls on the subagent call itself; they apply to every child.";
7
8
  export declare function isPlainRecord(value: unknown): value is Record<string, unknown>;
8
9
  /** Validate exact data shapes and expand them into a package-owned workflow script. */
9
10
  export declare function buildStructuredWorkflowScript(kind: StructuredWorkflowKind, steps: unknown, originalTask: string | undefined): string;
@@ -7,6 +7,7 @@ const OUTPUT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
7
7
  const PLACEHOLDER_PATTERN = /\{(?:task|previous|outputs\.([A-Za-z_][A-Za-z0-9_]*))\}/g;
8
8
  /** Optional per-child launch fields copied verbatim onto the generated runs.all item. */
9
9
  const CHILD_FIELDS = ["cwd", "model"];
10
+ export const TOP_LEVEL_CONTROLS_HINT = " Put worktree and other child controls on the subagent call itself; they apply to every child.";
10
11
  export function isPlainRecord(value) {
11
12
  if (!value || typeof value !== "object" || Array.isArray(value))
12
13
  return false;
@@ -97,7 +98,7 @@ function buildTasksScript(steps, originalTask) {
97
98
  throw new Error("tasks must be a non-empty array of { agent, task } items.");
98
99
  const children = steps.map((item, index) => {
99
100
  const path = `tasks[${index}]`;
100
- const record = exactFields(item, path, ["agent", "task", ...CHILD_FIELDS], "Each tasks item accepts agent and task, plus optional cwd and model.");
101
+ const record = exactFields(item, path, ["agent", "task", ...CHILD_FIELDS], `Each tasks item accepts agent and task, plus optional cwd and model.${TOP_LEVEL_CONTROLS_HINT}`);
101
102
  const agent = nonEmptyString(record.agent, `${path}.agent`);
102
103
  const task = nonEmptyString(record.task, `${path}.task`);
103
104
  const taskExpression = compileTemplate(task, {
@@ -131,20 +132,20 @@ function buildChainScript(steps, originalTask) {
131
132
  let stepChildren;
132
133
  let outputName;
133
134
  if (isPlainRecord(step) && Object.hasOwn(step, "parallel")) {
134
- const record = exactFields(step, path, ["parallel"], "A parallel step accepts only parallel; as is supported on sequential steps only.");
135
+ const record = exactFields(step, path, ["parallel"], `A parallel step accepts only parallel; as is supported on sequential steps only.${TOP_LEVEL_CONTROLS_HINT}`);
135
136
  const items = record.parallel;
136
137
  if (!Array.isArray(items) || items.length === 0)
137
138
  throw new Error(`${path}.parallel must be a non-empty array of { agent, task } items.`);
138
139
  stepChildren = items.map((item, itemIndex) => {
139
140
  const itemPath = `${path}.parallel[${itemIndex}]`;
140
- const itemRecord = exactFields(item, itemPath, ["agent", "task", ...CHILD_FIELDS], "Each parallel item accepts agent and task, plus optional cwd and model.");
141
+ const itemRecord = exactFields(item, itemPath, ["agent", "task", ...CHILD_FIELDS], `Each parallel item accepts agent and task, plus optional cwd and model.${TOP_LEVEL_CONTROLS_HINT}`);
141
142
  const agent = nonEmptyString(itemRecord.agent, `${itemPath}.agent`);
142
143
  const task = nonEmptyString(itemRecord.task, `${itemPath}.task`);
143
144
  return { key: `step-${number}-${itemIndex + 1}`, agent, fields: childFields(itemRecord, itemPath), taskExpression: compileTemplate(task, { path: `${itemPath}.task`, hasOriginalTask, previous, outputs }) };
144
145
  });
145
146
  }
146
147
  else {
147
- const record = exactFields(step, path, ["agent", "task", "as", ...CHILD_FIELDS], "A sequential step accepts agent, task, as, cwd and model; a parallel step accepts only parallel.");
148
+ const record = exactFields(step, path, ["agent", "task", "as", ...CHILD_FIELDS], `A sequential step accepts agent, task, as, cwd and model; a parallel step accepts only parallel.${TOP_LEVEL_CONTROLS_HINT}`);
148
149
  const agent = nonEmptyString(record.agent, `${path}.agent`);
149
150
  let task;
150
151
  if (record.task === undefined) {
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { stableJsonDigest } from "../shared/launch-contract.js";
3
3
  import { createWorkflowResourcePermit, } from "../shared/workflow-child-permit.js";
4
- import { buildStructuredWorkflowScript, isPlainRecord } from "./structured-workflow-scripts.js";
4
+ import { buildStructuredWorkflowScript, isPlainRecord, TOP_LEVEL_CONTROLS_HINT } from "./structured-workflow-scripts.js";
5
5
  const RESOURCE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
6
6
  export const MAX_ARGS_BYTES = 16 * 1024;
7
7
  export const MAX_ARGS_FIELDS = 16;
@@ -152,7 +152,7 @@ function resolveRunCi(args) {
152
152
  function resolveParallel(args) {
153
153
  const unsupported = Object.keys(args).filter((key) => key !== "tasks");
154
154
  if (unsupported.length > 0)
155
- return { error: `workflow 'parallel' args contain unsupported fields: ${unsupported.join(", ")}. Pass args.tasks: [{ agent, task, cwd?, model? }, ...].` };
155
+ return { error: `workflow 'parallel' args contain unsupported fields: ${unsupported.join(", ")}. Pass args.tasks: [{ agent, task, cwd?, model? }, ...].${TOP_LEVEL_CONTROLS_HINT}` };
156
156
  try {
157
157
  return { script: buildStructuredWorkflowScript("tasks", args.tasks, undefined) };
158
158
  }