actoviq-agent-sdk 0.4.13 → 0.4.15

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 (41) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/src/cli/hadamard-tui.js +5 -4
  3. package/dist/src/cli/hadamard-tui.js.map +1 -1
  4. package/dist/src/config/modelConfigurationCatalog.d.ts +4 -0
  5. package/dist/src/config/modelConfigurationCatalog.d.ts.map +1 -1
  6. package/dist/src/config/modelConfigurationCatalog.js +8 -0
  7. package/dist/src/config/modelConfigurationCatalog.js.map +1 -1
  8. package/dist/src/config/projectSettings.d.ts +8 -1
  9. package/dist/src/config/projectSettings.d.ts.map +1 -1
  10. package/dist/src/config/projectSettings.js +15 -0
  11. package/dist/src/config/projectSettings.js.map +1 -1
  12. package/dist/src/gui/hadamardGui.d.ts.map +1 -1
  13. package/dist/src/gui/hadamardGui.js +58 -15
  14. package/dist/src/gui/hadamardGui.js.map +1 -1
  15. package/dist/src/memory/projectContext.d.ts +16 -11
  16. package/dist/src/memory/projectContext.d.ts.map +1 -1
  17. package/dist/src/memory/projectContext.js +45 -37
  18. package/dist/src/memory/projectContext.js.map +1 -1
  19. package/dist/src/prompts/systemPrompt.js +2 -2
  20. package/dist/src/prompts/systemPrompt.js.map +1 -1
  21. package/dist/src/runtime/agentClient.d.ts.map +1 -1
  22. package/dist/src/runtime/agentClient.js +36 -3
  23. package/dist/src/runtime/agentClient.js.map +1 -1
  24. package/dist/src/runtime/conversationEngine.d.ts.map +1 -1
  25. package/dist/src/runtime/conversationEngine.js +3 -2
  26. package/dist/src/runtime/conversationEngine.js.map +1 -1
  27. package/dist/src/tui/hadamardTui.d.ts.map +1 -1
  28. package/dist/src/tui/hadamardTui.js +126 -26
  29. package/dist/src/tui/hadamardTui.js.map +1 -1
  30. package/dist/src/tui/reasoningDisplay.d.ts +1 -0
  31. package/dist/src/tui/reasoningDisplay.d.ts.map +1 -1
  32. package/dist/src/tui/reasoningDisplay.js +10 -3
  33. package/dist/src/tui/reasoningDisplay.js.map +1 -1
  34. package/dist/src/tui/toolActivityDisplay.d.ts +8 -0
  35. package/dist/src/tui/toolActivityDisplay.d.ts.map +1 -0
  36. package/dist/src/tui/toolActivityDisplay.js +24 -0
  37. package/dist/src/tui/toolActivityDisplay.js.map +1 -0
  38. package/dist/src/ui/commandSurface.d.ts.map +1 -1
  39. package/dist/src/ui/commandSurface.js +18 -8
  40. package/dist/src/ui/commandSurface.js.map +1 -1
  41. package/package.json +3 -1
@@ -18,11 +18,12 @@ import { parseCrushSessionReferenceDetails } from '../parity/crushSessionHistory
18
18
  import { readProjectMeta } from '../gui/projectMeta.js';
19
19
  import { readWorkspaceRegistry } from '../gui/workspaceRegistry.js';
20
20
  import { persistHadamardSettingsStore, resolveHadamardSettingsStore, } from '../config/hadamardSettingsStore.js';
21
+ import { readProjectSettings, writeProjectSettings, } from '../config/projectSettings.js';
21
22
  import { createPreToolUseHookClassifier, readPreToolUseHooks, readPostToolUseHooks, runPostToolUseHooks, readSessionStartHooks, runSessionStartHooks } from '../hooks/userHooks.js';
22
23
  import { parseTypedHooks } from '../hooks/hookConfig.js';
23
24
  import { isRecord } from '../runtime/helpers.js';
24
25
  import { getLoadedJsonConfig } from '../config/loadJsonConfigFile.js';
25
- import { buildModelConfigurationCatalog, findModelConfiguration, resolveHadamardConfigurationModel, } from '../config/modelConfigurationCatalog.js';
26
+ import { buildModelConfigurationCatalog, findModelConfiguration, isModelCredentialConfigured, resolveHadamardConfigurationModel, } from '../config/modelConfigurationCatalog.js';
26
27
  import { findBridgeConfig, maskApiKey, readBridgeConfigs, addBridgeConfig, removeBridgeConfig, isManagedExternalCliRuntime, } from '../parity/bridgeConfigs.js';
27
28
  import { buildRouteModelApi } from '../router/modelRouter.js';
28
29
  import { addMcpServer, readMcpServerConfig, removeMcpServer } from '../mcp/mcpServerConfig.js';
@@ -40,6 +41,7 @@ import { InputEditor } from './editor.js';
40
41
  import { discoverHadamardPlugins } from './pluginCatalog.js';
41
42
  import { TuiScreen } from './screen.js';
42
43
  import { ReasoningDisplayState } from './reasoningDisplay.js';
44
+ import { ToolActivityDisplayState } from './toolActivityDisplay.js';
43
45
  import { recallLatestFollowUp, restoreAbandonedFollowUp, submitActiveInput, } from './pendingInput.js';
44
46
  import { filterTuiSelectionItems, moveTuiSelection, } from './selection.js';
45
47
  import { StreamFlusher, formatBanner, formatCompactNotice, formatDivider, formatEditCall, formatErrorLine, formatInfoLine, formatQueuedPrompt, formatToolCall, formatToolResult, formatUserPrompt, } from './transcript.js';
@@ -172,7 +174,7 @@ export function renderRichText(text, width, opts = {}) {
172
174
  }
173
175
  return out;
174
176
  }
175
- function buildSystemPrompt(workDir) {
177
+ function buildSystemPrompt(workDir, projectSettings, hadamardHomeDir) {
176
178
  let isGit = false;
177
179
  try {
178
180
  execSync('git rev-parse --is-inside-work-tree', { cwd: workDir, stdio: 'ignore' });
@@ -181,11 +183,14 @@ function buildSystemPrompt(workDir) {
181
183
  catch {
182
184
  // not a git repo
183
185
  }
184
- // Load the CLAUDE.md hierarchy (user + project, with @includes) so the agent
186
+ // Load Hadamard instruction files (AGENTS.md by default) so the agent
185
187
  // picks up project-specific instructions — the canonical Claude Code behavior.
186
- const project = loadProjectContext(workDir);
188
+ const project = loadProjectContext(workDir, {
189
+ projectInstructionMode: projectSettings.context.instructionMode,
190
+ hadamardHomeDir,
191
+ });
187
192
  const projectSection = project.text
188
- ? `\n\n# Project context (CLAUDE.md)\n\nThe following project instructions were loaded from CLAUDE.md files. Treat them as authoritative guidance for this workspace.\n\n${project.text}\n`
193
+ ? `\n\n# Project context (AGENTS.md)\n\nThe following instruction files are authoritative guidance for this workspace.\n\n${project.text}\n`
189
194
  : '';
190
195
  return (`You are Hadamard Agent, an interactive CLI agent. Working directory: ${workDir}\n\n` +
191
196
  `<env>\nWorking directory: ${workDir}\nIs git repo: ${isGit ? 'Yes' : 'No'}\nPlatform: ${process.platform}\nDate: ${new Date().toISOString().slice(0, 10)}\n</env>\n\n` +
@@ -237,7 +242,6 @@ async function onboardCredentials(configPath) {
237
242
  export async function runHadamardTui(options = {}) {
238
243
  const workDir = path.resolve(options.workDir ?? process.cwd());
239
244
  const permissionMode = options.permissionMode ?? 'bypassPermissions';
240
- const systemPrompt = buildSystemPrompt(workDir);
241
245
  try {
242
246
  if (options.configPath) {
243
247
  await loadJsonConfigFile(options.configPath);
@@ -249,6 +253,9 @@ export async function runHadamardTui(options = {}) {
249
253
  catch {
250
254
  // Missing local config is fine; env vars may carry credentials.
251
255
  }
256
+ const hadamardHomeDir = resolveHadamardHome();
257
+ let projectSettings = await readProjectSettings(workDir, hadamardHomeDir);
258
+ let systemPrompt = buildSystemPrompt(workDir, projectSettings, hadamardHomeDir);
252
259
  let applyPlanPermission = null;
253
260
  let managedPluginRuntime = null;
254
261
  let tools = [];
@@ -654,6 +661,8 @@ export async function runHadamardTui(options = {}) {
654
661
  let spinnerTimer = null;
655
662
  let dynamicRenderTimer = null;
656
663
  let runStartedAt = 0;
664
+ let requestStartedAt = 0;
665
+ let providerActivitySeen = false;
657
666
  let runToolCount = 0;
658
667
  let lastTokenEstimate;
659
668
  // Running token + USD totals for /cost and /usage. Per-config breakdown
@@ -665,6 +674,11 @@ export async function runHadamardTui(options = {}) {
665
674
  function recordUsage(model, usage) {
666
675
  const inT = usage?.input_tokens ?? 0;
667
676
  const outT = usage?.output_tokens ?? 0;
677
+ const reportedContextTokens = inT
678
+ + (usage?.cache_creation_input_tokens ?? 0)
679
+ + (usage?.cache_read_input_tokens ?? 0);
680
+ if (reportedContextTokens > 0)
681
+ lastTokenEstimate = reportedContextTokens;
668
682
  totalInputTokens += inT;
669
683
  totalOutputTokens += outT;
670
684
  const cost = estimateCost(model, inT, outT);
@@ -690,6 +704,7 @@ export async function runHadamardTui(options = {}) {
690
704
  let ctrlCTimer = null;
691
705
  let streamedTextSeen = false;
692
706
  const reasoningDisplay = new ReasoningDisplayState();
707
+ const toolActivityDisplay = new ToolActivityDisplayState();
693
708
  // Track tool names by callId for PostToolUse hook context.
694
709
  const toolCallNames = new Map();
695
710
  // Live todo list (captured from TodoWrite tool calls). Rendered as a
@@ -1049,8 +1064,7 @@ export async function runHadamardTui(options = {}) {
1049
1064
  const selected = index === menuSelected;
1050
1065
  const command = `/${name}`.padEnd(commandWidth);
1051
1066
  const label = selected ? `${A.inverse}${command}${A.reset}` : command;
1052
- const commandName = name.split(/\s/u, 1)[0] ?? name;
1053
- const unavailable = running && interactiveCommandRunPolicy(commandName) === 'idle-only';
1067
+ const unavailable = running && interactiveCommandRunPolicy(name) === 'idle-only';
1054
1068
  const summary = TUI_SLASH_COMMANDS[name] ?? SUBCOMMAND_DESCRIPTIONS[name] ?? '';
1055
1069
  const description = truncateToWidth(unavailable ? `${summary} · after current run` : summary, descriptionWidth);
1056
1070
  return `${label} ${A.dim}${description}${A.reset}`;
@@ -1068,7 +1082,7 @@ export async function runHadamardTui(options = {}) {
1068
1082
  const selected = matches[Math.min(menuSelected, matches.length - 1)];
1069
1083
  if (selected) {
1070
1084
  const commandName = selected.split(/\s/u, 1)[0] ?? selected;
1071
- const availability = interactiveCommandRunPolicy(commandName) === 'during-run'
1085
+ const availability = interactiveCommandRunPolicy(selected) === 'during-run'
1072
1086
  ? 'available while working'
1073
1087
  : 'idle session';
1074
1088
  lines.push(`${A.dim} ${interactiveCommandUsage(commandName)} · ${availability}${A.reset}`);
@@ -1472,13 +1486,21 @@ export async function runHadamardTui(options = {}) {
1472
1486
  }
1473
1487
  running = true;
1474
1488
  runStartedAt = Date.now();
1489
+ requestStartedAt = 0;
1490
+ providerActivitySeen = false;
1475
1491
  runToolCount = 0;
1476
- statusNote = '';
1492
+ toolActivityDisplay.reset();
1493
+ toolCallNames.clear();
1494
+ statusNote = 'preparing locally';
1477
1495
  streamedTextSeen = false;
1478
1496
  lastTokenEstimate = undefined;
1479
1497
  abortCtrl = new AbortController();
1480
1498
  spinnerTimer = setInterval(() => {
1481
1499
  spinnerFrame += 1;
1500
+ if (requestStartedAt > 0 && !providerActivitySeen) {
1501
+ const waitingSeconds = Math.max(Math.round((Date.now() - requestStartedAt) / 1000), 0);
1502
+ statusNote = `waiting for model ${waitingSeconds}s`;
1503
+ }
1482
1504
  renderDynamic();
1483
1505
  }, 120);
1484
1506
  appendStatic(formatUserPrompt(text));
@@ -1630,8 +1652,13 @@ export async function runHadamardTui(options = {}) {
1630
1652
  case 'run.started':
1631
1653
  streamedTextSeen = false;
1632
1654
  reasoningDisplay.reset();
1655
+ toolActivityDisplay.reset();
1656
+ toolCallNames.clear();
1633
1657
  return;
1634
1658
  case 'request.started':
1659
+ requestStartedAt = Date.now();
1660
+ providerActivitySeen = false;
1661
+ statusNote = 'waiting for model 0s';
1635
1662
  lastTokenEstimate = typeof data.requestTokenEstimate === 'number'
1636
1663
  ? data.requestTokenEstimate
1637
1664
  : undefined;
@@ -1641,6 +1668,7 @@ export async function runHadamardTui(options = {}) {
1641
1668
  const delta = typeof data.delta === 'string' ? data.delta : '';
1642
1669
  if (!delta)
1643
1670
  return;
1671
+ providerActivitySeen = true;
1644
1672
  collapseReasoning();
1645
1673
  streamedTextSeen = true;
1646
1674
  const flushed = flusher.push(delta);
@@ -1653,11 +1681,13 @@ export async function runHadamardTui(options = {}) {
1653
1681
  const delta = typeof data.delta === 'string' ? data.delta : '';
1654
1682
  if (!delta)
1655
1683
  return;
1684
+ providerActivitySeen = true;
1656
1685
  reasoningDisplay.append(delta);
1657
1686
  scheduleDynamicRender();
1658
1687
  return;
1659
1688
  }
1660
1689
  case 'tool.input.delta': {
1690
+ providerActivitySeen = true;
1661
1691
  const name = typeof data.name === 'string' && data.name ? data.name : 'tool';
1662
1692
  const snapshot = typeof data.snapshot === 'string'
1663
1693
  ? data.snapshot.trim().replace(/\s+/g, ' ')
@@ -1668,6 +1698,7 @@ export async function runHadamardTui(options = {}) {
1668
1698
  return;
1669
1699
  }
1670
1700
  case 'model.content': {
1701
+ providerActivitySeen = true;
1671
1702
  const content = isRecord(data.content) ? data.content : undefined;
1672
1703
  if (data.kind === 'content' && content?.type === 'thinking' && !reasoningDisplay.hasStreamedContent) {
1673
1704
  const thinking = typeof content.thinking === 'string' ? content.thinking : '';
@@ -1677,8 +1708,11 @@ export async function runHadamardTui(options = {}) {
1677
1708
  return;
1678
1709
  }
1679
1710
  case 'tool.started': {
1680
- collapseReasoning();
1681
1711
  const callId = typeof data.callId === 'string' ? data.callId : '';
1712
+ if (!toolActivityDisplay.markStarted(callId))
1713
+ return;
1714
+ providerActivitySeen = true;
1715
+ collapseReasoning();
1682
1716
  const publicName = typeof data.publicName === 'string'
1683
1717
  ? data.publicName
1684
1718
  : typeof data.name === 'string' ? data.name : 'tool';
@@ -1727,6 +1761,8 @@ export async function runHadamardTui(options = {}) {
1727
1761
  case 'tool.failed':
1728
1762
  case 'tool.rejected': {
1729
1763
  const callId = typeof data.callId === 'string' ? data.callId : '';
1764
+ if (!toolActivityDisplay.markTerminal(callId))
1765
+ return;
1730
1766
  const outputText = typeof data.outputText === 'string' ? data.outputText : '';
1731
1767
  statusNote = '';
1732
1768
  appendStatic(formatToolResult({
@@ -1739,6 +1775,8 @@ export async function runHadamardTui(options = {}) {
1739
1775
  if (toolName) {
1740
1776
  runPostToolUseHooks(() => readPostToolUseHooks(getLoadedJsonConfig()?.raw), toolName, null, outputText, sdk.config.workDir);
1741
1777
  }
1778
+ if (callId)
1779
+ toolCallNames.delete(callId);
1742
1780
  renderDynamic();
1743
1781
  return;
1744
1782
  }
@@ -1813,6 +1851,43 @@ export async function runHadamardTui(options = {}) {
1813
1851
  if (selected)
1814
1852
  await resumeSession(selected);
1815
1853
  }
1854
+ async function configureContextSettings(requestedMode = '') {
1855
+ const normalized = requestedMode.trim().toLowerCase();
1856
+ let instructionMode;
1857
+ if (normalized === 'agents' || normalized === 'claude' || normalized === 'both') {
1858
+ instructionMode = normalized;
1859
+ }
1860
+ else if (normalized) {
1861
+ appendStatic([...formatErrorLine('usage: /context settings [agents|claude|both]'), '']);
1862
+ return;
1863
+ }
1864
+ else {
1865
+ const selected = await selectItem({
1866
+ title: 'Context settings',
1867
+ subtitle: `Current project mode: ${projectSettings.context.instructionMode}`,
1868
+ searchable: false,
1869
+ items: [
1870
+ { id: 'agents', label: 'AGENTS.md', description: 'Recommended Hadamard default' },
1871
+ { id: 'claude', label: 'CLAUDE.md', description: 'Project-level Claude compatibility' },
1872
+ { id: 'both', label: 'AGENTS.md + CLAUDE.md', description: 'Load both project instruction formats' },
1873
+ ],
1874
+ });
1875
+ if (selected === 'agents' || selected === 'claude' || selected === 'both') {
1876
+ instructionMode = selected;
1877
+ }
1878
+ }
1879
+ if (!instructionMode)
1880
+ return;
1881
+ projectSettings = await writeProjectSettings(workDir, hadamardHomeDir, {
1882
+ context: { instructionMode },
1883
+ });
1884
+ systemPrompt = buildSystemPrompt(workDir, projectSettings, hadamardHomeDir);
1885
+ appendStatic([
1886
+ ...formatInfoLine(`context instructions: ${instructionMode}`),
1887
+ ...formatInfoLine('Global rules remain ~/.hadamard/AGENTS.md.'),
1888
+ '',
1889
+ ]);
1890
+ }
1816
1891
  async function chooseModel() {
1817
1892
  const catalog = buildModelConfigurationCatalog({
1818
1893
  model: sdk.config.model,
@@ -1923,7 +1998,7 @@ export async function runHadamardTui(options = {}) {
1923
1998
  {
1924
1999
  id: 'api-key',
1925
2000
  label: 'API key',
1926
- description: env.HADAMARD_API_KEY || env.HADAMARD_AUTH_TOKEN ? 'configured' : 'not configured',
2001
+ description: isModelCredentialConfigured(env, sdk.config) ? 'configured' : 'not configured',
1927
2002
  },
1928
2003
  {
1929
2004
  id: 'base-url',
@@ -3517,7 +3592,7 @@ export async function runHadamardTui(options = {}) {
3517
3592
  const spaceIndex = raw.indexOf(' ');
3518
3593
  const name = (spaceIndex === -1 ? raw.slice(1) : raw.slice(1, spaceIndex)).toLowerCase();
3519
3594
  const args = spaceIndex === -1 ? '' : raw.slice(spaceIndex + 1).trim();
3520
- if (!canRunInteractiveCommand(name, running)) {
3595
+ if (!canRunInteractiveCommand(raw.slice(1), running)) {
3521
3596
  appendStatic(formatInfoLine(`/${name} requires an idle session; it will be available after this run`));
3522
3597
  return;
3523
3598
  }
@@ -3551,10 +3626,10 @@ export async function runHadamardTui(options = {}) {
3551
3626
  renderDynamic();
3552
3627
  return;
3553
3628
  case 'init': {
3554
- // Bootstrap a CLAUDE.md by having the agent explore the repo and
3555
- // write concise guidance — complements the CLAUDE.md loader (the
3629
+ // Bootstrap AGENTS.md by having the agent explore the repo and
3630
+ // write concise guidance for subsequent Hadamard sessions.
3556
3631
  // generated file is then injected into every system prompt).
3557
- await startRun('Create or update a CLAUDE.md at the repo root with concise guidance for AI coding assistants: the build/test/lint/run commands, a short architecture overview, key conventions, and non-obvious gotchas. Explore with Glob, Grep, and Read first (package.json, README, existing CLAUDE.md, key source dirs). If a CLAUDE.md already exists, improve it without discarding user-authored sections. Keep it focused — no filler.');
3632
+ await startRun('Create or update an AGENTS.md at the repo root with concise guidance for AI coding assistants: build/test/lint/run commands, a short architecture overview, key conventions, and non-obvious gotchas. Explore with Glob, Grep, and Read first (package.json, README, existing AGENTS.md, key source dirs). If AGENTS.md already exists, improve it without discarding user-authored sections. Keep it focused and avoid filler.');
3558
3633
  return;
3559
3634
  }
3560
3635
  case 'exit':
@@ -3927,9 +4002,22 @@ export async function runHadamardTui(options = {}) {
3927
4002
  return;
3928
4003
  }
3929
4004
  case 'context': {
4005
+ const contextArgs = args.trim();
4006
+ if (contextArgs === 'setting' || contextArgs === 'settings') {
4007
+ await configureContextSettings();
4008
+ return;
4009
+ }
4010
+ if (contextArgs.startsWith('setting ') || contextArgs.startsWith('settings ')) {
4011
+ await configureContextSettings(contextArgs.replace(/^settings?\s+/u, ''));
4012
+ return;
4013
+ }
4014
+ if (contextArgs) {
4015
+ appendStatic([...formatErrorLine('usage: /context [settings [agents|claude|both]]'), '']);
4016
+ return;
4017
+ }
3930
4018
  // Break down what is consuming the context window (gap #9 vs
3931
4019
  // claude-code's /context) — usage, messages, system prompt, tools,
3932
- // the loaded CLAUDE.md sources, and the active config.
4020
+ // the loaded project instruction sources, and the active config.
3933
4021
  const { resolveHadamardCompactBudget } = await import('../runtime/hadamardCompact.js');
3934
4022
  const compactBudget = resolveHadamardCompactBudget(sdk.config.compact);
3935
4023
  const window = compactBudget.effectiveContextWindowTokens;
@@ -3941,7 +4029,10 @@ export async function runHadamardTui(options = {}) {
3941
4029
  const messages = session.messages.length;
3942
4030
  const sysChars = systemPrompt.length;
3943
4031
  const mcpCount = toolMetadata.filter(t => t.provider === 'mcp').length;
3944
- const project = loadProjectContext(sdk.config.workDir);
4032
+ const project = loadProjectContext(sdk.config.workDir, {
4033
+ projectInstructionMode: projectSettings.context.instructionMode,
4034
+ hadamardHomeDir,
4035
+ });
3945
4036
  const team = activeTeamName ?? 'none';
3946
4037
  const router = activeRouter ? activeRouter.name : 'off';
3947
4038
  const bridge = bridgeMode && activeBridgeConfig ? activeBridgeConfig.name : 'off';
@@ -3953,7 +4044,7 @@ export async function runHadamardTui(options = {}) {
3953
4044
  ` ${A.dim}messages${A.reset} ${messages}`,
3954
4045
  ` ${A.dim}system prompt${A.reset} ~${sysChars} chars`,
3955
4046
  ` ${A.dim}tools${A.reset} ${toolMetadata.length}${mcpCount > 0 ? ` (${mcpCount} MCP)` : ''}`,
3956
- ` ${A.dim}CLAUDE.md${A.reset} ${project.sources.length ? project.sources.join(', ') : '(none loaded)'}`,
4047
+ ` ${A.dim}instruction files${A.reset} ${project.sources.length ? project.sources.join(', ') : '(none loaded)'}`,
3957
4048
  ` ${A.dim}active${A.reset} model=${session.model} · effort=${currentEffort() ?? 'auto'} · team=${team} · router=${router} · bridge=${bridge}`,
3958
4049
  '',
3959
4050
  ]);
@@ -4015,8 +4106,11 @@ export async function runHadamardTui(options = {}) {
4015
4106
  lines.push(` ${ok(true)} permission mode ${A.dim}${currentPermissionMode()}${A.reset}`);
4016
4107
  lines.push(` ${ok(toolMetadata.length > 0)} tools ${A.dim}${toolMetadata.length}${A.reset}`);
4017
4108
  // Context memory
4018
- const project = loadProjectContext(cfg.workDir);
4019
- lines.push(` ${ok(project.sources.length > 0)} CLAUDE.md ${A.dim}${project.sources.length ? project.sources.join(', ') : '(none)'}${A.reset}`);
4109
+ const project = loadProjectContext(cfg.workDir, {
4110
+ projectInstructionMode: projectSettings.context.instructionMode,
4111
+ hadamardHomeDir,
4112
+ });
4113
+ lines.push(` ${ok(project.sources.length > 0)} instruction files ${A.dim}${project.sources.length ? project.sources.join(', ') : '(none)'}${A.reset}`);
4020
4114
  // Bridge runtimes
4021
4115
  const detections = await withSpinner('detecting runtimes', detectBridgeProviders);
4022
4116
  const avail = detections.filter(d => d.available);
@@ -4106,7 +4200,7 @@ export async function runHadamardTui(options = {}) {
4106
4200
  case 'compact': {
4107
4201
  try {
4108
4202
  const summaryInstructions = args || undefined;
4109
- const result = await session.compact({ force: true, summaryInstructions });
4203
+ const result = await session.compact({ summaryInstructions });
4110
4204
  if (!result.compacted) {
4111
4205
  appendStatic([
4112
4206
  ...formatErrorLine(result.error ?? `compact skipped: ${result.reason}`),
@@ -4969,9 +5063,12 @@ export async function runHadamardTui(options = {}) {
4969
5063
  __hadamardUseDefaultTools: false,
4970
5064
  __hadamardAllowedTools: tools.map(item => item.name),
4971
5065
  });
5066
+ const assistantToolDisplay = new ToolActivityDisplayState();
4972
5067
  for await (const event of stream) {
4973
- if (event.type === 'tool.call')
5068
+ if (event.type === 'tool.call'
5069
+ && assistantToolDisplay.markStarted(event.call.id)) {
4974
5070
  appendStatic([`${A.dim} ⚙ ${event.call.name}${A.reset}`]);
5071
+ }
4975
5072
  }
4976
5073
  const result = await stream.result;
4977
5074
  if (result.text)
@@ -5321,9 +5418,12 @@ export async function runHadamardTui(options = {}) {
5321
5418
  __hadamardAllowedTools: managerTools.map((tool) => tool.name),
5322
5419
  };
5323
5420
  const stream = managerTuiSession.stream(prompt, runOptions);
5421
+ const managerToolDisplay = new ToolActivityDisplayState();
5324
5422
  for await (const event of stream) {
5325
- if (event.type === 'tool.call')
5423
+ if (event.type === 'tool.call'
5424
+ && managerToolDisplay.markStarted(event.call.id)) {
5326
5425
  appendStatic([`${A.dim} ⚡ ${event.call.name}${A.reset}`]);
5426
+ }
5327
5427
  }
5328
5428
  const result = await stream.result;
5329
5429
  if (result.text)
@@ -5416,7 +5516,7 @@ export async function runHadamardTui(options = {}) {
5416
5516
  recalledFollowUp = null;
5417
5517
  editor.clear();
5418
5518
  menuSelected = 0;
5419
- if (running && !canRunInteractiveCommand(selectedName, true)) {
5519
+ if (running && !canRunInteractiveCommand(selectedCommand.slice(1), true)) {
5420
5520
  appendStatic(formatInfoLine(`/${selectedName} requires an idle session; it will be available after this run`));
5421
5521
  renderDynamic();
5422
5522
  return;
@@ -5445,7 +5545,7 @@ export async function runHadamardTui(options = {}) {
5445
5545
  restoreAbandonedFollowUp(session, recalledFollowUp);
5446
5546
  recalledFollowUp = null;
5447
5547
  const command = text.slice(1).trim().split(/\s/u, 1)[0]?.toLowerCase() ?? '';
5448
- if (!canRunInteractiveCommand(command, true)) {
5548
+ if (!canRunInteractiveCommand(text.slice(1), true)) {
5449
5549
  appendStatic(formatInfoLine(`/${command} requires an idle session; it will be available after this run`));
5450
5550
  renderDynamic();
5451
5551
  return;