@bastani/atomic 0.8.28 → 0.8.29-alpha.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.
Files changed (134) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +27 -0
  3. package/dist/builtin/cursor/LICENSE +26 -0
  4. package/dist/builtin/cursor/README.md +22 -0
  5. package/dist/builtin/cursor/index.ts +9 -0
  6. package/dist/builtin/cursor/package.json +46 -0
  7. package/dist/builtin/cursor/src/auth.ts +352 -0
  8. package/dist/builtin/cursor/src/catalog-cache.ts +155 -0
  9. package/dist/builtin/cursor/src/config.ts +123 -0
  10. package/dist/builtin/cursor/src/conversation-state.ts +135 -0
  11. package/dist/builtin/cursor/src/cursor-models-raw.json +583 -0
  12. package/dist/builtin/cursor/src/model-mapper.ts +270 -0
  13. package/dist/builtin/cursor/src/models.ts +54 -0
  14. package/dist/builtin/cursor/src/native-loader.ts +71 -0
  15. package/dist/builtin/cursor/src/proto/README.md +34 -0
  16. package/dist/builtin/cursor/src/proto/agent_pb.ts +15294 -0
  17. package/dist/builtin/cursor/src/proto/protobuf-codec.ts +717 -0
  18. package/dist/builtin/cursor/src/provider.ts +301 -0
  19. package/dist/builtin/cursor/src/stream.ts +564 -0
  20. package/dist/builtin/cursor/src/transport.ts +791 -0
  21. package/dist/builtin/intercom/CHANGELOG.md +4 -0
  22. package/dist/builtin/intercom/package.json +2 -2
  23. package/dist/builtin/intercom/skills/intercom/SKILL.md +5 -5
  24. package/dist/builtin/mcp/CHANGELOG.md +4 -0
  25. package/dist/builtin/mcp/package.json +3 -3
  26. package/dist/builtin/subagents/CHANGELOG.md +12 -0
  27. package/dist/builtin/subagents/README.md +7 -3
  28. package/dist/builtin/subagents/agents/codebase-online-researcher.md +9 -24
  29. package/dist/builtin/subagents/agents/debugger.md +3 -5
  30. package/dist/builtin/subagents/package.json +4 -4
  31. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +2 -1
  32. package/dist/builtin/subagents/src/runs/foreground/execution.ts +2 -1
  33. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +1 -0
  34. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +19 -2
  35. package/dist/builtin/subagents/src/runs/shared/structured-output.ts +271 -10
  36. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +12 -39
  37. package/dist/builtin/subagents/src/shared/types.ts +1 -0
  38. package/dist/builtin/subagents/src/shared/utils.ts +50 -10
  39. package/dist/builtin/subagents/src/slash/saved-chain-mapping.ts +77 -0
  40. package/dist/builtin/subagents/src/slash/slash-commands.ts +1 -55
  41. package/dist/builtin/web-access/CHANGELOG.md +5 -1
  42. package/dist/builtin/web-access/README.md +1 -1
  43. package/dist/builtin/web-access/github-extract.ts +1 -1
  44. package/dist/builtin/web-access/package.json +3 -3
  45. package/dist/builtin/workflows/CHANGELOG.md +18 -0
  46. package/dist/builtin/workflows/README.md +19 -1
  47. package/dist/builtin/workflows/package.json +2 -2
  48. package/dist/builtin/workflows/skills/research-codebase/SKILL.md +17 -3
  49. package/dist/builtin/workflows/src/extension/wiring.ts +17 -1
  50. package/dist/builtin/workflows/src/extension/workflow-schema.ts +34 -0
  51. package/dist/builtin/workflows/src/runs/foreground/executor.ts +13 -2
  52. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +86 -14
  53. package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +11 -3
  54. package/dist/builtin/workflows/src/shared/types.ts +8 -4
  55. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +64 -2
  56. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -8
  57. package/dist/builtin/workflows/src/tui/workflow-status.ts +2 -0
  58. package/dist/core/builtin-packages.d.ts.map +1 -1
  59. package/dist/core/builtin-packages.js +6 -0
  60. package/dist/core/builtin-packages.js.map +1 -1
  61. package/dist/core/extensions/index.d.ts +1 -1
  62. package/dist/core/extensions/index.d.ts.map +1 -1
  63. package/dist/core/extensions/index.js.map +1 -1
  64. package/dist/core/extensions/types.d.ts +20 -0
  65. package/dist/core/extensions/types.d.ts.map +1 -1
  66. package/dist/core/extensions/types.js.map +1 -1
  67. package/dist/core/model-resolver.d.ts +1 -0
  68. package/dist/core/model-resolver.d.ts.map +1 -1
  69. package/dist/core/model-resolver.js +17 -8
  70. package/dist/core/model-resolver.js.map +1 -1
  71. package/dist/core/package-manager.d.ts +11 -9
  72. package/dist/core/package-manager.d.ts.map +1 -1
  73. package/dist/core/package-manager.js +55 -10
  74. package/dist/core/package-manager.js.map +1 -1
  75. package/dist/core/project-trust.d.ts +1 -0
  76. package/dist/core/project-trust.d.ts.map +1 -1
  77. package/dist/core/project-trust.js +3 -3
  78. package/dist/core/project-trust.js.map +1 -1
  79. package/dist/core/resource-loader.d.ts +9 -0
  80. package/dist/core/resource-loader.d.ts.map +1 -1
  81. package/dist/core/resource-loader.js +72 -9
  82. package/dist/core/resource-loader.js.map +1 -1
  83. package/dist/core/sdk.d.ts +3 -3
  84. package/dist/core/sdk.d.ts.map +1 -1
  85. package/dist/core/sdk.js +5 -5
  86. package/dist/core/sdk.js.map +1 -1
  87. package/dist/core/tools/index.d.ts +1 -0
  88. package/dist/core/tools/index.d.ts.map +1 -1
  89. package/dist/core/tools/index.js +1 -0
  90. package/dist/core/tools/index.js.map +1 -1
  91. package/dist/core/tools/structured-output.d.ts +39 -0
  92. package/dist/core/tools/structured-output.d.ts.map +1 -0
  93. package/dist/core/tools/structured-output.js +141 -0
  94. package/dist/core/tools/structured-output.js.map +1 -0
  95. package/dist/index.d.ts +1 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +1 -1
  98. package/dist/index.js.map +1 -1
  99. package/dist/main.d.ts.map +1 -1
  100. package/dist/main.js +36 -14
  101. package/dist/main.js.map +1 -1
  102. package/dist/modes/interactive/components/login-dialog.d.ts +3 -0
  103. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  104. package/dist/modes/interactive/components/login-dialog.js +16 -0
  105. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  106. package/dist/modes/interactive/interactive-mode.d.ts +11 -0
  107. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  108. package/dist/modes/interactive/interactive-mode.js +158 -11
  109. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  110. package/dist/modes/print-mode.d.ts.map +1 -1
  111. package/dist/modes/print-mode.js +39 -0
  112. package/dist/modes/print-mode.js.map +1 -1
  113. package/docs/custom-provider.md +1 -0
  114. package/docs/extensions.md +2 -2
  115. package/docs/models.md +2 -0
  116. package/docs/packages.md +3 -1
  117. package/docs/providers.md +15 -0
  118. package/docs/sdk.md +61 -0
  119. package/docs/security.md +1 -1
  120. package/docs/subagents.md +21 -0
  121. package/docs/usage.md +2 -0
  122. package/docs/workflows.md +10 -7
  123. package/examples/extensions/README.md +1 -1
  124. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  125. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  126. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  127. package/examples/extensions/gondolin/package-lock.json +2 -2
  128. package/examples/extensions/gondolin/package.json +1 -1
  129. package/examples/extensions/sandbox/package-lock.json +2 -2
  130. package/examples/extensions/sandbox/package.json +1 -1
  131. package/examples/extensions/structured-output.ts +22 -53
  132. package/examples/extensions/with-deps/package-lock.json +2 -2
  133. package/examples/extensions/with-deps/package.json +1 -1
  134. package/package.json +12 -9
@@ -1 +1 @@
1
- {"version":3,"file":"print-mode.d.ts","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAM5E;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;CAC/B;AAsBD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,mBAAmB,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4J/G","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent } from \"@earendil-works/pi-ai\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.ts\";\nimport type { ExtensionError } from \"../core/extensions/index.ts\";\nimport type { CustomMessage } from \"../core/messages.ts\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.ts\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.ts\";\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n}\n\nfunction isCommandExtensionError(error: ExtensionError): boolean {\n\treturn error.event === \"command\" || error.extensionPath.startsWith(\"command:\");\n}\n\nfunction displayableCustomText(message: CustomMessage): string | undefined {\n\tif (message.display !== true) return undefined;\n\tif (typeof message.content === \"string\") return message.content;\n\n\tlet text = \"\";\n\tlet hasTextPart = false;\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") {\n\t\t\thasTextPart = true;\n\t\t\ttext += part.text;\n\t\t}\n\t}\n\n\treturn hasTextPart ? text : undefined;\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(runtimeHost: AgentSessionRuntime, options: PrintModeOptions): Promise<number> {\n\tconst { mode, messages = [], initialMessage, initialImages } = options;\n\tlet exitCode = 0;\n\tlet suppressFinalOutput = false;\n\tlet activePromptHadCommandError = false;\n\tlet session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet disposed = false;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tawait runtimeHost.dispose();\n\t};\n\n\tconst registerSignalHandlers = (): void => {\n\t\tconst signals: NodeJS.Signals[] = [\"SIGTERM\"];\n\t\tif (process.platform !== \"win32\") {\n\t\t\tsignals.push(\"SIGHUP\");\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst handler = () => {\n\t\t\t\tkillTrackedDetachedChildren();\n\t\t\t\tvoid disposeRuntime().finally(() => {\n\t\t\t\t\tprocess.exit(signal === \"SIGHUP\" ? 129 : 143);\n\t\t\t\t});\n\t\t\t};\n\t\t\tprocess.on(signal, handler);\n\t\t\tsignalCleanupHandlers.push(() => process.off(signal, handler));\n\t\t}\n\t};\n\n\tregisterSignalHandlers();\n\n\tconst promptWithScopedCommandSuppression = async (\n\t\ttext: string,\n\t\tpromptOptions?: { images?: ImageContent[] },\n\t): Promise<void> => {\n\t\tactivePromptHadCommandError = false;\n\t\ttry {\n\t\t\tif (promptOptions === undefined) {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} else {\n\t\t\t\tawait session.prompt(text, promptOptions);\n\t\t\t}\n\t\t} finally {\n\t\t\t// Final-output suppression is scoped to the most recent prompt so a\n\t\t\t// later successful prompt in the same invocation can still print its\n\t\t\t// result. The non-zero exit code remains sticky across prompts.\n\t\t\tsuppressFinalOutput = activePromptHadCommandError;\n\t\t}\n\t};\n\n\tconst rebindSession = async (): Promise<void> => {\n\t\tsession = runtimeHost.session;\n\t\tawait session.bindExtensions({\n\t\t\tmode: mode === \"json\" ? \"json\" : \"print\",\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (newSessionOptions) => runtimeHost.newSession(newSessionOptions),\n\t\t\t\tfork: async (entryId, forkOptions) => {\n\t\t\t\t\tconst result = await runtimeHost.fork(entryId, forkOptions);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, navigateOptions) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: navigateOptions?.summarize,\n\t\t\t\t\t\tcustomInstructions: navigateOptions?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: navigateOptions?.replaceInstructions,\n\t\t\t\t\t\tlabel: navigateOptions?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, switchOptions) => {\n\t\t\t\t\treturn runtimeHost.switchSession(sessionPath, switchOptions);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\tconst isCommandError = isCommandExtensionError(err);\n\t\t\t\tif (isCommandError) exitCode = 1;\n\t\t\t\tactivePromptHadCommandError = activePromptHadCommandError || isCommandError;\n\t\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t\t}\n\t\t});\n\t};\n\n\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\n\t});\n\n\ttry {\n\t\tif (mode === \"json\") {\n\t\t\tconst header = session.sessionManager.getHeader();\n\t\t\tif (header) {\n\t\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t\t}\n\t\t}\n\n\t\tawait rebindSession();\n\n\t\tif (initialMessage) {\n\t\t\tawait promptWithScopedCommandSuppression(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait promptWithScopedCommandSuppression(message);\n\t\t}\n\n\t\tif (mode === \"text\" && !suppressFinalOutput) {\n\t\t\tconst state = session.state;\n\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\texitCode = 1;\n\t\t\t\t} else {\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (lastMessage?.role === \"custom\") {\n\t\t\t\tconst text = displayableCustomText(lastMessage as CustomMessage);\n\t\t\t\tif (text !== undefined) {\n\t\t\t\t\twriteRawStdout(`${text}\\n`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn exitCode;\n\t} catch (error: unknown) {\n\t\tconsole.error(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t} finally {\n\t\tfor (const cleanup of signalCleanupHandlers) {\n\t\t\tcleanup();\n\t\t}\n\t\tawait disposeRuntime();\n\t\tawait flushRawStdout();\n\t}\n}\n"]}
1
+ {"version":3,"file":"print-mode.d.ts","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,YAAY,EAAqB,MAAM,uBAAuB,CAAC;AAE/F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAO5E;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;CAC/B;AAiED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,mBAAmB,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqK/G","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent, ToolResultMessage } from \"@earendil-works/pi-ai\";\nimport type { AgentSessionEvent } from \"../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.ts\";\nimport type { ExtensionError } from \"../core/extensions/index.ts\";\nimport type { ToolDefinition } from \"../core/extensions/types.ts\";\nimport type { CustomMessage } from \"../core/messages.ts\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.ts\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.ts\";\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n}\n\nfunction isCommandExtensionError(error: ExtensionError): boolean {\n\treturn error.event === \"command\" || error.extensionPath.startsWith(\"command:\");\n}\n\nfunction displayableCustomText(message: CustomMessage): string | undefined {\n\tif (message.display !== true) return undefined;\n\tif (typeof message.content === \"string\") return message.content;\n\n\tlet text = \"\";\n\tlet hasTextPart = false;\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") {\n\t\t\thasTextPart = true;\n\t\t\ttext += part.text;\n\t\t}\n\t}\n\n\treturn hasTextPart ? text : undefined;\n}\n\ntype MaybeTerminatingToolResult = {\n\tterminate?: boolean;\n};\n\ntype ToolExecutionEndSessionEvent = Extract<AgentSessionEvent, { type: \"tool_execution_end\" }>;\n\ntype GetToolDefinition = (name: string) => ToolDefinition | undefined;\n\nfunction isStructuredOutputTool(getToolDefinition: GetToolDefinition, name: string): boolean {\n\treturn getToolDefinition(name)?.structuredOutput === true;\n}\n\nfunction isTerminatingStructuredOutputEvent(\n\tevent: AgentSessionEvent,\n\tgetToolDefinition: GetToolDefinition,\n): event is ToolExecutionEndSessionEvent {\n\tif (event.type !== \"tool_execution_end\") return false;\n\tif (!isStructuredOutputTool(getToolDefinition, event.toolName)) return false;\n\tif (event.isError) return false;\n\tconst result = event.result as MaybeTerminatingToolResult | undefined;\n\treturn result?.terminate === true;\n}\n\nfunction textFromToolResult(message: ToolResultMessage): string | undefined {\n\tlet text = \"\";\n\tlet hasTextPart = false;\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") {\n\t\t\thasTextPart = true;\n\t\t\ttext += part.text;\n\t\t}\n\t}\n\treturn hasTextPart ? text : undefined;\n}\n\nfunction terminatingStructuredOutputText(\n\tmessage: ToolResultMessage,\n\tterminatingStructuredOutputCallIds: ReadonlySet<string>,\n): string | undefined {\n\tif (!terminatingStructuredOutputCallIds.has(message.toolCallId)) return undefined;\n\treturn textFromToolResult(message);\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(runtimeHost: AgentSessionRuntime, options: PrintModeOptions): Promise<number> {\n\tconst { mode, messages = [], initialMessage, initialImages } = options;\n\tlet exitCode = 0;\n\tlet suppressFinalOutput = false;\n\tlet activePromptHadCommandError = false;\n\tlet session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tconst terminatingStructuredOutputCallIds = new Set<string>();\n\tlet disposed = false;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tawait runtimeHost.dispose();\n\t};\n\n\tconst registerSignalHandlers = (): void => {\n\t\tconst signals: NodeJS.Signals[] = [\"SIGTERM\"];\n\t\tif (process.platform !== \"win32\") {\n\t\t\tsignals.push(\"SIGHUP\");\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst handler = () => {\n\t\t\t\tkillTrackedDetachedChildren();\n\t\t\t\tvoid disposeRuntime().finally(() => {\n\t\t\t\t\tprocess.exit(signal === \"SIGHUP\" ? 129 : 143);\n\t\t\t\t});\n\t\t\t};\n\t\t\tprocess.on(signal, handler);\n\t\t\tsignalCleanupHandlers.push(() => process.off(signal, handler));\n\t\t}\n\t};\n\n\tregisterSignalHandlers();\n\n\tconst promptWithScopedCommandSuppression = async (\n\t\ttext: string,\n\t\tpromptOptions?: { images?: ImageContent[] },\n\t): Promise<void> => {\n\t\tactivePromptHadCommandError = false;\n\t\ttry {\n\t\t\tif (promptOptions === undefined) {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} else {\n\t\t\t\tawait session.prompt(text, promptOptions);\n\t\t\t}\n\t\t} finally {\n\t\t\t// Final-output suppression is scoped to the most recent prompt so a\n\t\t\t// later successful prompt in the same invocation can still print its\n\t\t\t// result. The non-zero exit code remains sticky across prompts.\n\t\t\tsuppressFinalOutput = activePromptHadCommandError;\n\t\t}\n\t};\n\n\tconst rebindSession = async (): Promise<void> => {\n\t\tsession = runtimeHost.session;\n\t\tawait session.bindExtensions({\n\t\t\tmode: mode === \"json\" ? \"json\" : \"print\",\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (newSessionOptions) => runtimeHost.newSession(newSessionOptions),\n\t\t\t\tfork: async (entryId, forkOptions) => {\n\t\t\t\t\tconst result = await runtimeHost.fork(entryId, forkOptions);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, navigateOptions) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: navigateOptions?.summarize,\n\t\t\t\t\t\tcustomInstructions: navigateOptions?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: navigateOptions?.replaceInstructions,\n\t\t\t\t\t\tlabel: navigateOptions?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, switchOptions) => {\n\t\t\t\t\treturn runtimeHost.switchSession(sessionPath, switchOptions);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\tconst isCommandError = isCommandExtensionError(err);\n\t\t\t\tif (isCommandError) exitCode = 1;\n\t\t\t\tactivePromptHadCommandError = activePromptHadCommandError || isCommandError;\n\t\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (isTerminatingStructuredOutputEvent(event, (name) => session.getToolDefinition(name))) {\n\t\t\t\tterminatingStructuredOutputCallIds.add(event.toolCallId);\n\t\t\t}\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t\t}\n\t\t});\n\t};\n\n\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\n\t});\n\n\ttry {\n\t\tif (mode === \"json\") {\n\t\t\tconst header = session.sessionManager.getHeader();\n\t\t\tif (header) {\n\t\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t\t}\n\t\t}\n\n\t\tawait rebindSession();\n\n\t\tif (initialMessage) {\n\t\t\tawait promptWithScopedCommandSuppression(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait promptWithScopedCommandSuppression(message);\n\t\t}\n\n\t\tif (mode === \"text\" && !suppressFinalOutput) {\n\t\t\tconst state = session.state;\n\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\texitCode = 1;\n\t\t\t\t} else {\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (lastMessage?.role === \"custom\") {\n\t\t\t\tconst text = displayableCustomText(lastMessage as CustomMessage);\n\t\t\t\tif (text !== undefined) {\n\t\t\t\t\twriteRawStdout(`${text}\\n`);\n\t\t\t\t}\n\t\t\t} else if (lastMessage?.role === \"toolResult\") {\n\t\t\t\tconst text = terminatingStructuredOutputText(lastMessage as ToolResultMessage, terminatingStructuredOutputCallIds);\n\t\t\t\tif (text !== undefined) {\n\t\t\t\t\twriteRawStdout(`${text}\\n`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn exitCode;\n\t} catch (error: unknown) {\n\t\tconsole.error(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t} finally {\n\t\tfor (const cleanup of signalCleanupHandlers) {\n\t\t\tcleanup();\n\t\t}\n\t\tawait disposeRuntime();\n\t\tawait flushRawStdout();\n\t}\n}\n"]}
@@ -25,6 +25,35 @@ function displayableCustomText(message) {
25
25
  }
26
26
  return hasTextPart ? text : undefined;
27
27
  }
28
+ function isStructuredOutputTool(getToolDefinition, name) {
29
+ return getToolDefinition(name)?.structuredOutput === true;
30
+ }
31
+ function isTerminatingStructuredOutputEvent(event, getToolDefinition) {
32
+ if (event.type !== "tool_execution_end")
33
+ return false;
34
+ if (!isStructuredOutputTool(getToolDefinition, event.toolName))
35
+ return false;
36
+ if (event.isError)
37
+ return false;
38
+ const result = event.result;
39
+ return result?.terminate === true;
40
+ }
41
+ function textFromToolResult(message) {
42
+ let text = "";
43
+ let hasTextPart = false;
44
+ for (const part of message.content) {
45
+ if (part.type === "text") {
46
+ hasTextPart = true;
47
+ text += part.text;
48
+ }
49
+ }
50
+ return hasTextPart ? text : undefined;
51
+ }
52
+ function terminatingStructuredOutputText(message, terminatingStructuredOutputCallIds) {
53
+ if (!terminatingStructuredOutputCallIds.has(message.toolCallId))
54
+ return undefined;
55
+ return textFromToolResult(message);
56
+ }
28
57
  /**
29
58
  * Run in print (single-shot) mode.
30
59
  * Sends prompts to the agent and outputs the result.
@@ -36,6 +65,7 @@ export async function runPrintMode(runtimeHost, options) {
36
65
  let activePromptHadCommandError = false;
37
66
  let session = runtimeHost.session;
38
67
  let unsubscribe;
68
+ const terminatingStructuredOutputCallIds = new Set();
39
69
  let disposed = false;
40
70
  const signalCleanupHandlers = [];
41
71
  const disposeRuntime = async () => {
@@ -116,6 +146,9 @@ export async function runPrintMode(runtimeHost, options) {
116
146
  });
117
147
  unsubscribe?.();
118
148
  unsubscribe = session.subscribe((event) => {
149
+ if (isTerminatingStructuredOutputEvent(event, (name) => session.getToolDefinition(name))) {
150
+ terminatingStructuredOutputCallIds.add(event.toolCallId);
151
+ }
119
152
  if (mode === "json") {
120
153
  writeRawStdout(`${JSON.stringify(event)}\n`);
121
154
  }
@@ -161,6 +194,12 @@ export async function runPrintMode(runtimeHost, options) {
161
194
  writeRawStdout(`${text}\n`);
162
195
  }
163
196
  }
197
+ else if (lastMessage?.role === "toolResult") {
198
+ const text = terminatingStructuredOutputText(lastMessage, terminatingStructuredOutputCallIds);
199
+ if (text !== undefined) {
200
+ writeRawStdout(`${text}\n`);
201
+ }
202
+ }
164
203
  }
165
204
  return exitCode;
166
205
  }
@@ -1 +1 @@
1
- {"version":3,"file":"print-mode.js","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent } from \"@earendil-works/pi-ai\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.ts\";\nimport type { ExtensionError } from \"../core/extensions/index.ts\";\nimport type { CustomMessage } from \"../core/messages.ts\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.ts\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.ts\";\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n}\n\nfunction isCommandExtensionError(error: ExtensionError): boolean {\n\treturn error.event === \"command\" || error.extensionPath.startsWith(\"command:\");\n}\n\nfunction displayableCustomText(message: CustomMessage): string | undefined {\n\tif (message.display !== true) return undefined;\n\tif (typeof message.content === \"string\") return message.content;\n\n\tlet text = \"\";\n\tlet hasTextPart = false;\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") {\n\t\t\thasTextPart = true;\n\t\t\ttext += part.text;\n\t\t}\n\t}\n\n\treturn hasTextPart ? text : undefined;\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(runtimeHost: AgentSessionRuntime, options: PrintModeOptions): Promise<number> {\n\tconst { mode, messages = [], initialMessage, initialImages } = options;\n\tlet exitCode = 0;\n\tlet suppressFinalOutput = false;\n\tlet activePromptHadCommandError = false;\n\tlet session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet disposed = false;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tawait runtimeHost.dispose();\n\t};\n\n\tconst registerSignalHandlers = (): void => {\n\t\tconst signals: NodeJS.Signals[] = [\"SIGTERM\"];\n\t\tif (process.platform !== \"win32\") {\n\t\t\tsignals.push(\"SIGHUP\");\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst handler = () => {\n\t\t\t\tkillTrackedDetachedChildren();\n\t\t\t\tvoid disposeRuntime().finally(() => {\n\t\t\t\t\tprocess.exit(signal === \"SIGHUP\" ? 129 : 143);\n\t\t\t\t});\n\t\t\t};\n\t\t\tprocess.on(signal, handler);\n\t\t\tsignalCleanupHandlers.push(() => process.off(signal, handler));\n\t\t}\n\t};\n\n\tregisterSignalHandlers();\n\n\tconst promptWithScopedCommandSuppression = async (\n\t\ttext: string,\n\t\tpromptOptions?: { images?: ImageContent[] },\n\t): Promise<void> => {\n\t\tactivePromptHadCommandError = false;\n\t\ttry {\n\t\t\tif (promptOptions === undefined) {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} else {\n\t\t\t\tawait session.prompt(text, promptOptions);\n\t\t\t}\n\t\t} finally {\n\t\t\t// Final-output suppression is scoped to the most recent prompt so a\n\t\t\t// later successful prompt in the same invocation can still print its\n\t\t\t// result. The non-zero exit code remains sticky across prompts.\n\t\t\tsuppressFinalOutput = activePromptHadCommandError;\n\t\t}\n\t};\n\n\tconst rebindSession = async (): Promise<void> => {\n\t\tsession = runtimeHost.session;\n\t\tawait session.bindExtensions({\n\t\t\tmode: mode === \"json\" ? \"json\" : \"print\",\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (newSessionOptions) => runtimeHost.newSession(newSessionOptions),\n\t\t\t\tfork: async (entryId, forkOptions) => {\n\t\t\t\t\tconst result = await runtimeHost.fork(entryId, forkOptions);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, navigateOptions) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: navigateOptions?.summarize,\n\t\t\t\t\t\tcustomInstructions: navigateOptions?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: navigateOptions?.replaceInstructions,\n\t\t\t\t\t\tlabel: navigateOptions?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, switchOptions) => {\n\t\t\t\t\treturn runtimeHost.switchSession(sessionPath, switchOptions);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\tconst isCommandError = isCommandExtensionError(err);\n\t\t\t\tif (isCommandError) exitCode = 1;\n\t\t\t\tactivePromptHadCommandError = activePromptHadCommandError || isCommandError;\n\t\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t\t}\n\t\t});\n\t};\n\n\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\n\t});\n\n\ttry {\n\t\tif (mode === \"json\") {\n\t\t\tconst header = session.sessionManager.getHeader();\n\t\t\tif (header) {\n\t\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t\t}\n\t\t}\n\n\t\tawait rebindSession();\n\n\t\tif (initialMessage) {\n\t\t\tawait promptWithScopedCommandSuppression(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait promptWithScopedCommandSuppression(message);\n\t\t}\n\n\t\tif (mode === \"text\" && !suppressFinalOutput) {\n\t\t\tconst state = session.state;\n\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\texitCode = 1;\n\t\t\t\t} else {\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (lastMessage?.role === \"custom\") {\n\t\t\t\tconst text = displayableCustomText(lastMessage as CustomMessage);\n\t\t\t\tif (text !== undefined) {\n\t\t\t\t\twriteRawStdout(`${text}\\n`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn exitCode;\n\t} catch (error: unknown) {\n\t\tconsole.error(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t} finally {\n\t\tfor (const cleanup of signalCleanupHandlers) {\n\t\t\tcleanup();\n\t\t}\n\t\tawait disposeRuntime();\n\t\tawait flushRawStdout();\n\t}\n}\n"]}
1
+ {"version":3,"file":"print-mode.js","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent, ToolResultMessage } from \"@earendil-works/pi-ai\";\nimport type { AgentSessionEvent } from \"../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.ts\";\nimport type { ExtensionError } from \"../core/extensions/index.ts\";\nimport type { ToolDefinition } from \"../core/extensions/types.ts\";\nimport type { CustomMessage } from \"../core/messages.ts\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.ts\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.ts\";\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n}\n\nfunction isCommandExtensionError(error: ExtensionError): boolean {\n\treturn error.event === \"command\" || error.extensionPath.startsWith(\"command:\");\n}\n\nfunction displayableCustomText(message: CustomMessage): string | undefined {\n\tif (message.display !== true) return undefined;\n\tif (typeof message.content === \"string\") return message.content;\n\n\tlet text = \"\";\n\tlet hasTextPart = false;\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") {\n\t\t\thasTextPart = true;\n\t\t\ttext += part.text;\n\t\t}\n\t}\n\n\treturn hasTextPart ? text : undefined;\n}\n\ntype MaybeTerminatingToolResult = {\n\tterminate?: boolean;\n};\n\ntype ToolExecutionEndSessionEvent = Extract<AgentSessionEvent, { type: \"tool_execution_end\" }>;\n\ntype GetToolDefinition = (name: string) => ToolDefinition | undefined;\n\nfunction isStructuredOutputTool(getToolDefinition: GetToolDefinition, name: string): boolean {\n\treturn getToolDefinition(name)?.structuredOutput === true;\n}\n\nfunction isTerminatingStructuredOutputEvent(\n\tevent: AgentSessionEvent,\n\tgetToolDefinition: GetToolDefinition,\n): event is ToolExecutionEndSessionEvent {\n\tif (event.type !== \"tool_execution_end\") return false;\n\tif (!isStructuredOutputTool(getToolDefinition, event.toolName)) return false;\n\tif (event.isError) return false;\n\tconst result = event.result as MaybeTerminatingToolResult | undefined;\n\treturn result?.terminate === true;\n}\n\nfunction textFromToolResult(message: ToolResultMessage): string | undefined {\n\tlet text = \"\";\n\tlet hasTextPart = false;\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") {\n\t\t\thasTextPart = true;\n\t\t\ttext += part.text;\n\t\t}\n\t}\n\treturn hasTextPart ? text : undefined;\n}\n\nfunction terminatingStructuredOutputText(\n\tmessage: ToolResultMessage,\n\tterminatingStructuredOutputCallIds: ReadonlySet<string>,\n): string | undefined {\n\tif (!terminatingStructuredOutputCallIds.has(message.toolCallId)) return undefined;\n\treturn textFromToolResult(message);\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(runtimeHost: AgentSessionRuntime, options: PrintModeOptions): Promise<number> {\n\tconst { mode, messages = [], initialMessage, initialImages } = options;\n\tlet exitCode = 0;\n\tlet suppressFinalOutput = false;\n\tlet activePromptHadCommandError = false;\n\tlet session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tconst terminatingStructuredOutputCallIds = new Set<string>();\n\tlet disposed = false;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tawait runtimeHost.dispose();\n\t};\n\n\tconst registerSignalHandlers = (): void => {\n\t\tconst signals: NodeJS.Signals[] = [\"SIGTERM\"];\n\t\tif (process.platform !== \"win32\") {\n\t\t\tsignals.push(\"SIGHUP\");\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst handler = () => {\n\t\t\t\tkillTrackedDetachedChildren();\n\t\t\t\tvoid disposeRuntime().finally(() => {\n\t\t\t\t\tprocess.exit(signal === \"SIGHUP\" ? 129 : 143);\n\t\t\t\t});\n\t\t\t};\n\t\t\tprocess.on(signal, handler);\n\t\t\tsignalCleanupHandlers.push(() => process.off(signal, handler));\n\t\t}\n\t};\n\n\tregisterSignalHandlers();\n\n\tconst promptWithScopedCommandSuppression = async (\n\t\ttext: string,\n\t\tpromptOptions?: { images?: ImageContent[] },\n\t): Promise<void> => {\n\t\tactivePromptHadCommandError = false;\n\t\ttry {\n\t\t\tif (promptOptions === undefined) {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} else {\n\t\t\t\tawait session.prompt(text, promptOptions);\n\t\t\t}\n\t\t} finally {\n\t\t\t// Final-output suppression is scoped to the most recent prompt so a\n\t\t\t// later successful prompt in the same invocation can still print its\n\t\t\t// result. The non-zero exit code remains sticky across prompts.\n\t\t\tsuppressFinalOutput = activePromptHadCommandError;\n\t\t}\n\t};\n\n\tconst rebindSession = async (): Promise<void> => {\n\t\tsession = runtimeHost.session;\n\t\tawait session.bindExtensions({\n\t\t\tmode: mode === \"json\" ? \"json\" : \"print\",\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (newSessionOptions) => runtimeHost.newSession(newSessionOptions),\n\t\t\t\tfork: async (entryId, forkOptions) => {\n\t\t\t\t\tconst result = await runtimeHost.fork(entryId, forkOptions);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, navigateOptions) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: navigateOptions?.summarize,\n\t\t\t\t\t\tcustomInstructions: navigateOptions?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: navigateOptions?.replaceInstructions,\n\t\t\t\t\t\tlabel: navigateOptions?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, switchOptions) => {\n\t\t\t\t\treturn runtimeHost.switchSession(sessionPath, switchOptions);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\tconst isCommandError = isCommandExtensionError(err);\n\t\t\t\tif (isCommandError) exitCode = 1;\n\t\t\t\tactivePromptHadCommandError = activePromptHadCommandError || isCommandError;\n\t\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (isTerminatingStructuredOutputEvent(event, (name) => session.getToolDefinition(name))) {\n\t\t\t\tterminatingStructuredOutputCallIds.add(event.toolCallId);\n\t\t\t}\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t\t}\n\t\t});\n\t};\n\n\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\n\t});\n\n\ttry {\n\t\tif (mode === \"json\") {\n\t\t\tconst header = session.sessionManager.getHeader();\n\t\t\tif (header) {\n\t\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t\t}\n\t\t}\n\n\t\tawait rebindSession();\n\n\t\tif (initialMessage) {\n\t\t\tawait promptWithScopedCommandSuppression(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait promptWithScopedCommandSuppression(message);\n\t\t}\n\n\t\tif (mode === \"text\" && !suppressFinalOutput) {\n\t\t\tconst state = session.state;\n\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\texitCode = 1;\n\t\t\t\t} else {\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (lastMessage?.role === \"custom\") {\n\t\t\t\tconst text = displayableCustomText(lastMessage as CustomMessage);\n\t\t\t\tif (text !== undefined) {\n\t\t\t\t\twriteRawStdout(`${text}\\n`);\n\t\t\t\t}\n\t\t\t} else if (lastMessage?.role === \"toolResult\") {\n\t\t\t\tconst text = terminatingStructuredOutputText(lastMessage as ToolResultMessage, terminatingStructuredOutputCallIds);\n\t\t\t\tif (text !== undefined) {\n\t\t\t\t\twriteRawStdout(`${text}\\n`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn exitCode;\n\t} catch (error: unknown) {\n\t\tconsole.error(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t} finally {\n\t\tfor (const cleanup of signalCleanupHandlers) {\n\t\t\tcleanup();\n\t\t}\n\t\tawait disposeRuntime();\n\t\tawait flushRawStdout();\n\t}\n}\n"]}
@@ -13,6 +13,7 @@ See these complete provider examples:
13
13
 
14
14
  - [`examples/extensions/custom-provider-anthropic/`](https://github.com/bastani-inc/atomic/tree/main/packages/coding-agent/examples/extensions/custom-provider-anthropic)
15
15
  - [`examples/extensions/custom-provider-gitlab-duo/`](https://github.com/bastani-inc/atomic/tree/main/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo)
16
+ - Built-in reference: `packages/cursor/` registers the experimental first-party Cursor provider with OAuth, model mapping, and a native `streamSimple` adapter without a local proxy.
16
17
 
17
18
  ## Table of Contents
18
19
 
@@ -1817,7 +1817,7 @@ pi.registerTool({
1817
1817
 
1818
1818
  **Signaling errors:** To mark a tool execution as failed (sets `isError: true` on the result and reports it to the LLM), throw an error from `execute`. Returning a value never sets the error flag regardless of what properties you include in the return object.
1819
1819
 
1820
- **Early termination:** Return `terminate: true` from `execute()` to hint that the automatic follow-up LLM call should be skipped after the current tool batch. This only takes effect when every finalized tool result in that batch is terminating. See [examples/extensions/structured-output.ts](https://github.com/bastani-inc/atomic/blob/main/packages/coding-agent/examples/extensions/structured-output.ts) for a minimal example where the agent ends on a final structured-output tool call.
1820
+ **Early termination:** Return `terminate: true` from `execute()` to hint that the automatic follow-up LLM call should be skipped after the current tool batch. This only takes effect when every finalized tool result in that batch is terminating. Atomic does not register `structured_output` in normal agent sessions by default; use `createStructuredOutputTool({ schema, capture, output, name })` when an extension, SDK session, workflow stage, or subagent runtime needs a schema-backed final-answer tool. The factory requires a top-level object tool-argument schema, uses that schema as the tool parameters directly, captures the flat tool arguments, and terminates the turn without a `{ value: ... }` wrapper. Wrap array or primitive final values in explicit object fields such as `{ items: [...] }` or `{ value: ... }`; in text print mode, a terminating result from a factory-created structured-output tool is emitted to stdout as the final response, including custom names such as `final_decision`. Hand-rolled terminating tools are not treated as printable structured-output results unless they opt into the same tool-definition metadata. Large final JSON stays inline for this tool instead of being redirected to `<persisted-output>`, preserving the machine-readable final-answer contract. Custom factory names are opt-in tools: if you register `final_decision`, include `final_decision` in any explicit `tools` allowlist; if you register the default `structured_output` name, it is available only to that session/runtime.
1821
1821
 
1822
1822
  ```typescript
1823
1823
  // Correct: throw to signal an error
@@ -2592,7 +2592,7 @@ All examples in [examples/extensions/](https://github.com/bastani-inc/atomic/tre
2592
2592
  | `questionnaire.ts` | Multi-step wizard tool | `registerTool`, `ui.custom` |
2593
2593
  | `todo.ts` | Stateful tool with persistence | `registerTool`, `appendEntry`, `renderResult`, session events |
2594
2594
  | `dynamic-tools.ts` | Register tools after startup and during commands | `registerTool`, `session_start`, `registerCommand` |
2595
- | `structured-output.ts` | Final structured-output tool with `terminate: true` | `registerTool`, terminating tool results |
2595
+ | `structured-output.ts` | Opt-in schema-specific `structured_output` tool using the canonical factory | `createStructuredOutputTool`, `registerTool`, terminating tool results |
2596
2596
  | `truncated-tool.ts` | Output truncation example | `registerTool`, `truncateHead` |
2597
2597
  | `tool-override.ts` | Override built-in read tool | `registerTool` (same name as built-in) |
2598
2598
  | **Commands** |||
package/docs/models.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Add custom providers and models (Ollama, vLLM, LM Studio, proxies) via `~/.atomic/agent/models.json` (legacy `~/.pi/agent/models.json` is also read).
4
4
 
5
+ Built-in subscription providers such as Cursor (experimental) are selected with the same `provider/model` syntax, for example `cursor/composer-2`. Cursor is text-only in the initial experimental implementation; live private-API model metadata may fall back to estimated labels. Because Cursor support targets undocumented private endpoints with Cursor CLI-compatible headers, maintainers and users should explicitly accept the risk that it may conflict with Cursor's terms, break without notice, or affect the Cursor account used to authenticate.
6
+
5
7
  ## Table of Contents
6
8
 
7
9
  - [Minimal Example](#minimal-example)
package/docs/packages.md CHANGED
@@ -52,6 +52,8 @@ atomic -e npm:@foo/bar
52
52
  atomic -e git:github.com/user/repo
53
53
  ```
54
54
 
55
+ For local directories, `-e <dir>` also borrows project-local Atomic resources under `<dir>/.atomic`, legacy `<dir>/.pi`, and `<dir>/.agents/skills` when present. Because borrowed extensions and workflows can execute code, Atomic resolves trust for that extension source before loading those borrowed project-local resources.
56
+
55
57
  ## Package Sources
56
58
 
57
59
  Atomic accepts three source types in settings and `atomic install`.
@@ -113,7 +115,7 @@ atomic install git:git@github.com:user/repo@v1.0.0
113
115
  ./relative/path/to/package
114
116
  ```
115
117
 
116
- Local paths point to files or directories on disk and are added to settings without copying. Relative paths are resolved against the settings file they appear in. If the path is a file, it loads as a single extension. If it is a directory, Atomic loads resources using package rules.
118
+ Local paths point to files or directories on disk and are added to settings without copying. Relative paths are resolved against the settings file they appear in. If the path is a file, it loads as a single extension. If it is a directory, Atomic loads resources using package rules. Temporary local directories supplied with `-e` may also expose `.atomic`/`.pi` project-local resources and `.agents/skills` after the extension source is trusted.
117
119
 
118
120
  ## Creating an Atomic Package
119
121
 
package/docs/providers.md CHANGED
@@ -18,6 +18,7 @@ Use `/login` in interactive mode, then select a provider:
18
18
  - ChatGPT Plus/Pro (Codex)
19
19
  - Claude Pro/Max
20
20
  - GitHub Copilot
21
+ - Cursor (experimental)
21
22
 
22
23
  Use `/logout` to clear credentials. Tokens are stored in `~/.atomic/agent/auth.json` and auto-refresh when expired.
23
24
 
@@ -39,6 +40,20 @@ Anthropic subscription auth is active for Claude Pro/Max accounts. Third-party h
39
40
  - Press Enter for github.com, or enter your GitHub Enterprise Server domain
40
41
  - If you get "model not supported", enable it in VS Code: Copilot Chat → model selector → select model → "Enable"
41
42
 
43
+ ### Cursor (experimental)
44
+
45
+ Cursor support is bundled as the first-party `@bastani/cursor` extension and appears in `/login` as **Cursor (experimental)**. It uses Cursor's browser PKCE flow and stores OAuth credentials in `~/.atomic/agent/auth.json`; do not paste Cursor tokens into environment variables, command-line arguments, or custom proxies. Atomic identifies as a Cursor CLI-compatible client against private endpoints; maintainers and users should explicitly accept that this may conflict with Cursor's terms of service, stop working without notice, or affect the Cursor account used to authenticate.
46
+
47
+ Current limitations:
48
+
49
+ - Cursor uses private, undocumented APIs and Cursor CLI-compatible headers. Atomic keeps the transport isolated and labels this provider experimental because Cursor may change the protocol without notice; use may conflict with Cursor's terms of service or provider-side account policies.
50
+ - Text input is supported; vision/image input is rejected with a clear error.
51
+ - Model metadata is cached token-free in `~/.atomic/agent/cursor-model-catalog.json` and can be used at startup before fresh credentials are available. Estimated labels are used only when no valid cache exists and allowed live `GetUsableModels` discovery failures occur; refresh-time discovery is best-effort so rotated credentials are still persisted.
52
+ - The implementation avoids a localhost proxy and keeps credentials OAuth-only. Cursor's HTTP/2 transport uses the bundled `@bastani/atomic-natives` Rust/N-API client, so it does not require Node.js on `PATH`. The native client currently opens request-scoped HTTP/2 sessions; pooling may be added in a future release.
53
+ - Cursor request encoding intentionally omits a `previousWorkspaceUris` current-directory entry by default so local absolute working-directory paths are not sent as workspace context. HTTP/2 Connect request/framing code is isolated, buffered across arbitrary chunks, tested with injected fakes, and uses a minimal production protobuf codec with field-order-independent exec ids, protobuf `Value` plus raw UTF-8/JSON tool arguments, historical tool-result correlation, checkpoint token-details parsing, paused-stream abort/idle cleanup, catalog-aware fast/thinking model grouping, and credential/PKCE-redacted protocol errors.
54
+
55
+ Select models as `cursor/<model-id>` (default: `cursor/composer-2`).
56
+
42
57
  ## API Keys
43
58
 
44
59
  ### Environment Variables or Auth File
package/docs/sdk.md CHANGED
@@ -622,6 +622,62 @@ Custom tools passed via `customTools` are combined with extension-registered too
622
622
 
623
623
  If you pass `tools`, include each custom or extension tool name you want enabled, for example `tools: ["read", "bash", "my_tool"]`. Use `excludedTools` to remove a custom or extension tool by name from the final exposed set.
624
624
 
625
+ #### Structured output final results
626
+
627
+ `structured_output` is not registered in normal agent sessions by default. Add it only when a caller needs a machine-readable final-answer contract by registering the exported factory as a custom tool:
628
+
629
+ ```typescript
630
+ import { Type, type Static } from "typebox";
631
+ import {
632
+ createAgentSession,
633
+ createStructuredOutputTool,
634
+ type StructuredOutputCapture,
635
+ } from "@bastani/atomic";
636
+
637
+ const DecisionSchema = Type.Object({
638
+ approved: Type.Boolean(),
639
+ findings: Type.Array(Type.String()),
640
+ }, { additionalProperties: false });
641
+
642
+ type Decision = Static<typeof DecisionSchema>;
643
+ const capture: StructuredOutputCapture<Decision> = {
644
+ called: false,
645
+ value: undefined,
646
+ };
647
+
648
+ const structuredOutput = createStructuredOutputTool({
649
+ schema: DecisionSchema,
650
+ capture,
651
+ });
652
+
653
+ const { session } = await createAgentSession({
654
+ customTools: [structuredOutput],
655
+ });
656
+ ```
657
+
658
+ The tool parameters are exactly `DecisionSchema`: the model calls `structured_output({ approved, findings })`, not `structured_output({ value: { approved, findings } })`. A successful call stores the flat params in `capture.value`, returns them as tool `details`, and sets `terminate: true` so there is no extra follow-up assistant turn. When an `output` file sink is configured, the factory writes the same flat schema-valid params to `output.json` and writes call metadata (`toolName`, `toolCallId`, `success`, `terminate`) to a private `output.meta.json` sidecar for finality-checked parent readback. Structured-output schemas must be top-level object tool-argument schemas; wrap array or primitive final values in object fields such as `{ items: [...] }` or `{ value: ... }`. Structured-output tool definitions opt out of oversized-result persistence, so large final JSON remains inline as the machine-readable result instead of being replaced by a `<persisted-output>` pointer; text print mode also emits the terminating JSON for factory-created tools, including custom names.
659
+
660
+ Custom tool names are supported, and the prompt metadata follows the configured name. If you use a custom name such as `final_decision`, include that name in any explicit `tools` allowlist. If the standard `structured_output` name is required, register the factory with its default name:
661
+
662
+ ```typescript
663
+ const finalDecision = createStructuredOutputTool({
664
+ name: "final_decision",
665
+ schema: DecisionSchema,
666
+ capture,
667
+ });
668
+ // The model is prompted to call final_decision exactly once, not structured_output.
669
+
670
+ await createAgentSession({
671
+ customTools: [finalDecision],
672
+ tools: ["final_decision"], // only this tool is enabled
673
+ });
674
+
675
+ await createAgentSession({
676
+ customTools: [createStructuredOutputTool({ schema: DecisionSchema, capture })],
677
+ // Registers the standard structured_output tool for this session only.
678
+ });
679
+ ```
680
+
625
681
  > See [examples/sdk/05-tools.ts](https://github.com/bastani-inc/atomic/blob/main/packages/coding-agent/examples/sdk/05-tools.ts)
626
682
 
627
683
  ### Extensions
@@ -1182,6 +1238,9 @@ createEventBus
1182
1238
 
1183
1239
  // Helpers
1184
1240
  defineTool
1241
+ STRUCTURED_OUTPUT_TOOL_NAME
1242
+ createStructuredOutputTool
1243
+ createStructuredOutputCapture
1185
1244
  getAgentDir
1186
1245
  getPackageDir
1187
1246
  getReadmePath
@@ -1203,6 +1262,8 @@ type CreateAgentSessionOptions
1203
1262
  type CreateAgentSessionResult
1204
1263
  type BashCommandPolicy
1205
1264
  type BashCommandRule
1265
+ type StructuredOutputCapture
1266
+ type StructuredOutputToolOptions
1206
1267
  type ExtensionFactory
1207
1268
  type ExtensionAPI
1208
1269
  type ToolDefinition
package/docs/security.md CHANGED
@@ -21,7 +21,7 @@ Trusting a project allows Atomic to load trust-gated project inputs, including:
21
21
  - missing project packages configured through project settings
22
22
  - project-local extensions and project package-managed extensions
23
23
 
24
- Declining trust skips protected resources. Atomic also skips project-local `AGENTS.md` and `CLAUDE.md` context-file discovery while the project is untrusted; global context and explicitly supplied CLI resources remain available. Before trust is resolved, Atomic only loads user/global extensions and explicit CLI `-e` extensions so those trusted extensions can handle the `project_trust` event; the first extension that returns a yes/no decision owns the decision.
24
+ Declining trust skips protected resources. Atomic also skips project-local `AGENTS.md` and `CLAUDE.md` context-file discovery while the project is untrusted; global context and explicitly supplied CLI resources remain available. Before trust is resolved, Atomic only loads user/global extensions and explicit CLI `-e` package-level extensions so those trusted extensions can handle the `project_trust` event; the first extension that returns a yes/no decision owns the decision. When `-e <dir>` discovers project-local resources borrowed from that directory's `.atomic` or legacy `.pi` config, or from `.agents/skills`, Atomic resolves trust for that extension source before loading those borrowed resources, because borrowed extensions and workflows can execute code with the Atomic process permissions.
25
25
 
26
26
  Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, `defaultProjectTrust: "ask"` and `"never"` ignore such resources, while `"always"` trusts them. Use `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
27
27
 
package/docs/subagents.md CHANGED
@@ -144,6 +144,27 @@ You are a read-only inspector. Inspect the current diff, cite evidence with file
144
144
 
145
145
  Use `completionGuard: false` sparingly. It opts a user-authored agent out of automatic completion-guard reminders and is intended for read-only agents whose prompt already prevents premature completion. Do not use it to bypass required implementation or validation work.
146
146
 
147
+ If an agent or chain step uses an explicit empty `tools: []` allowlist together with `outputSchema`, Atomic starts the child with only `structured_output` enabled for the required final answer. It does not omit `--tools` and accidentally restore default tools. Path-only tool entries remain extension paths and do not create a builtin allowlist by themselves. The child prompt-runtime extension is loaded before user/tool extensions so its schema-backed `structured_output` tool is registered before explicit allowlists are applied.
148
+
149
+ ## Structured output schemas
150
+
151
+ Chain and parallel steps can declare an `outputSchema` when the parent needs reliable machine-readable handoff data. Atomic passes that schema to the child as a schema-specific `structured_output` tool backed by the shared Atomic factory. The child must finish by calling `structured_output` exactly once with arguments that match the schema directly:
152
+
153
+ ```ts
154
+ structured_output({
155
+ files: ["src/auth.ts"],
156
+ risks: ["missing regression test"],
157
+ })
158
+ ```
159
+
160
+ `outputSchema` itself must be a top-level object schema because the schema is used directly as the tool's argument contract. Wrap array or primitive handoff values in an explicit object field, such as `{ items: [...] }` or `{ value: ... }`.
161
+
162
+ Do not wrap the payload as `structured_output({ value: ... })` unless your own schema explicitly defines a top-level `value` field. The child runtime writes the flat schema-valid params to `output.json` and call metadata (`toolName`, `toolCallId`, `success`, `terminate`) to the `output.meta.json` sidecar; the parent validates both files before checking transcript finality. Prose-only completion, missing tool calls, missing sidecar metadata, invalid schema data, duplicate structured-output calls, stale output-file captures, sibling tool calls in the same assistant batch, and any assistant/custom/tool-result messages after the successful structured-output result fail the step. The parent accepts cross-process captures only when the transcript proves the same `structured_output` call was the final successful terminating action, then returns the validated flat value as `result.structuredOutput` and in named-chain references under `outputs.name.structured`.
163
+
164
+ Children without `outputSchema` do not receive `structured_output` from Atomic's default tool registry. They can still use a custom extension-provided terminating tool if you explicitly add one, but validated `result.structuredOutput` is reserved for schema-backed `outputSchema` captures.
165
+
166
+ Dynamic fanout `collect.outputSchema` is different: it validates the collected result array after child runs finish, not a child tool-call argument object. Collection schemas remain general JSON Schemas and may use array roots such as `{ "type": "array", "minItems": 1 }`.
167
+
147
168
  ## Fallback models
148
169
 
149
170
  Agents can define ordered `fallbackModels` for retryable provider or model failures such as rate limits, quota/auth problems, unavailable models, network timeouts, or 5xx errors. Atomic tries the requested primary model first, then configured fallbacks, and finally appends the current user-selected model as the last fallback candidate when available.
package/docs/usage.md CHANGED
@@ -160,6 +160,8 @@ In print mode, Atomic also reads piped stdin and merges it into the initial prom
160
160
  cat README.md | atomic -p "Summarize this text"
161
161
  ```
162
162
 
163
+ When a print-mode turn correctly finishes by calling an opt-in terminating structured-output tool created with `createStructuredOutputTool` (for example from an extension, SDK caller, or workflow item with a schema), stdout contains the tool result's JSON text even though there is no follow-up assistant prose. This also works for custom factory names such as `final_decision`. Large final JSON from structured-output tools is preserved inline rather than redirected to a `<persisted-output>` pointer. Non-terminating or unrelated tool results are not printed as the final response.
164
+
163
165
  ### Model Options
164
166
 
165
167
  | Option | Description |
package/docs/workflows.md CHANGED
@@ -9,7 +9,7 @@ Use a workflow when a task should be repeatable, inspectable, resumable, or spli
9
9
  **Key capabilities:**
10
10
  - **Tracked stages** - Name each step and inspect it in workflow status and graph views
11
11
  - **Parallel branches** - Run independent research, review, or implementation branches concurrently
12
- - **Context handoffs** - Pass summaries, artifacts, files, and structured outputs between stages
12
+ - **Context handoffs** - Pass summaries, artifacts, files, and schema-backed structured results between stages
13
13
  - **Human input** - Pause for `ctx.ui.input`, `confirm`, `select`, `editor`, or custom TUI widget decisions during a run
14
14
  - **Resumable control** - Interrupt, pause, resume, attach to, or kill workflow runs
15
15
  - **Artifacts** - Save large outputs to files instead of pushing everything through model context
@@ -1100,7 +1100,7 @@ Control-signal probing is fail-closed. When the executor inspects an arbitrary t
1100
1100
  - Avoid workflow-specific or stage-specific vocabulary that is not explained inside the current prompt.
1101
1101
  - Use clear software engineering terminology in self-described prompts.
1102
1102
  - Avoid hard-coded regular expressions for condition matching when gating reviews or model outputs.
1103
- - Prefer structured output schemas for review/gate decisions whenever model output needs to be evaluated.
1103
+ - Prefer schema-backed workflow stages (`ctx.stage(..., { schema })`, `ctx.chain` items, or `ctx.parallel` items) for review/gate decisions whenever model output needs to be evaluated; Atomic injects the canonical `structured_output` tool only for those schema-enabled items.
1104
1104
  - Treat atomic workflow units as language model stages, not deterministic tools.
1105
1105
  - When deterministic gates are needed, create small dedicated stages that instruct a model to run a specific tool or perform a specific check. This keeps gates adaptive to the current codebase while preserving explicit workflow structure.
1106
1106
 
@@ -1473,9 +1473,12 @@ Common task/stage options include:
1473
1473
  - `context: "fresh" | "fork"`, `forkFromSessionFile`
1474
1474
  - `model`, `fallbackModels`, `thinkingLevel`, `scopedModels`, `modelRegistry` — `model` and each `fallbackModels` entry accept a `model_name:thinking_effort` reasoning suffix; the standalone `thinkingLevel` is deprecated (see [Reasoning levels](#reasoning-levels))
1475
1475
  - `tools`, `noTools`, `customTools`, `mcp: { allow?: string[], deny?: string[] }`, `bashPolicy`
1476
+ - `schema` for a structured final answer from this workflow item
1476
1477
  - `output`, `outputMode`, `reads`, `worktree`, `gitWorktreeDir`, `baseBranch`, `maxOutput`, `artifacts`, `sessionDir`, `cwd`, `agentDir`
1477
1478
  - advanced host-supplied SDK seams: `authStorage`, `resourceLoader`, `sessionManager`, `settingsManager`, `sessionStartEvent`
1478
1479
 
1480
+ `schema` is opt-in. When a `ctx.stage` call, `ctx.task` call, `ctx.chain` item, or `ctx.parallel` item includes a top-level object TypeBox/JSON Schema, Atomic registers a schema-specific `structured_output` tool for that item only, appends final-answer instructions, and requires the stage to finish by calling the tool exactly once. The prompt result is the parsed structured value for `ctx.stage(..., { schema }).prompt(...)`; task/chain/parallel results also include `result.structured` and keep `result.text` as formatted JSON for handoffs. Because the result contract is single-use, a schema-backed `StageContext` supports one `prompt()` call; create a new `ctx.stage(..., { schema })` for each additional structured prompt. If the item also uses an explicit `tools` allowlist, Atomic automatically adds `structured_output` to that allowlist. Items without `schema` do not receive `structured_output` from the normal tool registry.
1481
+
1479
1482
  `bashPolicy` scopes the built-in `bash` tool for one stage or task. `tools` must still include `"bash"` (or leave it available by default); the policy only narrows command text after the shell tool is exposed. It supports exact strings, `{ prefix }`, command-string `{ glob }`, and `{ regex, flags? }` rules, `default: "allow" | "deny"` (default `"allow"`), `deny` precedence, and `match: "segments" | "whole"` (default `"segments"`). Omitting `bashPolicy`, passing `{}`, or passing a default-allow policy with no `allow`/`deny` rules (including empty arrays or match-only default-allow policies) preserves legacy behavior and does not parse commands; malformed policy shapes such as unknown top-level keys (`denny`, `extra`), non-array `allow`/`deny`, invalid rule objects, invalid regexes, invalid glob bracket ranges, or stateful `g`/`y` regex flags fail closed as `invalid-policy`. Segment mode checks each command in pipelines/chains/substitutions before execution, treats unquoted LF, CRLF, and bare CR as command separators, keeps non-leading Bash `>|` noclobber redirections inside the current command segment, and rejects reserved/compound shell heads, leading redirections, attached command-head redirections, and command heads that are not literal words.
1480
1483
 
1481
1484
  ```ts
@@ -1619,7 +1622,7 @@ Stage prompts should be local contracts, not miniature descriptions of the entir
1619
1622
 
1620
1623
  - the stage's current objective and what is out of scope for this stage
1621
1624
  - the exact files, artifacts, child outputs, or user inputs it may use
1622
- - the expected output format or structured-output tool/schema it must return
1625
+ - the expected output format, or the schema it must return when the workflow item is schema-enabled
1623
1626
  - the checks, tools, or deterministic commands it should run when relevant
1624
1627
  - the success criteria that let this stage stop
1625
1628
 
@@ -1735,9 +1738,9 @@ Build validation into the workflow instead of waiting for a final manual check.
1735
1738
  - reviewer stages: fresh-context reviewers that inspect artifacts and current files
1736
1739
  - LLM-as-judge stages: direct scoring, pairwise comparison, or rubric-based grading for subjective outputs
1737
1740
 
1738
- Prefer structured output schemas or structured-output tools for model review and gate decisions. Do not make correctness depend on brittle regular-expression matching against free-form prose such as “looks good”, “approved”, or “PASS”. A schema with explicit booleans/enums, findings arrays, confidence, evidence fields, and error reporting is easier to validate, replay, and safely default to “not approved” when malformed.
1741
+ Prefer schema-enabled workflow items for model review and gate decisions. `structured_output` is not available to workflow stages through the normal Atomic tool registry; it is injected only when a `ctx.stage`, `ctx.task`, `ctx.chain` item, or `ctx.parallel` item includes `schema`. Structured-output schemas must be top-level object tool-argument schemas, so wrap array or primitive decisions in object fields such as `{ items: [...] }` or `{ value: ... }`; direct JSON invocations of the `workflow` tool must use a schema with `type: "object"` so invalid array/primitive contracts fail at argument validation instead of later in the stage. Terminating `structured_output` JSON is preserved inline even when it exceeds the normal oversized-tool-result threshold, so workflow code can consume the parsed value instead of a `<persisted-output>` pointer. Do not add the old synthetic `{ value: ... }` wrapper around an object payload unless your schema defines that field, and do not make correctness depend on brittle regular-expression matching against free-form prose such as “looks good”, “approved”, or “PASS”. A schema with explicit booleans/enums, findings arrays, confidence, evidence fields, and error reporting is easier to validate, replay, and safely default to “not approved” when malformed.
1739
1742
 
1740
- Use small dedicated model stages for adaptive gates when deterministic code alone cannot decide what to check. For example, a stage can read an artifact, inspect the repo, run a named tool or command, and then emit a structured decision. Keep that stage's prompt narrow: tell it the specific check to perform, the files/tools it may use, and the structured decision it must return.
1743
+ Use small dedicated model stages for adaptive gates when deterministic code alone cannot decide what to check. For example, a stage can read an artifact, inspect the repo, run a named tool or command, and then emit a structured decision by configuring `schema` on that workflow item. Keep that stage's prompt narrow: tell it the specific check to perform, the files/tools it may use, and the structured decision it must return.
1741
1744
 
1742
1745
  When using LLM judges, mitigate bias by defining score anchors, asking for evidence, calibrating against examples, and keeping length/order effects in mind. Track pass rates and failures over time for reusable workflows.
1743
1746
 
@@ -1794,5 +1797,5 @@ Good workflows are information-flow systems, not just prompt sequences. Keep sta
1794
1797
  - Do not call `kill` when the user asks to interrupt or pause resumably.
1795
1798
  - Keep stage names readable because they appear in workflow status and UI.
1796
1799
  - Do not write stage prompts that depend on hidden workflow-wide awareness; make each model stage locally scoped and self-described.
1797
- - Do not parse model gate decisions from ad-hoc prose with regular expressions; use structured output schemas/tools or a focused checking stage that returns a structured decision.
1798
- - Return compact structured output and save large artifacts to files.
1800
+ - Do not parse model gate decisions from ad-hoc prose with regular expressions; configure `schema` on a focused workflow item so Atomic injects the canonical `structured_output` tool for that item.
1801
+ - Return compact structured output for decisions and save large artifacts to files; schema-enabled workflow items preserve final JSON inline, but artifact handoffs should still use files when the next stage does not need the whole payload in context.
@@ -36,7 +36,7 @@ cp permission-gate.ts ~/.atomic/agent/extensions/
36
36
  | `questionnaire.ts` | Multi-question input with tab bar navigation between questions |
37
37
  | `tool-override.ts` | Override built-in tools (e.g., add logging/access control to `read`) |
38
38
  | `dynamic-tools.ts` | Register tools after startup (`session_start`) and at runtime via command, with prompt snippets and tool-specific prompt guidelines |
39
- | `structured-output.ts` | Final structured-output tool that returns `terminate: true` so the agent can end on the tool call |
39
+ | `structured-output.ts` | Opt-in schema-specific `structured_output` tool using Atomic's canonical terminating output factory |
40
40
  | `built-in-tool-renderer.ts` | Custom compact rendering for built-in tools (read, bash, edit, write) while keeping original behavior |
41
41
  | `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
42
42
  | `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "atomic-extension-custom-provider-anthropic",
3
- "version": "0.79.1",
3
+ "version": "0.79.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "atomic-extension-custom-provider-anthropic",
9
- "version": "0.79.1",
9
+ "version": "0.79.3",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "atomic-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.79.1",
4
+ "version": "0.79.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "atomic-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.79.1",
4
+ "version": "0.79.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "atomic-extension-gondolin",
3
- "version": "0.79.1",
3
+ "version": "0.79.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "atomic-extension-gondolin",
9
- "version": "0.79.1",
9
+ "version": "0.79.3",
10
10
  "dependencies": {
11
11
  "@earendil-works/gondolin": "0.12.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "atomic-extension-gondolin",
3
3
  "private": true,
4
- "version": "0.79.1",
4
+ "version": "0.79.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "atomic-extension-sandbox",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "atomic-extension-sandbox",
9
- "version": "1.9.1",
9
+ "version": "1.9.3",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sandbox-runtime": "^0.0.26"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "atomic-extension-sandbox",
3
3
  "private": true,
4
- "version": "1.9.1",
4
+ "version": "1.9.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",