apex-code 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/dist/core/settings-manager.d.ts.map +1 -1
  3. package/dist/core/settings-manager.js +1 -1
  4. package/dist/core/settings-manager.js.map +1 -1
  5. package/dist/core/tools/bash.d.ts +2 -7
  6. package/dist/core/tools/bash.d.ts.map +1 -1
  7. package/dist/core/tools/bash.js +8 -30
  8. package/dist/core/tools/bash.js.map +1 -1
  9. package/dist/core/tools/delegate.d.ts.map +1 -1
  10. package/dist/core/tools/delegate.js +2 -2
  11. package/dist/core/tools/delegate.js.map +1 -1
  12. package/dist/core/tools/find.d.ts.map +1 -1
  13. package/dist/core/tools/find.js +4 -4
  14. package/dist/core/tools/find.js.map +1 -1
  15. package/dist/core/tools/grep.d.ts.map +1 -1
  16. package/dist/core/tools/grep.js +4 -4
  17. package/dist/core/tools/grep.js.map +1 -1
  18. package/dist/core/tools/ls.d.ts.map +1 -1
  19. package/dist/core/tools/ls.js +4 -4
  20. package/dist/core/tools/ls.js.map +1 -1
  21. package/dist/core/tools/read.d.ts.map +1 -1
  22. package/dist/core/tools/read.js +11 -8
  23. package/dist/core/tools/read.js.map +1 -1
  24. package/dist/core/tools/write.d.ts.map +1 -1
  25. package/dist/core/tools/write.js +4 -5
  26. package/dist/core/tools/write.js.map +1 -1
  27. package/dist/extensions/llama/ui.d.ts.map +1 -1
  28. package/dist/extensions/llama/ui.js +3 -2
  29. package/dist/extensions/llama/ui.js.map +1 -1
  30. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  31. package/dist/modes/interactive/components/assistant-message.js +0 -10
  32. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  33. package/dist/modes/interactive/components/autocomplete-source.d.ts +8 -0
  34. package/dist/modes/interactive/components/autocomplete-source.d.ts.map +1 -0
  35. package/dist/modes/interactive/components/autocomplete-source.js +23 -0
  36. package/dist/modes/interactive/components/autocomplete-source.js.map +1 -0
  37. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  38. package/dist/modes/interactive/components/bash-execution.js +6 -4
  39. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  40. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  41. package/dist/modes/interactive/components/bordered-loader.js +1 -1
  42. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  43. package/dist/modes/interactive/components/composer-dock.d.ts +12 -0
  44. package/dist/modes/interactive/components/composer-dock.d.ts.map +1 -0
  45. package/dist/modes/interactive/components/composer-dock.js +24 -0
  46. package/dist/modes/interactive/components/composer-dock.js.map +1 -0
  47. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  48. package/dist/modes/interactive/components/config-selector.js +11 -9
  49. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  50. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  51. package/dist/modes/interactive/components/extension-editor.js +9 -9
  52. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  53. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  54. package/dist/modes/interactive/components/extension-input.js +9 -5
  55. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  56. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  57. package/dist/modes/interactive/components/extension-selector.js +11 -10
  58. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  59. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  60. package/dist/modes/interactive/components/first-time-setup.js +6 -6
  61. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  62. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  63. package/dist/modes/interactive/components/footer.js +8 -3
  64. package/dist/modes/interactive/components/footer.js.map +1 -1
  65. package/dist/modes/interactive/components/keybinding-hints.d.ts +21 -0
  66. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  67. package/dist/modes/interactive/components/keybinding-hints.js +34 -0
  68. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  69. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  70. package/dist/modes/interactive/components/login-dialog.js +27 -23
  71. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  72. package/dist/modes/interactive/components/model-selector.d.ts +1 -1
  73. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  74. package/dist/modes/interactive/components/model-selector.js +14 -18
  75. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  76. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  77. package/dist/modes/interactive/components/oauth-selector.js +7 -6
  78. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  79. package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -1
  80. package/dist/modes/interactive/components/permission-preview.js +3 -3
  81. package/dist/modes/interactive/components/permission-preview.js.map +1 -1
  82. package/dist/modes/interactive/components/prompt-input.d.ts +12 -0
  83. package/dist/modes/interactive/components/prompt-input.d.ts.map +1 -0
  84. package/dist/modes/interactive/components/prompt-input.js +18 -0
  85. package/dist/modes/interactive/components/prompt-input.js.map +1 -0
  86. package/dist/modes/interactive/components/scoped-models-selector.d.ts +1 -1
  87. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  88. package/dist/modes/interactive/components/scoped-models-selector.js +4 -3
  89. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  90. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  91. package/dist/modes/interactive/components/session-selector.js +26 -19
  92. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  93. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  94. package/dist/modes/interactive/components/settings-selector.js +39 -6
  95. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  96. package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -1
  97. package/dist/modes/interactive/components/settings-submenu.js +8 -6
  98. package/dist/modes/interactive/components/settings-submenu.js.map +1 -1
  99. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  100. package/dist/modes/interactive/components/thinking-selector.js +10 -5
  101. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  102. package/dist/modes/interactive/components/tool-execution.d.ts +1 -11
  103. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  104. package/dist/modes/interactive/components/tool-execution.js +9 -38
  105. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  106. package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -1
  107. package/dist/modes/interactive/components/tool-panel.js +7 -2
  108. package/dist/modes/interactive/components/tool-panel.js.map +1 -1
  109. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/tree-selector.js +9 -5
  111. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  112. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -1
  113. package/dist/modes/interactive/components/trust-selector.js +11 -11
  114. package/dist/modes/interactive/components/trust-selector.js.map +1 -1
  115. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  116. package/dist/modes/interactive/components/user-message-selector.js +2 -2
  117. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  118. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  119. package/dist/modes/interactive/components/user-message.js +6 -3
  120. package/dist/modes/interactive/components/user-message.js.map +1 -1
  121. package/dist/modes/interactive/interactive-mode.d.ts +7 -2
  122. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  123. package/dist/modes/interactive/interactive-mode.js +64 -64
  124. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  125. package/dist/modes/print-mode.d.ts.map +1 -1
  126. package/dist/modes/print-mode.js +49 -23
  127. package/dist/modes/print-mode.js.map +1 -1
  128. package/docs/json.md +36 -0
  129. package/docs/settings.md +1 -1
  130. package/npm-shrinkwrap.json +5 -5
  131. package/package.json +2 -2
  132. package/dist/modes/interactive/components/message-spine.d.ts +0 -14
  133. package/dist/modes/interactive/components/message-spine.d.ts.map +0 -1
  134. package/dist/modes/interactive/components/message-spine.js +0 -19
  135. package/dist/modes/interactive/components/message-spine.js.map +0 -1
@@ -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;AAE5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAK5E;;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;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,mBAAmB,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwJ/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 { AgentStopReason } from \"apex-code-agent-core\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.ts\";\nimport { flushRawStdout, waitForRawStdoutBackpressure, writeRawStdout } from \"../core/output-guard.ts\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.ts\";\nimport { toJsonEvent } from \"./json-event.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\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 session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet unsubscribeBackpressure: (() => void) | undefined;\n\tlet disposed = false;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\t// Structured terminal outcome of the last run (spec 2026-09-01-tool-reliability-\n\t// and-execution-budgets.md); surfaced in this mode's native form below.\n\tlet lastStopReason: AgentStopReason | undefined;\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\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\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\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.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\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\tlastStopReason = event.stopReason;\n\t\t\t}\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(toJsonEvent(event))}\\n`);\n\t\t\t}\n\t\t});\n\t\tunsubscribeBackpressure =\n\t\t\tmode === \"json\"\n\t\t\t\t? session.agent.subscribe(async () => {\n\t\t\t\t\t\tawait waitForRawStdoutBackpressure();\n\t\t\t\t\t})\n\t\t\t\t: undefined;\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 session.prompt(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait session.prompt(message);\n\t\t}\n\n\t\tif (mode === \"text\") {\n\t\t\t// A budget stop can settle after a tool result with no assistant message,\n\t\t\t// so it is reported from the structured stop reason rather than inferred\n\t\t\t// from the last message.\n\t\t\tif (lastStopReason?.kind === \"budget-exhausted\") {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Run stopped: the ${lastStopReason.limit.replace(/-/g, \" \")} budget was exhausted (runBudget settings).`,\n\t\t\t\t);\n\t\t\t\texitCode = 1;\n\t\t\t} else {\n\t\t\t\tconst state = session.state;\n\t\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\t\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\t\texitCode = 1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\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,EAAE,MAAM,uBAAuB,CAAC;AAE5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAK5E;;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;AAoDD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,mBAAmB,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmJ/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 { AgentStopReason } from \"apex-code-agent-core\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.ts\";\nimport { flushRawStdout, waitForRawStdoutBackpressure, writeRawStdout } from \"../core/output-guard.ts\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.ts\";\nimport { toJsonEvent } from \"./json-event.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\n/**\n * Terminal status of a non-interactive run, written as the `result` envelope in\n * JSON mode. The vocabulary is `AgentStopReason`'s own, rather than a second one\n * defined here, so the two cannot drift.\n */\ntype PrintRunStatus = AgentStopReason[\"kind\"];\n\ninterface PrintRunOutcome {\n\tstatus: PrintRunStatus;\n\t/** What text mode prints to stderr. Empty when the run completed. */\n\tmessage: string;\n}\n\n/**\n * Resolve the run's terminal outcome.\n *\n * `agent_end`'s structured stop reason wins because the loop already applied its\n * documented precedence (aborted > error > budget > completed) to produce it. The\n * settled assistant message is only a fallback, for the runs that end without an\n * `agent_end` reaching this mode at all.\n */\nfunction resolveRunOutcome(\n\tstopReason: AgentStopReason | undefined,\n\tsettled: AssistantMessage | undefined,\n): PrintRunOutcome {\n\tif (stopReason?.kind === \"budget-exhausted\") {\n\t\tconst limit = stopReason.limit.replace(/-/g, \" \");\n\t\treturn {\n\t\t\tstatus: \"budget-exhausted\",\n\t\t\tmessage: `Run stopped: the ${limit} budget was exhausted (runBudget settings).`,\n\t\t};\n\t}\n\tconst failed = (reason: \"aborted\" | \"error\"): PrintRunOutcome => ({\n\t\tstatus: reason,\n\t\tmessage: settled?.errorMessage || `Request ${reason}`,\n\t});\n\tconst completed: PrintRunOutcome = { status: \"completed\", message: \"\" };\n\n\t// A stop reason answers the question by itself, including when it says the run\n\t// completed. Reading the settled message in that case would let one left over\n\t// from an earlier failed turn outrank what the loop concluded.\n\tif (stopReason !== undefined) {\n\t\treturn stopReason.kind === \"aborted\" || stopReason.kind === \"error\" ? failed(stopReason.kind) : completed;\n\t}\n\tif (settled?.stopReason === \"aborted\" || settled?.stopReason === \"error\") {\n\t\treturn failed(settled.stopReason);\n\t}\n\treturn completed;\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 session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet unsubscribeBackpressure: (() => void) | undefined;\n\tlet disposed = false;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\t// Structured terminal outcome of the last run (spec 2026-09-01-tool-reliability-\n\t// and-execution-budgets.md); surfaced in this mode's native form below.\n\tlet lastStopReason: AgentStopReason | undefined;\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\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\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\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.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\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\tlastStopReason = event.stopReason;\n\t\t\t}\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(toJsonEvent(event))}\\n`);\n\t\t\t}\n\t\t});\n\t\tunsubscribeBackpressure =\n\t\t\tmode === \"json\"\n\t\t\t\t? session.agent.subscribe(async () => {\n\t\t\t\t\t\tawait waitForRawStdoutBackpressure();\n\t\t\t\t\t})\n\t\t\t\t: undefined;\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 session.prompt(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait session.prompt(message);\n\t\t}\n\n\t\t// The outcome is resolved once, for every mode. Deciding it inside the text\n\t\t// branch is what let a failed `--mode json` run exit 0: the stream carried the\n\t\t// error and the exit code reported success.\n\t\tconst lastMessage = session.state.messages[session.state.messages.length - 1];\n\t\tconst outcome = resolveRunOutcome(\n\t\t\tlastStopReason,\n\t\t\tlastMessage?.role === \"assistant\" ? (lastMessage as AssistantMessage) : undefined,\n\t\t);\n\t\texitCode = outcome.status === \"completed\" ? 0 : 1;\n\n\t\tif (mode === \"json\") {\n\t\t\twriteRawStdout(`${JSON.stringify({ type: \"result\", status: outcome.status })}\\n`);\n\t\t} else if (outcome.status === \"completed\") {\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tfor (const content of (lastMessage as AssistantMessage).content) {\n\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconsole.error(outcome.message);\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"]}
@@ -8,6 +8,38 @@
8
8
  import { flushRawStdout, waitForRawStdoutBackpressure, writeRawStdout } from "../core/output-guard.js";
9
9
  import { killTrackedDetachedChildren } from "../utils/shell.js";
10
10
  import { toJsonEvent } from "./json-event.js";
11
+ /**
12
+ * Resolve the run's terminal outcome.
13
+ *
14
+ * `agent_end`'s structured stop reason wins because the loop already applied its
15
+ * documented precedence (aborted > error > budget > completed) to produce it. The
16
+ * settled assistant message is only a fallback, for the runs that end without an
17
+ * `agent_end` reaching this mode at all.
18
+ */
19
+ function resolveRunOutcome(stopReason, settled) {
20
+ if (stopReason?.kind === "budget-exhausted") {
21
+ const limit = stopReason.limit.replace(/-/g, " ");
22
+ return {
23
+ status: "budget-exhausted",
24
+ message: `Run stopped: the ${limit} budget was exhausted (runBudget settings).`,
25
+ };
26
+ }
27
+ const failed = (reason) => ({
28
+ status: reason,
29
+ message: settled?.errorMessage || `Request ${reason}`,
30
+ });
31
+ const completed = { status: "completed", message: "" };
32
+ // A stop reason answers the question by itself, including when it says the run
33
+ // completed. Reading the settled message in that case would let one left over
34
+ // from an earlier failed turn outrank what the loop concluded.
35
+ if (stopReason !== undefined) {
36
+ return stopReason.kind === "aborted" || stopReason.kind === "error" ? failed(stopReason.kind) : completed;
37
+ }
38
+ if (settled?.stopReason === "aborted" || settled?.stopReason === "error") {
39
+ return failed(settled.stopReason);
40
+ }
41
+ return completed;
42
+ }
11
43
  /**
12
44
  * Run in print (single-shot) mode.
13
45
  * Sends prompts to the agent and outputs the result.
@@ -113,33 +145,27 @@ export async function runPrintMode(runtimeHost, options) {
113
145
  for (const message of messages) {
114
146
  await session.prompt(message);
115
147
  }
116
- if (mode === "text") {
117
- // A budget stop can settle after a tool result with no assistant message,
118
- // so it is reported from the structured stop reason rather than inferred
119
- // from the last message.
120
- if (lastStopReason?.kind === "budget-exhausted") {
121
- console.error(`Run stopped: the ${lastStopReason.limit.replace(/-/g, " ")} budget was exhausted (runBudget settings).`);
122
- exitCode = 1;
123
- }
124
- else {
125
- const state = session.state;
126
- const lastMessage = state.messages[state.messages.length - 1];
127
- if (lastMessage?.role === "assistant") {
128
- const assistantMsg = lastMessage;
129
- if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
130
- console.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
131
- exitCode = 1;
132
- }
133
- else {
134
- for (const content of assistantMsg.content) {
135
- if (content.type === "text") {
136
- writeRawStdout(`${content.text}\n`);
137
- }
138
- }
148
+ // The outcome is resolved once, for every mode. Deciding it inside the text
149
+ // branch is what let a failed `--mode json` run exit 0: the stream carried the
150
+ // error and the exit code reported success.
151
+ const lastMessage = session.state.messages[session.state.messages.length - 1];
152
+ const outcome = resolveRunOutcome(lastStopReason, lastMessage?.role === "assistant" ? lastMessage : undefined);
153
+ exitCode = outcome.status === "completed" ? 0 : 1;
154
+ if (mode === "json") {
155
+ writeRawStdout(`${JSON.stringify({ type: "result", status: outcome.status })}\n`);
156
+ }
157
+ else if (outcome.status === "completed") {
158
+ if (lastMessage?.role === "assistant") {
159
+ for (const content of lastMessage.content) {
160
+ if (content.type === "text") {
161
+ writeRawStdout(`${content.text}\n`);
139
162
  }
140
163
  }
141
164
  }
142
165
  }
166
+ else {
167
+ console.error(outcome.message);
168
+ }
143
169
  return exitCode;
144
170
  }
145
171
  catch (error) {
@@ -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 { AgentStopReason } from \"apex-code-agent-core\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.ts\";\nimport { flushRawStdout, waitForRawStdoutBackpressure, writeRawStdout } from \"../core/output-guard.ts\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.ts\";\nimport { toJsonEvent } from \"./json-event.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\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 session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet unsubscribeBackpressure: (() => void) | undefined;\n\tlet disposed = false;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\t// Structured terminal outcome of the last run (spec 2026-09-01-tool-reliability-\n\t// and-execution-budgets.md); surfaced in this mode's native form below.\n\tlet lastStopReason: AgentStopReason | undefined;\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\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\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\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.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\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\tlastStopReason = event.stopReason;\n\t\t\t}\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(toJsonEvent(event))}\\n`);\n\t\t\t}\n\t\t});\n\t\tunsubscribeBackpressure =\n\t\t\tmode === \"json\"\n\t\t\t\t? session.agent.subscribe(async () => {\n\t\t\t\t\t\tawait waitForRawStdoutBackpressure();\n\t\t\t\t\t})\n\t\t\t\t: undefined;\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 session.prompt(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait session.prompt(message);\n\t\t}\n\n\t\tif (mode === \"text\") {\n\t\t\t// A budget stop can settle after a tool result with no assistant message,\n\t\t\t// so it is reported from the structured stop reason rather than inferred\n\t\t\t// from the last message.\n\t\t\tif (lastStopReason?.kind === \"budget-exhausted\") {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Run stopped: the ${lastStopReason.limit.replace(/-/g, \" \")} budget was exhausted (runBudget settings).`,\n\t\t\t\t);\n\t\t\t\texitCode = 1;\n\t\t\t} else {\n\t\t\t\tconst state = session.state;\n\t\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\t\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\t\texitCode = 1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\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 } from \"@earendil-works/pi-ai\";\nimport type { AgentStopReason } from \"apex-code-agent-core\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.ts\";\nimport { flushRawStdout, waitForRawStdoutBackpressure, writeRawStdout } from \"../core/output-guard.ts\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.ts\";\nimport { toJsonEvent } from \"./json-event.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\n/**\n * Terminal status of a non-interactive run, written as the `result` envelope in\n * JSON mode. The vocabulary is `AgentStopReason`'s own, rather than a second one\n * defined here, so the two cannot drift.\n */\ntype PrintRunStatus = AgentStopReason[\"kind\"];\n\ninterface PrintRunOutcome {\n\tstatus: PrintRunStatus;\n\t/** What text mode prints to stderr. Empty when the run completed. */\n\tmessage: string;\n}\n\n/**\n * Resolve the run's terminal outcome.\n *\n * `agent_end`'s structured stop reason wins because the loop already applied its\n * documented precedence (aborted > error > budget > completed) to produce it. The\n * settled assistant message is only a fallback, for the runs that end without an\n * `agent_end` reaching this mode at all.\n */\nfunction resolveRunOutcome(\n\tstopReason: AgentStopReason | undefined,\n\tsettled: AssistantMessage | undefined,\n): PrintRunOutcome {\n\tif (stopReason?.kind === \"budget-exhausted\") {\n\t\tconst limit = stopReason.limit.replace(/-/g, \" \");\n\t\treturn {\n\t\t\tstatus: \"budget-exhausted\",\n\t\t\tmessage: `Run stopped: the ${limit} budget was exhausted (runBudget settings).`,\n\t\t};\n\t}\n\tconst failed = (reason: \"aborted\" | \"error\"): PrintRunOutcome => ({\n\t\tstatus: reason,\n\t\tmessage: settled?.errorMessage || `Request ${reason}`,\n\t});\n\tconst completed: PrintRunOutcome = { status: \"completed\", message: \"\" };\n\n\t// A stop reason answers the question by itself, including when it says the run\n\t// completed. Reading the settled message in that case would let one left over\n\t// from an earlier failed turn outrank what the loop concluded.\n\tif (stopReason !== undefined) {\n\t\treturn stopReason.kind === \"aborted\" || stopReason.kind === \"error\" ? failed(stopReason.kind) : completed;\n\t}\n\tif (settled?.stopReason === \"aborted\" || settled?.stopReason === \"error\") {\n\t\treturn failed(settled.stopReason);\n\t}\n\treturn completed;\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 session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet unsubscribeBackpressure: (() => void) | undefined;\n\tlet disposed = false;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\t// Structured terminal outcome of the last run (spec 2026-09-01-tool-reliability-\n\t// and-execution-budgets.md); surfaced in this mode's native form below.\n\tlet lastStopReason: AgentStopReason | undefined;\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\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\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\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.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\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\tlastStopReason = event.stopReason;\n\t\t\t}\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(toJsonEvent(event))}\\n`);\n\t\t\t}\n\t\t});\n\t\tunsubscribeBackpressure =\n\t\t\tmode === \"json\"\n\t\t\t\t? session.agent.subscribe(async () => {\n\t\t\t\t\t\tawait waitForRawStdoutBackpressure();\n\t\t\t\t\t})\n\t\t\t\t: undefined;\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 session.prompt(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait session.prompt(message);\n\t\t}\n\n\t\t// The outcome is resolved once, for every mode. Deciding it inside the text\n\t\t// branch is what let a failed `--mode json` run exit 0: the stream carried the\n\t\t// error and the exit code reported success.\n\t\tconst lastMessage = session.state.messages[session.state.messages.length - 1];\n\t\tconst outcome = resolveRunOutcome(\n\t\t\tlastStopReason,\n\t\t\tlastMessage?.role === \"assistant\" ? (lastMessage as AssistantMessage) : undefined,\n\t\t);\n\t\texitCode = outcome.status === \"completed\" ? 0 : 1;\n\n\t\tif (mode === \"json\") {\n\t\t\twriteRawStdout(`${JSON.stringify({ type: \"result\", status: outcome.status })}\\n`);\n\t\t} else if (outcome.status === \"completed\") {\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tfor (const content of (lastMessage as AssistantMessage).content) {\n\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconsole.error(outcome.message);\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"]}
package/docs/json.md CHANGED
@@ -96,3 +96,39 @@ fields. `message_end` contains the final authoritative message.
96
96
  ```bash
97
97
  apex-code --mode json "List files" 2>/dev/null | jq -c 'select(.type == "message_end")'
98
98
  ```
99
+
100
+ ## Result envelope and exit codes
101
+
102
+ A `--mode json` run that reaches a normal terminal outcome ends with a `result` envelope.
103
+ It is written by print mode rather than emitted by the session, so it is not an
104
+ `AgentSessionEvent` and does not appear in `--mode text`. A run killed by `SIGTERM` or
105
+ `SIGHUP` exits from the signal handler before the envelope is written, so a consumer
106
+ treats a missing envelope as an incomplete run rather than as a success.
107
+
108
+ ```json
109
+ {"type":"result","status":"completed"}
110
+ ```
111
+
112
+ `status` is the run's terminal outcome, using the same vocabulary as the agent loop's
113
+ `AgentStopReason`:
114
+
115
+ | `status` | Meaning | Exit code |
116
+ | --- | --- | --- |
117
+ | `completed` | The run finished normally. | `0` |
118
+ | `error` | The run failed, for example a provider error. | `1` |
119
+ | `aborted` | The run was cancelled. | `1` |
120
+ | `budget-exhausted` | A `runBudget` limit stopped the run. | `1` |
121
+
122
+ Exit codes are `0` for `completed` and `1` for every other status. A caller that needs to
123
+ distinguish the causes reads `status` rather than the exit code, so new statuses can be
124
+ added without changing what an existing script sees. Signals are unchanged: `SIGTERM` exits
125
+ `143` and `SIGHUP` exits `129`.
126
+
127
+ Before this envelope existed, a failed `--mode json` run exited `0`, because the exit-code
128
+ decision was reachable only from `--mode text`. Scripts written against that behavior treated
129
+ every run as a success.
130
+
131
+ ```bash
132
+ apex-code --mode json --permission-mode plan --print "Summarize the repository" \
133
+ | jq -r 'select(.type == "result") | .status'
134
+ ```
package/docs/settings.md CHANGED
@@ -56,7 +56,7 @@ Use `/trust` in interactive mode to save a project trust decision for future ses
56
56
  | `externalEditor` | string | `$VISUAL`, then `$EDITOR`, then Notepad on Windows or `nano` elsewhere | Command for Ctrl+G external editor; takes precedence over environment variables |
57
57
  | `quietStartup` | boolean | `false` | Hide startup header |
58
58
  | `defaultProjectTrust` | string | `"ask"` | Fallback project trust behavior: `"ask"`, `"always"`, or `"never"`. Global setting only |
59
- | `collapseChangelog` | boolean | `false` | Show condensed changelog after updates |
59
+ | `collapseChangelog` | boolean | `true` | Show one line after an update instead of the full release notes; `/changelog` shows them |
60
60
  | `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"`, `"fork"`, or `"none"` |
61
61
  | `treeFilterMode` | string | `"default"` | Default filter for `/tree`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, `"all"` |
62
62
  | `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) |
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "apex-code",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "apex-code",
9
- "version": "0.4.0",
9
+ "version": "0.5.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@earendil-works/pi-ai": "^0.84.4",
@@ -16,7 +16,7 @@
16
16
  "@modelcontextprotocol/client": "2.0.0",
17
17
  "@modelcontextprotocol/core": "2.0.0",
18
18
  "@silvia-odwyer/photon-node": "0.3.4",
19
- "apex-code-agent-core": "0.4.0",
19
+ "apex-code-agent-core": "0.5.0",
20
20
  "chalk": "5.6.2",
21
21
  "cross-spawn": "7.0.6",
22
22
  "diff": "9.0.0",
@@ -1085,8 +1085,8 @@
1085
1085
  }
1086
1086
  },
1087
1087
  "node_modules/apex-code-agent-core": {
1088
- "version": "0.4.0",
1089
- "resolved": "https://registry.npmjs.org/apex-code-agent-core/-/apex-code-agent-core-0.4.0.tgz",
1088
+ "version": "0.5.0",
1089
+ "resolved": "https://registry.npmjs.org/apex-code-agent-core/-/apex-code-agent-core-0.5.0.tgz",
1090
1090
  "license": "MIT",
1091
1091
  "dependencies": {
1092
1092
  "@earendil-works/pi-ai": "^0.84.4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apex-code",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -50,7 +50,7 @@
50
50
  "@modelcontextprotocol/client": "2.0.0",
51
51
  "@modelcontextprotocol/core": "2.0.0",
52
52
  "@silvia-odwyer/photon-node": "0.3.4",
53
- "apex-code-agent-core": "0.4.0",
53
+ "apex-code-agent-core": "0.5.0",
54
54
  "chalk": "5.6.2",
55
55
  "cross-spawn": "7.0.6",
56
56
  "diff": "9.0.0",
@@ -1,14 +0,0 @@
1
- /**
2
- * Put the accent spine in the first padding column of an already-rendered line.
3
- *
4
- * Both message components render their children with `outputPad` leading spaces, so the
5
- * first column is padding and is free to take. Prefixing the spine instead makes the line
6
- * one column wider than the width it was rendered for, and nothing downstream re-flows it:
7
- * the user-message line then overflows the terminal by one column, and the assistant path
8
- * hands it to `truncateToWidth`, which clips real content and appends an ellipsis.
9
- *
10
- * A line that does not start with a padding column is returned unchanged, because there is
11
- * no free column to take and widening it is the defect this exists to avoid.
12
- */
13
- export declare function withAccentSpine(line: string): string;
14
- //# sourceMappingURL=message-spine.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message-spine.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/message-spine.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGpD","sourcesContent":["import { theme } from \"../theme/theme.ts\";\n\n/**\n * Put the accent spine in the first padding column of an already-rendered line.\n *\n * Both message components render their children with `outputPad` leading spaces, so the\n * first column is padding and is free to take. Prefixing the spine instead makes the line\n * one column wider than the width it was rendered for, and nothing downstream re-flows it:\n * the user-message line then overflows the terminal by one column, and the assistant path\n * hands it to `truncateToWidth`, which clips real content and appends an ellipsis.\n *\n * A line that does not start with a padding column is returned unchanged, because there is\n * no free column to take and widening it is the defect this exists to avoid.\n */\nexport function withAccentSpine(line: string): string {\n\tif (!line.startsWith(\" \")) return line;\n\treturn theme.fg(\"accent\", \"│\") + line.slice(1);\n}\n"]}
@@ -1,19 +0,0 @@
1
- import { theme } from "../theme/theme.js";
2
- /**
3
- * Put the accent spine in the first padding column of an already-rendered line.
4
- *
5
- * Both message components render their children with `outputPad` leading spaces, so the
6
- * first column is padding and is free to take. Prefixing the spine instead makes the line
7
- * one column wider than the width it was rendered for, and nothing downstream re-flows it:
8
- * the user-message line then overflows the terminal by one column, and the assistant path
9
- * hands it to `truncateToWidth`, which clips real content and appends an ellipsis.
10
- *
11
- * A line that does not start with a padding column is returned unchanged, because there is
12
- * no free column to take and widening it is the defect this exists to avoid.
13
- */
14
- export function withAccentSpine(line) {
15
- if (!line.startsWith(" "))
16
- return line;
17
- return theme.fg("accent", "│") + line.slice(1);
18
- }
19
- //# sourceMappingURL=message-spine.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message-spine.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/message-spine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAU;IACrD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAAA,CAC/C","sourcesContent":["import { theme } from \"../theme/theme.ts\";\n\n/**\n * Put the accent spine in the first padding column of an already-rendered line.\n *\n * Both message components render their children with `outputPad` leading spaces, so the\n * first column is padding and is free to take. Prefixing the spine instead makes the line\n * one column wider than the width it was rendered for, and nothing downstream re-flows it:\n * the user-message line then overflows the terminal by one column, and the assistant path\n * hands it to `truncateToWidth`, which clips real content and appends an ellipsis.\n *\n * A line that does not start with a padding column is returned unchanged, because there is\n * no free column to take and widening it is the defect this exists to avoid.\n */\nexport function withAccentSpine(line: string): string {\n\tif (!line.startsWith(\" \")) return line;\n\treturn theme.fg(\"accent\", \"│\") + line.slice(1);\n}\n"]}