@bastani/atomic 0.8.24-alpha.2 → 0.8.24-alpha.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -1
  3. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  4. package/dist/builtin/intercom/package.json +1 -1
  5. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  6. package/dist/builtin/mcp/package.json +1 -1
  7. package/dist/builtin/subagents/CHANGELOG.md +10 -0
  8. package/dist/builtin/subagents/README.md +132 -21
  9. package/dist/builtin/subagents/package.json +1 -1
  10. package/dist/builtin/subagents/prompts/parallel-context-build.md +4 -2
  11. package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +3 -1
  12. package/dist/builtin/subagents/skills/subagent/SKILL.md +49 -11
  13. package/dist/builtin/subagents/src/agents/agent-management.ts +79 -16
  14. package/dist/builtin/subagents/src/agents/agents.ts +47 -16
  15. package/dist/builtin/subagents/src/agents/chain-serializer.ts +114 -0
  16. package/dist/builtin/subagents/src/extension/schemas.ts +139 -3
  17. package/dist/builtin/subagents/src/runs/background/async-execution.ts +92 -6
  18. package/dist/builtin/subagents/src/runs/background/async-status.ts +11 -1
  19. package/dist/builtin/subagents/src/runs/background/run-status.ts +4 -1
  20. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +529 -32
  21. package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +361 -118
  22. package/dist/builtin/subagents/src/runs/foreground/execution.ts +75 -7
  23. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +33 -0
  24. package/dist/builtin/subagents/src/runs/shared/acceptance.ts +611 -0
  25. package/dist/builtin/subagents/src/runs/shared/chain-outputs.ts +101 -0
  26. package/dist/builtin/subagents/src/runs/shared/dynamic-fanout.ts +293 -0
  27. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +29 -1
  28. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +11 -0
  29. package/dist/builtin/subagents/src/runs/shared/structured-output.ts +79 -0
  30. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +52 -2
  31. package/dist/builtin/subagents/src/runs/shared/workflow-graph.ts +206 -0
  32. package/dist/builtin/subagents/src/shared/formatters.ts +2 -2
  33. package/dist/builtin/subagents/src/shared/settings.ts +53 -4
  34. package/dist/builtin/subagents/src/shared/types.ts +226 -0
  35. package/dist/builtin/subagents/src/shared/utils.ts +2 -1
  36. package/dist/builtin/subagents/src/slash/slash-commands.ts +41 -3
  37. package/dist/builtin/subagents/src/tui/render.ts +152 -34
  38. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  39. package/dist/builtin/web-access/package.json +1 -1
  40. package/dist/builtin/workflows/CHANGELOG.md +6 -0
  41. package/dist/builtin/workflows/package.json +1 -1
  42. package/dist/builtin/workflows/skills/create-spec/SKILL.md +1 -1
  43. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +0 -1
  44. package/dist/core/slash-commands.d.ts.map +1 -1
  45. package/dist/core/slash-commands.js +1 -0
  46. package/dist/core/slash-commands.js.map +1 -1
  47. package/dist/core/system-prompt.d.ts.map +1 -1
  48. package/dist/core/system-prompt.js +4 -3
  49. package/dist/core/system-prompt.js.map +1 -1
  50. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  51. package/dist/modes/interactive/interactive-mode.js +1 -1
  52. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  53. package/docs/usage.md +1 -0
  54. package/docs/workflows.md +173 -0
  55. package/package.json +1 -1
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { spawn } from "node:child_process";
6
- import { existsSync } from "node:fs";
6
+ import { existsSync, unlinkSync } from "node:fs";
7
7
  import type { Message } from "@earendil-works/pi-ai";
8
8
  import type { CodexFastModeResolvedSettings, CodexFastModeScope } from "@bastani/atomic";
9
9
  import type { AgentConfig } from "../../agents/agents.ts";
@@ -47,6 +47,7 @@ import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
47
47
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
48
48
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
49
49
  import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
50
+ import { readStructuredOutput } from "../shared/structured-output.ts";
50
51
  import { captureSingleOutputSnapshot, formatSavedOutputReference, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
51
52
  import {
52
53
  buildModelCandidates,
@@ -70,8 +71,10 @@ import {
70
71
  resolveSubagentModelFastMode,
71
72
  } from "../../shared/fast-mode.ts";
72
73
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
74
+ import { acceptanceFailureMessage, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
73
75
 
74
76
  const artifactOutputByResult = new WeakMap<SingleResult, string>();
77
+ const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
75
78
 
76
79
  function emptyUsage(): Usage {
77
80
  return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
@@ -94,6 +97,17 @@ function appendRecentOutput(progress: AgentProgress, lines: string[]): void {
94
97
  }
95
98
  }
96
99
 
100
+ function stripAcceptanceReportsFromMessages(messages: Message[] | undefined): void {
101
+ for (const message of messages ?? []) {
102
+ if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
103
+ for (const part of message.content) {
104
+ if (part.type === "text" && "text" in part && typeof part.text === "string") {
105
+ part.text = stripAcceptanceReport(part.text);
106
+ }
107
+ }
108
+ }
109
+ }
110
+
97
111
  function snapshotProgress(progress: AgentProgress): AgentProgress {
98
112
  return {
99
113
  ...progress,
@@ -142,6 +156,7 @@ async function runSingleAttempt(
142
156
  outputSnapshot?: SingleOutputSnapshot;
143
157
  fastModeSettings: CodexFastModeResolvedSettings;
144
158
  fastModeScope: CodexFastModeScope;
159
+ originalTask?: string;
145
160
  },
146
161
  ): Promise<SingleResult> {
147
162
  const modelArg = applyThinkingSuffix(model, agent.thinking);
@@ -180,11 +195,12 @@ async function runSingleAttempt(
180
195
  parentCapabilityToken: options.nestedRoute?.capabilityToken,
181
196
  codexFastModeSettings: shared.fastModeSettings,
182
197
  codexFastModeScope: shared.fastModeScope,
198
+ structuredOutput: options.structuredOutput,
183
199
  });
184
200
 
185
201
  const result: SingleResult = {
186
202
  agent: agent.name,
187
- task,
203
+ task: shared.originalTask ?? task,
188
204
  exitCode: 0,
189
205
  messages: [],
190
206
  usage: emptyUsage(),
@@ -195,6 +211,13 @@ async function runSingleAttempt(
195
211
  skillsWarning: shared.skillsWarning,
196
212
  };
197
213
  const startTime = Date.now();
214
+ if (options.structuredOutput) {
215
+ try {
216
+ if (existsSync(options.structuredOutput.outputPath)) unlinkSync(options.structuredOutput.outputPath);
217
+ } catch {
218
+ // Missing/stale structured-output files are handled after the child exits.
219
+ }
220
+ }
198
221
  const controlConfig = options.controlConfig ?? DEFAULT_CONTROL_CONFIG;
199
222
  let interruptedByControl = false;
200
223
  const allControlEvents: ControlEvent[] = [];
@@ -680,6 +703,21 @@ async function runSingleAttempt(
680
703
  : `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;
681
704
  }
682
705
  }
706
+ if (options.structuredOutput && result.exitCode === 0 && !result.error) {
707
+ const structured = readStructuredOutput({
708
+ schema: options.structuredOutput.schema,
709
+ schemaPath: options.structuredOutput.schemaPath,
710
+ outputPath: options.structuredOutput.outputPath,
711
+ });
712
+ result.structuredOutputSchemaPath = options.structuredOutput.schemaPath;
713
+ result.structuredOutputPath = options.structuredOutput.outputPath;
714
+ if (structured.error) {
715
+ result.exitCode = 1;
716
+ result.error = structured.error;
717
+ } else {
718
+ result.structuredOutput = structured.value;
719
+ }
720
+ }
683
721
 
684
722
  progress.status = result.exitCode === 0 ? "completed" : "failed";
685
723
  progress.durationMs = Date.now() - startTime;
@@ -696,11 +734,12 @@ async function runSingleAttempt(
696
734
  durationMs: progress.durationMs,
697
735
  };
698
736
 
699
- let fullOutput = getFinalOutput(result.messages ?? []);
737
+ const acceptanceOutput = getFinalOutput(result.messages ?? []);
738
+ let fullOutput = stripAcceptanceReport(acceptanceOutput);
700
739
  const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false
701
740
  ? evaluateCompletionMutationGuard({
702
741
  agent: agent.name,
703
- task,
742
+ task: shared.originalTask ?? task,
704
743
  messages: result.messages ?? [],
705
744
  tools: agent.tools,
706
745
  mcpDirectTools: agent.mcpDirectTools,
@@ -724,7 +763,7 @@ async function runSingleAttempt(
724
763
  }
725
764
  if (options.outputPath && result.exitCode === 0) {
726
765
  const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
727
- fullOutput = resolvedOutput.fullOutput;
766
+ fullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);
728
767
  result.savedOutputPath = resolvedOutput.savedPath;
729
768
  result.outputSaveError = resolvedOutput.saveError;
730
769
  if (resolvedOutput.savedPath) {
@@ -732,6 +771,7 @@ async function runSingleAttempt(
732
771
  }
733
772
  }
734
773
  artifactOutputByResult.set(result, fullOutput);
774
+ acceptanceOutputByResult.set(result, acceptanceOutput);
735
775
  result.outputMode = options.outputMode ?? "inline";
736
776
  result.finalOutput = options.outputMode === "file-only" && result.savedOutputPath && result.outputReference
737
777
  ? result.outputReference.message
@@ -789,6 +829,17 @@ export async function runSync(
789
829
  }
790
830
 
791
831
  const shareEnabled = options.share === true;
832
+ const effectiveAcceptance = resolveEffectiveAcceptance({
833
+ explicit: options.acceptance,
834
+ agentName,
835
+ task,
836
+ mode: options.acceptanceContext?.mode ?? "single",
837
+ async: options.acceptanceContext?.async,
838
+ dynamic: options.acceptanceContext?.dynamic,
839
+ dynamicGroup: options.acceptanceContext?.dynamicGroup,
840
+ });
841
+ const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
842
+ const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
792
843
  const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
793
844
  const skillNames = options.skills ?? agent.skills ?? [];
794
845
  const skillCwd = options.cwd ?? runtimeCwd;
@@ -833,7 +884,7 @@ export async function runSync(
833
884
  artifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, agentName, options.index);
834
885
  ensureArtifactsDir(options.artifactsDir);
835
886
  if (options.artifactConfig?.includeInput !== false) {
836
- writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${task}`);
887
+ writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${taskWithAcceptance}`);
837
888
  }
838
889
  if (options.artifactConfig?.includeJsonl !== false) {
839
890
  jsonlPath = artifactPathsResult.jsonlPath;
@@ -846,7 +897,7 @@ export async function runSync(
846
897
  const candidate = modelsToTry[i];
847
898
  if (candidate) attemptedModels.push(candidate);
848
899
  const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
849
- const result = await runSingleAttempt(runtimeCwd, agent, task, candidate, options, {
900
+ const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, options, {
850
901
  sessionEnabled,
851
902
  systemPrompt,
852
903
  resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
@@ -857,6 +908,7 @@ export async function runSync(
857
908
  outputSnapshot,
858
909
  fastModeSettings,
859
910
  fastModeScope,
911
+ originalTask: task,
860
912
  });
861
913
  lastResult = result;
862
914
  sumUsage(aggregateUsage, result.usage);
@@ -949,5 +1001,21 @@ export async function runSync(
949
1001
  if (sessionFile) result.sessionFile = sessionFile;
950
1002
  }
951
1003
 
1004
+ result.acceptance = await evaluateAcceptance({
1005
+ acceptance: effectiveAcceptance,
1006
+ output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
1007
+ cwd: options.cwd ?? runtimeCwd,
1008
+ });
1009
+ const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
1010
+ stripAcceptanceReportsFromMessages(result.messages);
1011
+ if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted) {
1012
+ result.exitCode = 1;
1013
+ result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
1014
+ if (result.progress) {
1015
+ result.progress.status = "failed";
1016
+ result.progress.error = result.error;
1017
+ }
1018
+ }
1019
+
952
1020
  return result;
953
1021
  }
@@ -21,6 +21,7 @@ import {
21
21
  writeInitialProgressFile,
22
22
  getStepAgents,
23
23
  isParallelStep,
24
+ isDynamicParallelStep,
24
25
  resolveStepBehavior,
25
26
  suppressProgressForReadOnlyTask,
26
27
  taskDisallowsFileUpdates,
@@ -63,6 +64,7 @@ import {
63
64
  } from "../shared/worktree.ts";
64
65
  import {
65
66
  type AgentProgress,
67
+ type AcceptanceInput,
66
68
  type ArtifactConfig,
67
69
  type ArtifactPaths,
68
70
  type ControlConfig,
@@ -107,6 +109,7 @@ interface TaskParam {
107
109
  progress?: boolean;
108
110
  model?: string;
109
111
  skill?: string | string[] | boolean;
112
+ acceptance?: AcceptanceInput;
110
113
  }
111
114
 
112
115
  export interface SubagentParamsLike {
@@ -140,6 +143,7 @@ export interface SubagentParamsLike {
140
143
  outputMode?: "inline" | "file-only";
141
144
  agentScope?: string;
142
145
  chainDir?: string;
146
+ acceptance?: AcceptanceInput;
143
147
  }
144
148
 
145
149
  export interface SubagentExecutorRuntimeDeps {
@@ -858,6 +862,12 @@ function validateExecutionInput(
858
862
  details: { mode: "chain" as const, results: [] },
859
863
  };
860
864
  }
865
+ } else if (isDynamicParallelStep(firstStep)) {
866
+ return {
867
+ content: [{ type: "text", text: "First step in chain cannot be dynamic fanout; expand.from requires a prior structured named output" }],
868
+ isError: true,
869
+ details: { mode: "chain" as const, results: [] },
870
+ };
861
871
  } else if (!(firstStep as SequentialStep).task && !params.task && !allowClarifyTaskPrompt) {
862
872
  return {
863
873
  content: [{ type: "text", text: "First step in chain must have a task" }],
@@ -1019,6 +1029,10 @@ function collectChainSessionFiles(
1019
1029
  }
1020
1030
  continue;
1021
1031
  }
1032
+ if (isDynamicParallelStep(step)) {
1033
+ sessionFiles.push(undefined);
1034
+ continue;
1035
+ }
1022
1036
  sessionFiles.push(sessionFileForIndex(flatIndex));
1023
1037
  flatIndex++;
1024
1038
  }
@@ -1037,6 +1051,15 @@ function wrapChainTasksForFork(chain: ChainStep[], context: SubagentParamsLike["
1037
1051
  })),
1038
1052
  };
1039
1053
  }
1054
+ if (isDynamicParallelStep(step)) {
1055
+ return {
1056
+ ...step,
1057
+ parallel: {
1058
+ ...step.parallel,
1059
+ task: wrapForkTask(step.parallel.task ?? "{previous}"),
1060
+ },
1061
+ };
1062
+ }
1040
1063
  const sequential = step as SequentialStep;
1041
1064
  return {
1042
1065
  ...sequential,
@@ -1127,6 +1150,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ResolvedExecutorDeps): S
1127
1150
  ...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
1128
1151
  ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
1129
1152
  ...(task.progress !== undefined ? { progress: task.progress } : {}),
1153
+ ...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
1130
1154
  }));
1131
1155
  return deps.runtime.executeAsyncChain(id, {
1132
1156
  chain: [{
@@ -1175,6 +1199,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ResolvedExecutorDeps): S
1175
1199
  sessionRoot,
1176
1200
  chainSkills,
1177
1201
  sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForIndex),
1202
+ dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
1178
1203
  maxSubagentDepth: currentMaxSubagentDepth,
1179
1204
  workflowStageSubagentGuard,
1180
1205
  worktreeSetupHook: deps.config.worktreeSetupHook,
@@ -1227,6 +1252,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ResolvedExecutorDeps): S
1227
1252
  controlIntercomTarget,
1228
1253
  childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
1229
1254
  nestedRoute,
1255
+ acceptance: params.acceptance,
1230
1256
  });
1231
1257
  }
1232
1258
 
@@ -1284,6 +1310,7 @@ async function runChainPath(data: ExecutionContextData, deps: ResolvedExecutorDe
1284
1310
  nestedRoute: foregroundControl?.nestedRoute,
1285
1311
  chainSkills,
1286
1312
  chainDir: params.chainDir,
1313
+ dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
1287
1314
  maxSubagentDepth: currentMaxSubagentDepth,
1288
1315
  workflowStageSubagentGuard,
1289
1316
  worktreeSetupHook: deps.config.worktreeSetupHook,
@@ -1322,6 +1349,7 @@ async function runChainPath(data: ExecutionContextData, deps: ResolvedExecutorDe
1322
1349
  sessionRoot,
1323
1350
  chainSkills: chainResult.requestedAsync.chainSkills,
1324
1351
  sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForIndex),
1352
+ dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
1325
1353
  maxSubagentDepth: currentMaxSubagentDepth,
1326
1354
  workflowStageSubagentGuard,
1327
1355
  worktreeSetupHook: deps.config.worktreeSetupHook,
@@ -1549,6 +1577,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1549
1577
  preferredModelProvider: input.ctx.model?.provider,
1550
1578
  currentModel: currentModelFullId(input.ctx.model),
1551
1579
  skills: effectiveSkills === false ? [] : effectiveSkills,
1580
+ acceptance: task.acceptance,
1581
+ acceptanceContext: { mode: "parallel" },
1552
1582
  onUpdate: input.onUpdate
1553
1583
  ? (progressUpdate) => {
1554
1584
  const stepResults = progressUpdate.details?.results || [];
@@ -1741,6 +1771,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ResolvedExecuto
1741
1771
  ...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
1742
1772
  ...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
1743
1773
  ...(progress !== undefined ? { progress } : {}),
1774
+ ...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
1744
1775
  };
1745
1776
  });
1746
1777
  return deps.runtime.executeAsyncChain(id, {
@@ -2123,6 +2154,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ResolvedExecutorD
2123
2154
  preferredModelProvider: currentProvider,
2124
2155
  currentModel: currentModelFullId(ctx.model),
2125
2156
  skills: effectiveSkills,
2157
+ acceptance: params.acceptance,
2158
+ acceptanceContext: { mode: "single" },
2126
2159
  });
2127
2160
  if (foregroundControl?.currentIndex === 0) {
2128
2161
  foregroundControl.interrupt = undefined;