actoviq-agent-sdk 0.4.12 → 0.4.13

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 (194) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/src/config/agentDefinitionMigration.d.ts +125 -0
  3. package/dist/src/config/agentDefinitionMigration.d.ts.map +1 -0
  4. package/dist/src/config/agentDefinitionMigration.js +540 -0
  5. package/dist/src/config/agentDefinitionMigration.js.map +1 -0
  6. package/dist/src/config/agentProfiles.d.ts +19 -0
  7. package/dist/src/config/agentProfiles.d.ts.map +1 -1
  8. package/dist/src/config/agentProfiles.js +65 -13
  9. package/dist/src/config/agentProfiles.js.map +1 -1
  10. package/dist/src/config/modelConfigurationCatalog.d.ts +25 -0
  11. package/dist/src/config/modelConfigurationCatalog.d.ts.map +1 -0
  12. package/dist/src/config/modelConfigurationCatalog.js +50 -0
  13. package/dist/src/config/modelConfigurationCatalog.js.map +1 -0
  14. package/dist/src/gui/agentTargetPicker.d.ts +32 -0
  15. package/dist/src/gui/agentTargetPicker.d.ts.map +1 -0
  16. package/dist/src/gui/agentTargetPicker.js +99 -0
  17. package/dist/src/gui/agentTargetPicker.js.map +1 -0
  18. package/dist/src/gui/electronMain.js +1 -0
  19. package/dist/src/gui/electronMain.js.map +1 -1
  20. package/dist/src/gui/hadamardGui.d.ts +23 -0
  21. package/dist/src/gui/hadamardGui.d.ts.map +1 -1
  22. package/dist/src/gui/hadamardGui.js +4120 -931
  23. package/dist/src/gui/hadamardGui.js.map +1 -1
  24. package/dist/src/gui/projectWorkbench.d.ts +6 -2
  25. package/dist/src/gui/projectWorkbench.d.ts.map +1 -1
  26. package/dist/src/gui/projectWorkbench.js +76 -67
  27. package/dist/src/gui/projectWorkbench.js.map +1 -1
  28. package/dist/src/gui/promptTemplates.d.ts +0 -1
  29. package/dist/src/gui/promptTemplates.d.ts.map +1 -1
  30. package/dist/src/gui/promptTemplates.js +0 -3
  31. package/dist/src/gui/promptTemplates.js.map +1 -1
  32. package/dist/src/gui/transcript/clientBundle.d.ts +6 -1
  33. package/dist/src/gui/transcript/clientBundle.d.ts.map +1 -1
  34. package/dist/src/gui/transcript/clientBundle.js +6 -1
  35. package/dist/src/gui/transcript/clientBundle.js.map +1 -1
  36. package/dist/src/help/productCapabilities.d.ts +17 -0
  37. package/dist/src/help/productCapabilities.d.ts.map +1 -0
  38. package/dist/src/help/productCapabilities.js +146 -0
  39. package/dist/src/help/productCapabilities.js.map +1 -0
  40. package/dist/src/index.d.ts +15 -4
  41. package/dist/src/index.d.ts.map +1 -1
  42. package/dist/src/index.js +9 -3
  43. package/dist/src/index.js.map +1 -1
  44. package/dist/src/issues/issueExecution.d.ts.map +1 -1
  45. package/dist/src/issues/issueExecution.js +29 -9
  46. package/dist/src/issues/issueExecution.js.map +1 -1
  47. package/dist/src/manager/assistantGlobalTools.d.ts +44 -0
  48. package/dist/src/manager/assistantGlobalTools.d.ts.map +1 -1
  49. package/dist/src/manager/assistantGlobalTools.js +565 -32
  50. package/dist/src/manager/assistantGlobalTools.js.map +1 -1
  51. package/dist/src/manager/assistantProposals.d.ts +87 -0
  52. package/dist/src/manager/assistantProposals.d.ts.map +1 -0
  53. package/dist/src/manager/assistantProposals.js +207 -0
  54. package/dist/src/manager/assistantProposals.js.map +1 -0
  55. package/dist/src/manager/referenceIndex.d.ts +82 -0
  56. package/dist/src/manager/referenceIndex.d.ts.map +1 -0
  57. package/dist/src/manager/referenceIndex.js +167 -0
  58. package/dist/src/manager/referenceIndex.js.map +1 -0
  59. package/dist/src/manager/referenceOperations.d.ts +108 -0
  60. package/dist/src/manager/referenceOperations.d.ts.map +1 -0
  61. package/dist/src/manager/referenceOperations.js +821 -0
  62. package/dist/src/manager/referenceOperations.js.map +1 -0
  63. package/dist/src/manager/resolveTargetRef.d.ts +67 -0
  64. package/dist/src/manager/resolveTargetRef.d.ts.map +1 -0
  65. package/dist/src/manager/resolveTargetRef.js +81 -0
  66. package/dist/src/manager/resolveTargetRef.js.map +1 -0
  67. package/dist/src/router/modelRouter.d.ts +22 -2
  68. package/dist/src/router/modelRouter.d.ts.map +1 -1
  69. package/dist/src/router/modelRouter.js +76 -9
  70. package/dist/src/router/modelRouter.js.map +1 -1
  71. package/dist/src/runtime/agentClient.d.ts +18 -0
  72. package/dist/src/runtime/agentClient.d.ts.map +1 -1
  73. package/dist/src/runtime/agentClient.js +209 -21
  74. package/dist/src/runtime/agentClient.js.map +1 -1
  75. package/dist/src/runtime/agentSession.d.ts +7 -0
  76. package/dist/src/runtime/agentSession.d.ts.map +1 -1
  77. package/dist/src/runtime/agentSession.js +10 -0
  78. package/dist/src/runtime/agentSession.js.map +1 -1
  79. package/dist/src/runtime/agentTemplates.d.ts +28 -0
  80. package/dist/src/runtime/agentTemplates.d.ts.map +1 -0
  81. package/dist/src/runtime/agentTemplates.js +73 -0
  82. package/dist/src/runtime/agentTemplates.js.map +1 -0
  83. package/dist/src/runtime/conversationEngine.d.ts +2 -0
  84. package/dist/src/runtime/conversationEngine.d.ts.map +1 -1
  85. package/dist/src/runtime/conversationEngine.js +10 -1
  86. package/dist/src/runtime/conversationEngine.js.map +1 -1
  87. package/dist/src/runtime/defaultHadamardAgents.d.ts.map +1 -1
  88. package/dist/src/runtime/defaultHadamardAgents.js +9 -47
  89. package/dist/src/runtime/defaultHadamardAgents.js.map +1 -1
  90. package/dist/src/runtime/effectiveAgentRunOptions.d.ts +49 -0
  91. package/dist/src/runtime/effectiveAgentRunOptions.d.ts.map +1 -0
  92. package/dist/src/runtime/effectiveAgentRunOptions.js +50 -0
  93. package/dist/src/runtime/effectiveAgentRunOptions.js.map +1 -0
  94. package/dist/src/runtime/externalAgentRunner.d.ts +25 -0
  95. package/dist/src/runtime/externalAgentRunner.d.ts.map +1 -0
  96. package/dist/src/runtime/externalAgentRunner.js +57 -0
  97. package/dist/src/runtime/externalAgentRunner.js.map +1 -0
  98. package/dist/src/runtime/hadamardAgentDefinitions.d.ts +15 -0
  99. package/dist/src/runtime/hadamardAgentDefinitions.d.ts.map +1 -1
  100. package/dist/src/runtime/hadamardAgentDefinitions.js +42 -2
  101. package/dist/src/runtime/hadamardAgentDefinitions.js.map +1 -1
  102. package/dist/src/runtime/hadamardAgents.d.ts +6 -0
  103. package/dist/src/runtime/hadamardAgents.d.ts.map +1 -1
  104. package/dist/src/runtime/hadamardAgents.js +52 -1
  105. package/dist/src/runtime/hadamardAgents.js.map +1 -1
  106. package/dist/src/runtime/hadamardPermissions.d.ts +1 -0
  107. package/dist/src/runtime/hadamardPermissions.d.ts.map +1 -1
  108. package/dist/src/runtime/hadamardPermissions.js +6 -0
  109. package/dist/src/runtime/hadamardPermissions.js.map +1 -1
  110. package/dist/src/runtime/tools.d.ts.map +1 -1
  111. package/dist/src/runtime/tools.js +2 -0
  112. package/dist/src/runtime/tools.js.map +1 -1
  113. package/dist/src/team/executorTarget.d.ts +26 -0
  114. package/dist/src/team/executorTarget.d.ts.map +1 -0
  115. package/dist/src/team/executorTarget.js +15 -0
  116. package/dist/src/team/executorTarget.js.map +1 -0
  117. package/dist/src/team/modelTeam.d.ts +5 -1
  118. package/dist/src/team/modelTeam.d.ts.map +1 -1
  119. package/dist/src/team/modelTeam.js +157 -41
  120. package/dist/src/team/modelTeam.js.map +1 -1
  121. package/dist/src/team/teamComposition.d.ts +20 -0
  122. package/dist/src/team/teamComposition.d.ts.map +1 -0
  123. package/dist/src/team/teamComposition.js +78 -0
  124. package/dist/src/team/teamComposition.js.map +1 -0
  125. package/dist/src/team/teamDefinitions.d.ts +10 -1
  126. package/dist/src/team/teamDefinitions.d.ts.map +1 -1
  127. package/dist/src/team/teamDefinitions.js +123 -16
  128. package/dist/src/team/teamDefinitions.js.map +1 -1
  129. package/dist/src/team/teamGraph.d.ts +1 -1
  130. package/dist/src/team/teamGraph.d.ts.map +1 -1
  131. package/dist/src/team/teamGraph.js +1 -1
  132. package/dist/src/team/teamGraph.js.map +1 -1
  133. package/dist/src/team/teamPrompts.d.ts.map +1 -1
  134. package/dist/src/team/teamPrompts.js +0 -4
  135. package/dist/src/team/teamPrompts.js.map +1 -1
  136. package/dist/src/team/teamProposalService.d.ts +9 -1
  137. package/dist/src/team/teamProposalService.d.ts.map +1 -1
  138. package/dist/src/team/teamProposalService.js +66 -6
  139. package/dist/src/team/teamProposalService.js.map +1 -1
  140. package/dist/src/team/teamPropose.d.ts +3 -1
  141. package/dist/src/team/teamPropose.d.ts.map +1 -1
  142. package/dist/src/team/teamPropose.js +16 -10
  143. package/dist/src/team/teamPropose.js.map +1 -1
  144. package/dist/src/team/teamRuntime.d.ts +5 -0
  145. package/dist/src/team/teamRuntime.d.ts.map +1 -1
  146. package/dist/src/team/teamRuntime.js +23 -5
  147. package/dist/src/team/teamRuntime.js.map +1 -1
  148. package/dist/src/team/workflowSquad.d.ts +10 -2
  149. package/dist/src/team/workflowSquad.d.ts.map +1 -1
  150. package/dist/src/team/workflowSquad.js +250 -12
  151. package/dist/src/team/workflowSquad.js.map +1 -1
  152. package/dist/src/tools/enterWorktree.d.ts.map +1 -1
  153. package/dist/src/tools/enterWorktree.js +3 -0
  154. package/dist/src/tools/enterWorktree.js.map +1 -1
  155. package/dist/src/tools/exaSearch.d.ts +2 -2
  156. package/dist/src/tools/exitWorktree.d.ts.map +1 -1
  157. package/dist/src/tools/exitWorktree.js +3 -1
  158. package/dist/src/tools/exitWorktree.js.map +1 -1
  159. package/dist/src/tools/hadamardCoreTools.d.ts +5 -14
  160. package/dist/src/tools/hadamardCoreTools.d.ts.map +1 -1
  161. package/dist/src/tools/hadamardCoreTools.js +31 -0
  162. package/dist/src/tools/hadamardCoreTools.js.map +1 -1
  163. package/dist/src/tools/hadamardFileTools.d.ts.map +1 -1
  164. package/dist/src/tools/hadamardFileTools.js +116 -38
  165. package/dist/src/tools/hadamardFileTools.js.map +1 -1
  166. package/dist/src/tools/planMode/PlanModeTools.js +2 -2
  167. package/dist/src/tools/planMode/PlanModeTools.js.map +1 -1
  168. package/dist/src/tui/hadamardTui.d.ts.map +1 -1
  169. package/dist/src/tui/hadamardTui.js +358 -69
  170. package/dist/src/tui/hadamardTui.js.map +1 -1
  171. package/dist/src/tui/pendingInput.d.ts +17 -0
  172. package/dist/src/tui/pendingInput.d.ts.map +1 -0
  173. package/dist/src/tui/pendingInput.js +23 -0
  174. package/dist/src/tui/pendingInput.js.map +1 -0
  175. package/dist/src/tui/reasoningDisplay.d.ts +13 -0
  176. package/dist/src/tui/reasoningDisplay.d.ts.map +1 -0
  177. package/dist/src/tui/reasoningDisplay.js +45 -0
  178. package/dist/src/tui/reasoningDisplay.js.map +1 -0
  179. package/dist/src/tui/screen.d.ts +11 -1
  180. package/dist/src/tui/screen.d.ts.map +1 -1
  181. package/dist/src/tui/screen.js +64 -34
  182. package/dist/src/tui/screen.js.map +1 -1
  183. package/dist/src/types.d.ts +171 -4
  184. package/dist/src/types.d.ts.map +1 -1
  185. package/dist/src/types.js.map +1 -1
  186. package/dist/src/ui/commandSurface.d.ts +8 -0
  187. package/dist/src/ui/commandSurface.d.ts.map +1 -1
  188. package/dist/src/ui/commandSurface.js +71 -7
  189. package/dist/src/ui/commandSurface.js.map +1 -1
  190. package/package.json +1 -1
  191. package/dist/src/gui/projectSettings.d.ts +0 -2
  192. package/dist/src/gui/projectSettings.d.ts.map +0 -1
  193. package/dist/src/gui/projectSettings.js +0 -2
  194. package/dist/src/gui/projectSettings.js.map +0 -1
@@ -10,7 +10,7 @@ import { createHash, randomUUID } from 'node:crypto';
10
10
  import fs from 'node:fs';
11
11
  import path from 'node:path';
12
12
  import * as readline from 'node:readline';
13
- import { createHadamardCoreTools, createAgentSdk, createHadamardBridgeSdk, detectBridgeProviders, loadDefaultHadamardSettings, loadJsonConfigFile, listWorkflows, loadWorkflow, listTeamDefinitions, loadTeamDefinition, cloneTeamDefinition, instantiateTeamDefinition, listTeamAgentLabels, countTeamAgents, createModelTeam, createTeamTool, readTeamPreferences, createManagerTools, createAssistantTeamTools, buildAssistantTeamSystemPrompt, TeamProposalStore, SessionCatalog, createAssistantGlobalTools, buildAssistantGlobalSystemPrompt, readAssistantConfig, writeAssistantConfig, buildManagerSystemPrompt, buildUpdateProgressPrompt, formatManagerUpdatePreview, resolveGitHubDigestForUpdate, readManagerConfig, writeManagerConfig, readProjectPlanFile, readProgressFile, managerProgressPath, createProjectIssue, executeProjectIssue, isIssueStatus, isIssueStorageMode, listProjectIssues, listScheduledAutomationTasks, upsertScheduledAutomationTask, resolveHadamardHome, externalSkillPreferencesToRuntimeOptions, readHadamardExternalSkillPreferences, createManagedPluginRuntime, patchManagedPluginSettings, readManagedPluginCatalog, listRouterProfiles, loadRouterProfile, resolveRoutedRun, transitionProjectIssue, WorktreeService, } from '../index.js';
13
+ import { createHadamardCoreTools, createAgentSdk, createHadamardBridgeSdk, detectBridgeProviders, loadDefaultHadamardSettings, loadJsonConfigFile, listWorkflows, loadWorkflow, saveWorkflow, deleteWorkflow, listTeamDefinitions, loadTeamDefinition, cloneTeamDefinition, deleteTeamDefinition, getBuiltInTeamDefinition, instantiateTeamDefinition, listTeamAgentLabels, countTeamAgents, askTeamDefinition, createTeamTool, readTeamPreferences, createManagerTools, createAssistantTeamTools, buildAssistantTeamSystemPrompt, TeamProposalStore, SessionCatalog, createAssistantGlobalTools, buildAssistantGlobalSystemPrompt, readAssistantConfig, writeAssistantConfig, buildManagerSystemPrompt, buildUpdateProgressPrompt, formatManagerUpdatePreview, resolveGitHubDigestForUpdate, readManagerConfig, writeManagerConfig, readProjectPlanFile, readProgressFile, managerProgressPath, createProjectIssue, executeProjectIssue, isIssueStatus, isIssueStorageMode, listProjectIssues, listScheduledAutomationTasks, upsertScheduledAutomationTask, resolveHadamardHome, externalSkillPreferencesToRuntimeOptions, readHadamardExternalSkillPreferences, createManagedPluginRuntime, patchManagedPluginSettings, readManagedPluginCatalog, listRouterProfiles, loadRouterProfile, resolveRoutedRun, transitionProjectIssue, WorktreeService, } from '../index.js';
14
14
  import { adaptBridgeRun } from '../parity/bridgeEventAdapter.js';
15
15
  import { ExternalCliRuntimeManager } from '../parity/externalCliRuntimeManager.js';
16
16
  import { externalCliSessionMatchesConfig, listExternalCliSessions, readExternalCliSession, } from '../parity/externalCliSessions.js';
@@ -22,6 +22,7 @@ import { createPreToolUseHookClassifier, readPreToolUseHooks, readPostToolUseHoo
22
22
  import { parseTypedHooks } from '../hooks/hookConfig.js';
23
23
  import { isRecord } from '../runtime/helpers.js';
24
24
  import { getLoadedJsonConfig } from '../config/loadJsonConfigFile.js';
25
+ import { buildModelConfigurationCatalog, findModelConfiguration, resolveHadamardConfigurationModel, } from '../config/modelConfigurationCatalog.js';
25
26
  import { findBridgeConfig, maskApiKey, readBridgeConfigs, addBridgeConfig, removeBridgeConfig, isManagedExternalCliRuntime, } from '../parity/bridgeConfigs.js';
26
27
  import { buildRouteModelApi } from '../router/modelRouter.js';
27
28
  import { addMcpServer, readMcpServerConfig, removeMcpServer } from '../mcp/mcpServerConfig.js';
@@ -32,14 +33,16 @@ import { planFilePath, readPlanFile } from '../tools/planMode/PlanModeTools.js';
32
33
  import { loadProjectContext } from '../memory/projectContext.js';
33
34
  import { LegacySurfaceEventPipeline, } from '../surfaces/index.js';
34
35
  import { pathToFileURL } from 'node:url';
35
- import { HADAMARD_INTERACTIVE_COMMANDS, SUBCOMMAND_DESCRIPTIONS, filterInteractiveCommands, interactiveCommandUsage, selectInteractiveCommand, } from '../ui/commandSurface.js';
36
+ import { HADAMARD_INTERACTIVE_COMMANDS, SUBCOMMAND_DESCRIPTIONS, canRunInteractiveCommand, filterInteractiveCommands, interactiveCommandRunPolicy, interactiveCommandUsage, parseTeamAskArguments, selectInteractiveCommand, } from '../ui/commandSurface.js';
36
37
  import { createAgentExecutionProjectView, createAgentExecutionRootView, formatAgentExecutionTreeLines, } from '../ui/agentExecutionView.js';
37
38
  import { A, stringWidth, truncateToWidth, wrapToWidth } from './ansi.js';
38
39
  import { InputEditor } from './editor.js';
39
40
  import { discoverHadamardPlugins } from './pluginCatalog.js';
40
41
  import { TuiScreen } from './screen.js';
42
+ import { ReasoningDisplayState } from './reasoningDisplay.js';
43
+ import { recallLatestFollowUp, restoreAbandonedFollowUp, submitActiveInput, } from './pendingInput.js';
41
44
  import { filterTuiSelectionItems, moveTuiSelection, } from './selection.js';
42
- import { StreamFlusher, formatBanner, formatCompactNotice, formatDivider, formatEditCall, formatErrorLine, formatInfoLine, formatQueuedPrompt, formatThinking, formatToolCall, formatToolResult, formatUserPrompt, } from './transcript.js';
45
+ import { StreamFlusher, formatBanner, formatCompactNotice, formatDivider, formatEditCall, formatErrorLine, formatInfoLine, formatQueuedPrompt, formatToolCall, formatToolResult, formatUserPrompt, } from './transcript.js';
43
46
  const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
44
47
  const CTRL_C_EXIT_WINDOW_MS = 600;
45
48
  const DYNAMIC_FRAME_MS = 33;
@@ -355,6 +358,8 @@ export async function runHadamardTui(options = {}) {
355
358
  let running = false;
356
359
  let commandBusy = false;
357
360
  let shuttingDown = false;
361
+ /** Follow-up pulled out of the queue into the editor; restored if abandoned. */
362
+ let recalledFollowUp = null;
358
363
  // Active team tool the main agent may call (toggled via /team). null = no team.
359
364
  // The tool is only injected into runs when preferences.team.autoInvoke is on;
360
365
  // otherwise attach is a selection and /team ask stays the manual path.
@@ -684,7 +689,7 @@ export async function runHadamardTui(options = {}) {
684
689
  let ctrlCCount = 0;
685
690
  let ctrlCTimer = null;
686
691
  let streamedTextSeen = false;
687
- let streamedThinkingSeen = false;
692
+ const reasoningDisplay = new ReasoningDisplayState();
688
693
  // Track tool names by callId for PostToolUse hook context.
689
694
  const toolCallNames = new Map();
690
695
  // Live todo list (captured from TodoWrite tool calls). Rendered as a
@@ -884,6 +889,16 @@ export async function runHadamardTui(options = {}) {
884
889
  lines.push(promptDivider());
885
890
  return lines;
886
891
  }
892
+ function promptCursorPosition(promptStartLine) {
893
+ if (editor.isEmpty()) {
894
+ return { line: promptStartLine + 1, column: 2 };
895
+ }
896
+ const visual = editor.visualLines(Math.max(screen.width - 5, 7));
897
+ return {
898
+ line: promptStartLine + 1 + visual.cursorRow,
899
+ column: 2 + visual.cursorCol,
900
+ };
901
+ }
887
902
  // ── @-mention file completion ──────────────────────────────────────
888
903
  // Prefer git's view (tracked + untracked, honoring .gitignore) so the list
889
904
  // matches what the agent actually sees; fall back to a bounded fs walk for
@@ -1034,7 +1049,10 @@ export async function runHadamardTui(options = {}) {
1034
1049
  const selected = index === menuSelected;
1035
1050
  const command = `/${name}`.padEnd(commandWidth);
1036
1051
  const label = selected ? `${A.inverse}${command}${A.reset}` : command;
1037
- const description = truncateToWidth(TUI_SLASH_COMMANDS[name] ?? SUBCOMMAND_DESCRIPTIONS[name] ?? '', descriptionWidth);
1052
+ const commandName = name.split(/\s/u, 1)[0] ?? name;
1053
+ const unavailable = running && interactiveCommandRunPolicy(commandName) === 'idle-only';
1054
+ const summary = TUI_SLASH_COMMANDS[name] ?? SUBCOMMAND_DESCRIPTIONS[name] ?? '';
1055
+ const description = truncateToWidth(unavailable ? `${summary} · after current run` : summary, descriptionWidth);
1038
1056
  return `${label} ${A.dim}${description}${A.reset}`;
1039
1057
  });
1040
1058
  const hiddenAbove = windowStart;
@@ -1047,6 +1065,14 @@ export async function runHadamardTui(options = {}) {
1047
1065
  parts.push(`↓${hiddenBelow}`);
1048
1066
  lines.push(`${A.dim} ${parts.join(' ')} more · ${menuSelected + 1}/${matches.length} (↑/↓ to scroll)${A.reset}`);
1049
1067
  }
1068
+ const selected = matches[Math.min(menuSelected, matches.length - 1)];
1069
+ if (selected) {
1070
+ const commandName = selected.split(/\s/u, 1)[0] ?? selected;
1071
+ const availability = interactiveCommandRunPolicy(commandName) === 'during-run'
1072
+ ? 'available while working'
1073
+ : 'idle session';
1074
+ lines.push(`${A.dim} ${interactiveCommandUsage(commandName)} · ${availability}${A.reset}`);
1075
+ }
1050
1076
  return lines;
1051
1077
  }
1052
1078
  function buildDialog() {
@@ -1142,7 +1168,9 @@ export async function runHadamardTui(options = {}) {
1142
1168
  const ctxColor = pct >= 90 ? A.red : pct >= 70 ? A.yellow : A.dim;
1143
1169
  const modelLabel = activeRouter
1144
1170
  ? `router:${activeRouter.name}${routedModelLabel ? ` → ${routedModelLabel}` : ''}`
1145
- : session.model;
1171
+ : !bridgeMode && activeBridgeConfig?.runtime === 'hadamard' && bridgeModelLabel
1172
+ ? `${activeBridgeConfig.name}:${bridgeModelLabel}`
1173
+ : session.model;
1146
1174
  const bridgeTag = bridgeMode && activeBridgeConfig
1147
1175
  ? ` · bridge:${activeBridgeConfig.name}${bridgeModelLabel ? ` · ${bridgeModelLabel}` : ''}`
1148
1176
  : '';
@@ -1167,10 +1195,39 @@ export async function runHadamardTui(options = {}) {
1167
1195
  }
1168
1196
  function buildHintLine() {
1169
1197
  if (running) {
1170
- return [`${A.dim} enter to queue a steering message · esc interrupt · ctrl+c twice to exit${A.reset}`];
1198
+ return [`${A.dim} enter queue follow-up · shift+enter steer now · ↑ recall queued · esc interrupt${A.reset}`];
1171
1199
  }
1172
1200
  return [`${A.dim} ? shortcuts · / commands · @ files · \\↵ newline · ↑↓ history · ctrl+c clear/exit${A.reset}`];
1173
1201
  }
1202
+ function buildPendingInputPanel() {
1203
+ const pending = session.pendingInputs;
1204
+ const visible = pending.followUps.slice(-3);
1205
+ const hidden = pending.followUps.length - visible.length;
1206
+ const lines = visible.map((text, index) => (`${A.dim} queued ${hidden + index + 1}: ${truncateToWidth(text.replace(/\s+/g, ' '), Math.max(screen.width - 15, 12))}${A.reset}`));
1207
+ if (pending.steering.length > 0) {
1208
+ lines.push(`${A.dim} ${pending.steering.length} immediate steering message${pending.steering.length === 1 ? '' : 's'} pending${A.reset}`);
1209
+ }
1210
+ return lines;
1211
+ }
1212
+ function selectionDialogCursorPosition(startLine) {
1213
+ if (!selectionDialog?.searchable)
1214
+ return undefined;
1215
+ return {
1216
+ line: startLine + 2 + (selectionDialog.subtitle ? 1 : 0),
1217
+ column: 4 + stringWidth(selectionDialog.query),
1218
+ };
1219
+ }
1220
+ function textInputDialogCursorPosition(startLine) {
1221
+ if (!textInputDialog)
1222
+ return undefined;
1223
+ const valueBeforeCursor = textInputDialog.secret
1224
+ ? '•'.repeat(textInputDialog.editor.cursor)
1225
+ : textInputDialog.editor.text.slice(0, textInputDialog.editor.cursor);
1226
+ return {
1227
+ line: startLine + 2 + (textInputDialog.description ? 1 : 0),
1228
+ column: 2 + stringWidth(`${textInputDialog.label}: `) + stringWidth(valueBeforeCursor),
1229
+ };
1230
+ }
1174
1231
  function buildTodoPanel() {
1175
1232
  if (currentTodos.length === 0)
1176
1233
  return [];
@@ -1206,7 +1263,14 @@ export async function runHadamardTui(options = {}) {
1206
1263
  }
1207
1264
  function renderDynamic() {
1208
1265
  const lines = [];
1266
+ let promptCursor;
1209
1267
  lines.push(...buildStatusLine());
1268
+ if (running && session.pendingInputCount > 0) {
1269
+ lines.push(...buildPendingInputPanel());
1270
+ }
1271
+ if (reasoningDisplay.hasActive) {
1272
+ lines.push(...reasoningDisplay.liveLines(screen.width));
1273
+ }
1210
1274
  const tail = flusher.tail();
1211
1275
  if (running && tail) {
1212
1276
  lines.push(tail);
@@ -1220,13 +1284,19 @@ export async function runHadamardTui(options = {}) {
1220
1284
  lines.push(...buildDialog());
1221
1285
  }
1222
1286
  else if (selectionDialog) {
1287
+ const dialogStartLine = lines.length;
1223
1288
  lines.push(...buildSelectionDialog());
1289
+ promptCursor = selectionDialogCursorPosition(dialogStartLine);
1224
1290
  }
1225
1291
  else if (textInputDialog) {
1292
+ const dialogStartLine = lines.length;
1226
1293
  lines.push(...buildTextInputDialog());
1294
+ promptCursor = textInputDialogCursorPosition(dialogStartLine);
1227
1295
  }
1228
1296
  else {
1297
+ const promptStartLine = lines.length;
1229
1298
  lines.push(...buildPromptBar());
1299
+ promptCursor = promptCursorPosition(promptStartLine);
1230
1300
  const atMenu = buildAtMenu();
1231
1301
  if (atMenu.length > 0) {
1232
1302
  lines.push(...atMenu);
@@ -1236,7 +1306,7 @@ export async function runHadamardTui(options = {}) {
1236
1306
  lines.push(...(menu.length > 0 ? menu : buildHintLine()));
1237
1307
  }
1238
1308
  }
1239
- screen.setDynamic(lines);
1309
+ screen.setDynamic(lines, promptCursor);
1240
1310
  }
1241
1311
  function scheduleDynamicRender() {
1242
1312
  if (dynamicRenderTimer)
@@ -1255,6 +1325,11 @@ export async function runHadamardTui(options = {}) {
1255
1325
  function appendStatic(lines) {
1256
1326
  screen.appendStatic(lines);
1257
1327
  }
1328
+ function collapseReasoning() {
1329
+ const lines = reasoningDisplay.complete();
1330
+ if (lines.length > 0)
1331
+ appendStatic(lines);
1332
+ }
1258
1333
  // Inline CLI spinner for long async operations (probe, reload).
1259
1334
  // Writes directly to stdout with \r — independent of the redrawable region.
1260
1335
  const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
@@ -1415,7 +1490,7 @@ export async function runHadamardTui(options = {}) {
1415
1490
  if (activeRouter && !bridgeMode) {
1416
1491
  try {
1417
1492
  const decision = await resolveRoutedRun(activeRouter, text, abortCtrl.signal);
1418
- routed = { model: decision.model, modelApi: decision.modelApi };
1493
+ routed = { model: decision.model, modelApi: decision.modelApi, effort: decision.effort };
1419
1494
  routedModelLabel = `${decision.label} (${decision.model})`;
1420
1495
  appendStatic(formatInfoLine(`router → ${routedModelLabel}`));
1421
1496
  }
@@ -1466,14 +1541,18 @@ export async function runHadamardTui(options = {}) {
1466
1541
  resultPromise = stream.result;
1467
1542
  }
1468
1543
  else {
1544
+ const selectedConfigModel = resolveHadamardConfigurationModel(activeBridgeConfig, bridgeMode, bridgeModelLabel);
1469
1545
  const stream = session.stream(expandImageRefs(text), {
1470
1546
  systemPrompt: systemPrompt + buildAgentContext(),
1471
1547
  signal: abortCtrl.signal,
1472
1548
  permissionMode: currentPermissionMode(),
1473
- effort: currentEffort(),
1549
+ // A matched route's effort overrides the session effort for this turn.
1550
+ effort: routed?.effort ?? currentEffort(),
1474
1551
  approver,
1475
1552
  classifier: preToolUseHookClassifier,
1476
- ...(routed ? { model: routed.model, modelApi: routed.modelApi } : {}),
1553
+ ...(routed
1554
+ ? { model: routed.model, modelApi: routed.modelApi }
1555
+ : selectedConfigModel ? { model: selectedConfigModel } : {}),
1477
1556
  ...(activeTeamTool && teamPrefs.autoInvoke ? { tools: [...tools, activeTeamTool] } : {}),
1478
1557
  ...(canUseTool ? { canUseTool } : {}),
1479
1558
  });
@@ -1487,6 +1566,7 @@ export async function runHadamardTui(options = {}) {
1487
1566
  }
1488
1567
  }
1489
1568
  const result = await resultPromise;
1569
+ collapseReasoning();
1490
1570
  // Accumulate token + USD usage for /cost and /usage. The model is the
1491
1571
  // routed model (if a router is active) or the session model. Bridge runs
1492
1572
  recordUsage(routed?.model ?? activeBridgeModelApi?.model ?? bridgeModelLabel ?? session.model, result.usage);
@@ -1517,6 +1597,7 @@ export async function runHadamardTui(options = {}) {
1517
1597
  appendStatic(['']);
1518
1598
  }
1519
1599
  catch (error) {
1600
+ collapseReasoning();
1520
1601
  const rest = flusher.drain();
1521
1602
  if (rest.length > 0)
1522
1603
  appendStatic(rest);
@@ -1548,7 +1629,7 @@ export async function runHadamardTui(options = {}) {
1548
1629
  switch (event.type) {
1549
1630
  case 'run.started':
1550
1631
  streamedTextSeen = false;
1551
- streamedThinkingSeen = false;
1632
+ reasoningDisplay.reset();
1552
1633
  return;
1553
1634
  case 'request.started':
1554
1635
  lastTokenEstimate = typeof data.requestTokenEstimate === 'number'
@@ -1560,6 +1641,7 @@ export async function runHadamardTui(options = {}) {
1560
1641
  const delta = typeof data.delta === 'string' ? data.delta : '';
1561
1642
  if (!delta)
1562
1643
  return;
1644
+ collapseReasoning();
1563
1645
  streamedTextSeen = true;
1564
1646
  const flushed = flusher.push(delta);
1565
1647
  if (flushed.length > 0)
@@ -1571,10 +1653,7 @@ export async function runHadamardTui(options = {}) {
1571
1653
  const delta = typeof data.delta === 'string' ? data.delta : '';
1572
1654
  if (!delta)
1573
1655
  return;
1574
- const lines = formatThinking(delta, screen.width);
1575
- if (lines.length > 0)
1576
- appendStatic(lines);
1577
- streamedThinkingSeen = true;
1656
+ reasoningDisplay.append(delta);
1578
1657
  scheduleDynamicRender();
1579
1658
  return;
1580
1659
  }
@@ -1590,15 +1669,15 @@ export async function runHadamardTui(options = {}) {
1590
1669
  }
1591
1670
  case 'model.content': {
1592
1671
  const content = isRecord(data.content) ? data.content : undefined;
1593
- if (data.kind === 'content' && content?.type === 'thinking' && !streamedThinkingSeen) {
1672
+ if (data.kind === 'content' && content?.type === 'thinking' && !reasoningDisplay.hasStreamedContent) {
1594
1673
  const thinking = typeof content.thinking === 'string' ? content.thinking : '';
1595
- const lines = formatThinking(thinking, screen.width);
1596
- if (lines.length > 0)
1597
- appendStatic(lines);
1674
+ reasoningDisplay.setCompleteContent(thinking);
1675
+ scheduleDynamicRender();
1598
1676
  }
1599
1677
  return;
1600
1678
  }
1601
1679
  case 'tool.started': {
1680
+ collapseReasoning();
1602
1681
  const callId = typeof data.callId === 'string' ? data.callId : '';
1603
1682
  const publicName = typeof data.publicName === 'string'
1604
1683
  ? data.publicName
@@ -1735,35 +1814,37 @@ export async function runHadamardTui(options = {}) {
1735
1814
  await resumeSession(selected);
1736
1815
  }
1737
1816
  async function chooseModel() {
1817
+ const catalog = buildModelConfigurationCatalog({
1818
+ model: sdk.config.model,
1819
+ provider: sdk.config.provider,
1820
+ baseURL: sdk.config.baseURL,
1821
+ }, readBridgeConfigs().configs);
1738
1822
  const items = [
1739
- {
1740
- id: 'default',
1741
- label: 'Configured default',
1742
- description: sdk.config.model,
1743
- },
1744
- ...['min', 'medium', 'max']
1745
- .filter(tier => Boolean(sdk.config.modelTiers[tier]))
1746
- .map(tier => ({
1747
- id: `tier:${tier}`,
1748
- label: tier,
1749
- description: sdk.config.modelTiers[tier],
1823
+ ...catalog.map(config => ({
1824
+ id: config.id,
1825
+ label: config.source === 'default' ? 'default' : config.name,
1826
+ description: [
1827
+ config.execution === 'cli' ? `${config.runtime} CLI` : config.runtime,
1828
+ config.model || 'runtime default',
1829
+ config.models.length > 1 ? `${config.models.length} models` : '',
1830
+ ].filter(Boolean).join(' · '),
1750
1831
  })),
1751
1832
  {
1752
1833
  id: 'custom',
1753
1834
  label: 'Enter a model ID',
1754
- description: 'Session override',
1835
+ description: 'Use the default provider for this session',
1755
1836
  },
1756
1837
  {
1757
1838
  id: 'configure',
1758
- label: 'Configure provider, API key, and models',
1759
- description: 'Updates the active Hadamard Agent settings file',
1839
+ label: 'Manage configurations',
1840
+ description: 'Default plus named provider configurations',
1760
1841
  },
1761
1842
  ];
1762
1843
  const selected = await selectItem({
1763
1844
  title: 'Select model',
1764
1845
  subtitle: `Current: ${session.model}`,
1765
1846
  items,
1766
- searchable: false,
1847
+ searchable: true,
1767
1848
  });
1768
1849
  if (!selected)
1769
1850
  return;
@@ -1779,15 +1860,38 @@ export async function runHadamardTui(options = {}) {
1779
1860
  });
1780
1861
  if (!model?.trim())
1781
1862
  return;
1863
+ await disableBridge();
1782
1864
  await session.setModel(model.trim());
1783
1865
  }
1784
- else if (selected === 'default') {
1785
- await session.setModel(sdk.config.model);
1786
- }
1787
1866
  else {
1788
- await session.setModel(selected.slice('tier:'.length));
1867
+ const config = findModelConfiguration(catalog, selected);
1868
+ if (!config)
1869
+ return;
1870
+ if (config.source === 'default') {
1871
+ await disableBridge();
1872
+ await session.setModel(sdk.config.model);
1873
+ }
1874
+ else if (config.config) {
1875
+ let selectedConfig = { ...config.config, model: config.model };
1876
+ if (config.models.length > 1) {
1877
+ const model = await selectItem({
1878
+ title: config.name,
1879
+ subtitle: 'Select a model from this configuration',
1880
+ searchable: true,
1881
+ items: config.models.map(item => ({
1882
+ id: item.name,
1883
+ label: item.name,
1884
+ description: item.modality ?? 'text',
1885
+ })),
1886
+ });
1887
+ if (!model)
1888
+ return;
1889
+ selectedConfig = { ...selectedConfig, model };
1890
+ }
1891
+ await activateBridgeConfig(selectedConfig);
1892
+ }
1789
1893
  }
1790
- appendStatic([...formatInfoLine(`model set to: ${session.model}`), '']);
1894
+ appendStatic([...formatInfoLine(`model configuration: ${activeBridgeConfig?.name ?? 'default'} · ${bridgeModelLabel ?? session.model}`), '']);
1791
1895
  }
1792
1896
  async function configureModelSettings() {
1793
1897
  const store = await resolveHadamardSettingsStore({ configPath: options.configPath });
@@ -1826,11 +1930,16 @@ export async function runHadamardTui(options = {}) {
1826
1930
  label: 'Base URL',
1827
1931
  description: env.HADAMARD_BASE_URL || 'provider default',
1828
1932
  },
1829
- ...['min', 'medium', 'max'].map(tier => ({
1830
- id: `tier:${tier}`,
1831
- label: `${tier} model`,
1832
- description: env[`HADAMARD_DEFAULT_${tier.toUpperCase()}_MODEL`] || 'not configured',
1833
- })),
1933
+ {
1934
+ id: 'model',
1935
+ label: 'Default model',
1936
+ description: env.HADAMARD_MODEL || sdk.config.model || 'not configured',
1937
+ },
1938
+ {
1939
+ id: 'named',
1940
+ label: 'Named configurations',
1941
+ description: `${readBridgeConfigs().configs.length} configured`,
1942
+ },
1834
1943
  {
1835
1944
  id: 'save',
1836
1945
  label: 'Save and apply',
@@ -1900,21 +2009,23 @@ export async function runHadamardTui(options = {}) {
1900
2009
  }
1901
2010
  continue;
1902
2011
  }
1903
- if (selected.startsWith('tier:')) {
1904
- const tier = selected.slice('tier:'.length).toUpperCase();
1905
- const key = `HADAMARD_DEFAULT_${tier}_MODEL`;
2012
+ if (selected === 'model') {
1906
2013
  const model = await promptText({
1907
- title: `Configure ${tier.toLowerCase()} model`,
2014
+ title: 'Configure default model',
1908
2015
  label: 'Model ID',
1909
- initial: env[key] ?? '',
2016
+ initial: env.HADAMARD_MODEL ?? sdk.config.model,
1910
2017
  });
1911
2018
  if (model !== undefined) {
1912
2019
  if (model.trim())
1913
- env[key] = model.trim();
2020
+ env.HADAMARD_MODEL = model.trim();
1914
2021
  else
1915
- delete env[key];
2022
+ delete env.HADAMARD_MODEL;
1916
2023
  dirty = true;
1917
2024
  }
2025
+ continue;
2026
+ }
2027
+ if (selected === 'named') {
2028
+ await manageBridgeConfigs();
1918
2029
  }
1919
2030
  }
1920
2031
  }
@@ -3406,6 +3517,10 @@ export async function runHadamardTui(options = {}) {
3406
3517
  const spaceIndex = raw.indexOf(' ');
3407
3518
  const name = (spaceIndex === -1 ? raw.slice(1) : raw.slice(1, spaceIndex)).toLowerCase();
3408
3519
  const args = spaceIndex === -1 ? '' : raw.slice(spaceIndex + 1).trim();
3520
+ if (!canRunInteractiveCommand(name, running)) {
3521
+ appendStatic(formatInfoLine(`/${name} requires an idle session; it will be available after this run`));
3522
+ return;
3523
+ }
3409
3524
  appendStatic(formatUserPrompt(raw));
3410
3525
  commandBusy = true;
3411
3526
  renderDynamic();
@@ -3432,6 +3547,7 @@ export async function runHadamardTui(options = {}) {
3432
3547
  }
3433
3548
  case 'clear':
3434
3549
  process.stdout.write('\x1b[2J\x1b[H');
3550
+ screen.setDynamic([]);
3435
3551
  renderDynamic();
3436
3552
  return;
3437
3553
  case 'init': {
@@ -3458,8 +3574,71 @@ export async function runHadamardTui(options = {}) {
3458
3574
  await chooseRouter(args.slice('router'.length).trim());
3459
3575
  return;
3460
3576
  }
3461
- await session.setModel(args === 'default' ? sdk.config.model : args);
3462
- appendStatic([...formatInfoLine(`model set to: ${session.model}`), '']);
3577
+ if (args === 'custom' || args.startsWith('custom ')) {
3578
+ const customModel = args.slice('custom'.length).trim();
3579
+ if (!customModel) {
3580
+ appendStatic([...formatErrorLine('usage: /model custom <model-id>'), '']);
3581
+ return;
3582
+ }
3583
+ await disableBridge();
3584
+ await session.setModel(customModel);
3585
+ appendStatic([...formatInfoLine(`custom model: ${session.model}`), '']);
3586
+ return;
3587
+ }
3588
+ const catalog = buildModelConfigurationCatalog({ model: sdk.config.model, provider: sdk.config.provider, baseURL: sdk.config.baseURL }, readBridgeConfigs().configs);
3589
+ // `/model <config>` or `/model <config> <model-id>` — match the longest
3590
+ // known configuration name so model ids with spaces still work.
3591
+ let config = findModelConfiguration(catalog, args);
3592
+ let explicitModel;
3593
+ if (!config) {
3594
+ const tokens = args.split(/\s+/u).filter(Boolean);
3595
+ for (let i = tokens.length - 1; i >= 1; i -= 1) {
3596
+ const candidate = tokens.slice(0, i).join(' ');
3597
+ const match = findModelConfiguration(catalog, candidate);
3598
+ if (match) {
3599
+ config = match;
3600
+ explicitModel = tokens.slice(i).join(' ');
3601
+ break;
3602
+ }
3603
+ }
3604
+ }
3605
+ if (!config) {
3606
+ appendStatic([
3607
+ ...formatErrorLine(`unknown model configuration: ${args}`),
3608
+ ...formatInfoLine('Use /model to choose one, or /model custom <model-id>.'),
3609
+ '',
3610
+ ]);
3611
+ return;
3612
+ }
3613
+ if (config.source === 'default') {
3614
+ await disableBridge();
3615
+ await session.setModel(explicitModel || sdk.config.model);
3616
+ appendStatic([...formatInfoLine(`model configuration: default · ${session.model}`), '']);
3617
+ return;
3618
+ }
3619
+ if (!config.config)
3620
+ return;
3621
+ let selectedConfig = { ...config.config, model: config.model };
3622
+ if (explicitModel) {
3623
+ selectedConfig = { ...selectedConfig, model: explicitModel };
3624
+ }
3625
+ else if (config.models.length > 1) {
3626
+ const model = await selectItem({
3627
+ title: config.name,
3628
+ subtitle: 'Select a model from this configuration',
3629
+ searchable: true,
3630
+ items: config.models.map(item => ({
3631
+ id: item.name,
3632
+ label: item.name,
3633
+ description: item.modality ?? 'text',
3634
+ })),
3635
+ });
3636
+ if (!model)
3637
+ return;
3638
+ selectedConfig = { ...selectedConfig, model };
3639
+ }
3640
+ await activateBridgeConfig(selectedConfig);
3641
+ appendStatic([...formatInfoLine(`model configuration: ${activeBridgeConfig?.name ?? config.name} · ${bridgeModelLabel ?? session.model}`), '']);
3463
3642
  return;
3464
3643
  }
3465
3644
  case 'effort':
@@ -4234,8 +4413,40 @@ export async function runHadamardTui(options = {}) {
4234
4413
  await runSavedWorkflow(runSpace === -1 ? runRest : runRest.slice(0, runSpace), runSpace === -1 ? undefined : runRest.slice(runSpace + 1).trim());
4235
4414
  return;
4236
4415
  }
4416
+ if (args.startsWith('delete ')) {
4417
+ const name = args.slice(7).trim();
4418
+ if (!name) {
4419
+ appendStatic([...formatErrorLine('usage: /workflows delete <name>'), '']);
4420
+ return;
4421
+ }
4422
+ const removed = await deleteWorkflow(name, sdk.config.workDir);
4423
+ appendStatic([...formatInfoLine(removed ? `deleted workflow: ${name}` : `workflow not found: ${name}`), '']);
4424
+ return;
4425
+ }
4426
+ if (args.startsWith('save ')) {
4427
+ const rest = args.slice(5).trim();
4428
+ const split = rest.indexOf(' ');
4429
+ if (split === -1) {
4430
+ appendStatic([...formatErrorLine('usage: /workflows save <name> <script-path> [--overwrite]'), '']);
4431
+ return;
4432
+ }
4433
+ const name = rest.slice(0, split).trim();
4434
+ const pathParts = rest.slice(split + 1).trim().split(/\s+/);
4435
+ const overwrite = pathParts.includes('--overwrite');
4436
+ const scriptPath = pathParts.filter((part) => part !== '--overwrite').join(' ');
4437
+ try {
4438
+ const resolved = path.isAbsolute(scriptPath) ? scriptPath : path.resolve(sdk.config.workDir, scriptPath);
4439
+ const script = fs.readFileSync(resolved, 'utf8');
4440
+ const filePath = await saveWorkflow(name, script, { projectDir: sdk.config.workDir, overwrite });
4441
+ appendStatic([...formatInfoLine(`saved workflow: ${name} -> ${filePath}`), '']);
4442
+ }
4443
+ catch (error) {
4444
+ appendStatic([...formatErrorLine(error.message || String(error)), '']);
4445
+ }
4446
+ return;
4447
+ }
4237
4448
  if (args && args !== 'list') {
4238
- appendStatic([...formatErrorLine('usage: /workflows [list|run <name> [task]]'), '']);
4449
+ appendStatic([...formatErrorLine('usage: /workflows [list|run <name> [task]|save <name> <script-path>|delete <name>]'), '']);
4239
4450
  return;
4240
4451
  }
4241
4452
  // No sub-command → selection picker.
@@ -4455,15 +4666,36 @@ export async function runHadamardTui(options = {}) {
4455
4666
  }
4456
4667
  return;
4457
4668
  }
4669
+ if (args.startsWith('delete ')) {
4670
+ const teamName = args.slice(7).trim();
4671
+ if (!teamName) {
4672
+ appendStatic([...formatErrorLine('usage: /team delete <name>'), '']);
4673
+ return;
4674
+ }
4675
+ if (getBuiltInTeamDefinition(teamName)) {
4676
+ appendStatic([...formatErrorLine(`cannot delete built-in team: ${teamName}`), '']);
4677
+ return;
4678
+ }
4679
+ const removed = await deleteTeamDefinition(teamName, sdk.config.workDir);
4680
+ if (!removed) {
4681
+ appendStatic([...formatErrorLine(`team not found: ${teamName}`), '']);
4682
+ return;
4683
+ }
4684
+ if (activeTeamName === teamName) {
4685
+ activeTeamTool = null;
4686
+ activeTeamName = null;
4687
+ }
4688
+ appendStatic([...formatInfoLine(`deleted team: ${teamName}`), '']);
4689
+ return;
4690
+ }
4458
4691
  if (args.startsWith('ask ')) {
4459
4692
  const rest = args.slice(4).trim();
4460
- const spaceIdx = rest.indexOf(' ');
4461
- if (spaceIdx === -1) {
4693
+ const parsed = parseTeamAskArguments(rest);
4694
+ if (!parsed) {
4462
4695
  appendStatic([...formatErrorLine('usage: /team ask <name> <prompt>'), '']);
4463
4696
  return;
4464
4697
  }
4465
- const teamName = rest.slice(0, spaceIdx);
4466
- const prompt = rest.slice(spaceIdx + 1).trim();
4698
+ const { name: teamName, prompt } = parsed;
4467
4699
  const loaded = loadTeamDefinition(teamName, sdk.config.workDir);
4468
4700
  if (!loaded) {
4469
4701
  appendStatic([...formatErrorLine(`team not found: ${teamName}`), '']);
@@ -4489,7 +4721,6 @@ export async function runHadamardTui(options = {}) {
4489
4721
  '',
4490
4722
  ]);
4491
4723
  try {
4492
- const team = createModelTeam(definition);
4493
4724
  const teamRunView = createTeamRunViewState(definition.name);
4494
4725
  const printTeamRunTree = () => {
4495
4726
  const lines = formatTeamRunTreeLines(teamRunView);
@@ -4497,7 +4728,7 @@ export async function runHadamardTui(options = {}) {
4497
4728
  return;
4498
4729
  appendStatic([...lines.map((line) => `${A.dim}${line}${A.reset}`), '']);
4499
4730
  };
4500
- const result = await team.ask(prompt, undefined, {
4731
+ const result = await askTeamDefinition(definition, prompt, undefined, {
4501
4732
  workDir: sdk.config.workDir,
4502
4733
  onEvent: (e) => {
4503
4734
  applyTeamRunEvent(teamRunView, e);
@@ -5165,17 +5396,31 @@ export async function runHadamardTui(options = {}) {
5165
5396
  }
5166
5397
  }
5167
5398
  // ── Submit / key handling ──────────────────────────────────────────
5168
- async function submit() {
5169
- if (!running && applyAtCompletion()) {
5399
+ /** If the editor was emptied after ↑-recall, put the follow-up back in queue. */
5400
+ function restoreRecalledFollowUpIfAbandoned() {
5401
+ if (!recalledFollowUp || !editor.isEmpty())
5402
+ return;
5403
+ restoreAbandonedFollowUp(session, recalledFollowUp);
5404
+ recalledFollowUp = null;
5405
+ }
5406
+ async function submit(mode = 'follow-up') {
5407
+ if (applyAtCompletion()) {
5170
5408
  renderDynamic();
5171
5409
  return;
5172
5410
  }
5173
- const selectedCommand = !running
5174
- ? selectInteractiveCommand(editor.text, menuSelected)
5175
- : undefined;
5411
+ const selectedCommand = selectInteractiveCommand(editor.text, menuSelected);
5176
5412
  if (selectedCommand) {
5413
+ const selectedName = selectedCommand.slice(1).split(/\s/u, 1)[0] ?? '';
5414
+ // Slash completions are not a resubmit of the recalled follow-up.
5415
+ restoreAbandonedFollowUp(session, recalledFollowUp);
5416
+ recalledFollowUp = null;
5177
5417
  editor.clear();
5178
5418
  menuSelected = 0;
5419
+ if (running && !canRunInteractiveCommand(selectedName, true)) {
5420
+ appendStatic(formatInfoLine(`/${selectedName} requires an idle session; it will be available after this run`));
5421
+ renderDynamic();
5422
+ return;
5423
+ }
5179
5424
  // A selected completion already contains its subcommand. Keeping the
5180
5425
  // partially typed second word would turn `/agents runs` into
5181
5426
  // `/agents runs runs` on Enter.
@@ -5190,17 +5435,31 @@ export async function runHadamardTui(options = {}) {
5190
5435
  const text = value.trim();
5191
5436
  menuSelected = 0;
5192
5437
  if (!text) {
5438
+ restoreRecalledFollowUpIfAbandoned();
5193
5439
  renderDynamic();
5194
5440
  return;
5195
5441
  }
5196
5442
  if (running) {
5197
5443
  if (text.startsWith('/')) {
5198
- appendStatic(formatInfoLine('slash commands are unavailable while the agent is working'));
5444
+ // Slash commands are not a resubmit of the recalled follow-up.
5445
+ restoreAbandonedFollowUp(session, recalledFollowUp);
5446
+ recalledFollowUp = null;
5447
+ const command = text.slice(1).trim().split(/\s/u, 1)[0]?.toLowerCase() ?? '';
5448
+ if (!canRunInteractiveCommand(command, true)) {
5449
+ appendStatic(formatInfoLine(`/${command} requires an idle session; it will be available after this run`));
5450
+ renderDynamic();
5451
+ return;
5452
+ }
5453
+ await runSlashCommand(text);
5199
5454
  renderDynamic();
5200
5455
  return;
5201
5456
  }
5202
- session.steer(text);
5203
- appendStatic(formatQueuedPrompt(text));
5457
+ // Resubmit consumes the recalled draft (re-queued below).
5458
+ recalledFollowUp = null;
5459
+ submitActiveInput(session, text, mode);
5460
+ appendStatic(mode === 'steer'
5461
+ ? formatInfoLine(`steering now: ${text.replace(/\s+/g, ' ')}`)
5462
+ : formatQueuedPrompt(text));
5204
5463
  renderDynamic();
5205
5464
  return;
5206
5465
  }
@@ -5209,9 +5468,12 @@ export async function runHadamardTui(options = {}) {
5209
5468
  return;
5210
5469
  }
5211
5470
  if (text.startsWith('/')) {
5471
+ restoreAbandonedFollowUp(session, recalledFollowUp);
5472
+ recalledFollowUp = null;
5212
5473
  await runSlashCommand(text);
5213
5474
  return;
5214
5475
  }
5476
+ recalledFollowUp = null;
5215
5477
  void startRun(text);
5216
5478
  }
5217
5479
  function handleDialogKey(key) {
@@ -5392,6 +5654,7 @@ export async function runHadamardTui(options = {}) {
5392
5654
  else if (!editor.isEmpty()) {
5393
5655
  editor.clear();
5394
5656
  menuSelected = 0;
5657
+ restoreRecalledFollowUpIfAbandoned();
5395
5658
  }
5396
5659
  renderDynamic();
5397
5660
  return;
@@ -5402,6 +5665,7 @@ export async function runHadamardTui(options = {}) {
5402
5665
  return;
5403
5666
  }
5404
5667
  editor.deleteForward();
5668
+ restoreRecalledFollowUpIfAbandoned();
5405
5669
  break;
5406
5670
  case 'a':
5407
5671
  editor.moveHome();
@@ -5411,12 +5675,15 @@ export async function runHadamardTui(options = {}) {
5411
5675
  break;
5412
5676
  case 'k':
5413
5677
  editor.killToEnd();
5678
+ restoreRecalledFollowUpIfAbandoned();
5414
5679
  break;
5415
5680
  case 'u':
5416
5681
  editor.killToStart();
5682
+ restoreRecalledFollowUpIfAbandoned();
5417
5683
  break;
5418
5684
  case 'w':
5419
5685
  editor.deleteWordLeft();
5686
+ restoreRecalledFollowUpIfAbandoned();
5420
5687
  break;
5421
5688
  case 'left':
5422
5689
  editor.moveWordLeft();
@@ -5438,6 +5705,13 @@ export async function runHadamardTui(options = {}) {
5438
5705
  }
5439
5706
  switch (name) {
5440
5707
  case 'return': {
5708
+ if (key.shift) {
5709
+ if (running)
5710
+ void submit('steer');
5711
+ else
5712
+ editor.insert('\n');
5713
+ return;
5714
+ }
5441
5715
  if (key.meta) {
5442
5716
  editor.insert('\n');
5443
5717
  break;
@@ -5446,6 +5720,10 @@ export async function runHadamardTui(options = {}) {
5446
5720
  return;
5447
5721
  }
5448
5722
  case 'enter':
5723
+ if (key.shift && running) {
5724
+ void submit('steer');
5725
+ return;
5726
+ }
5449
5727
  editor.insert('\n');
5450
5728
  break;
5451
5729
  case 'escape': {
@@ -5455,6 +5733,7 @@ export async function runHadamardTui(options = {}) {
5455
5733
  else if (!editor.isEmpty()) {
5456
5734
  editor.clear();
5457
5735
  menuSelected = 0;
5736
+ restoreRecalledFollowUpIfAbandoned();
5458
5737
  }
5459
5738
  break;
5460
5739
  }
@@ -5462,9 +5741,11 @@ export async function runHadamardTui(options = {}) {
5462
5741
  editor.backspace();
5463
5742
  menuSelected = 0;
5464
5743
  atSelected = 0;
5744
+ restoreRecalledFollowUpIfAbandoned();
5465
5745
  break;
5466
5746
  case 'delete':
5467
5747
  editor.deleteForward();
5748
+ restoreRecalledFollowUpIfAbandoned();
5468
5749
  break;
5469
5750
  case 'left':
5470
5751
  if (key.meta)
@@ -5485,6 +5766,14 @@ export async function runHadamardTui(options = {}) {
5485
5766
  editor.moveEnd();
5486
5767
  break;
5487
5768
  case 'up': {
5769
+ if (running && editor.isEmpty()) {
5770
+ const recalled = recallLatestFollowUp(session);
5771
+ if (recalled) {
5772
+ recalledFollowUp = recalled;
5773
+ editor.setText(recalled);
5774
+ break;
5775
+ }
5776
+ }
5488
5777
  const atToken = activeAtToken(editor.text, editor.cursor);
5489
5778
  const atCount = atToken ? atCompletions(atToken.token).length : 0;
5490
5779
  const menu = filterSlashCommands(editor.text);