praxis-agent 0.20.14 → 0.20.16

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.
@@ -5626,7 +5626,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
5626
5626
  }
5627
5627
  editComposer();
5628
5628
  });
5629
- return (_jsx(TuiThemeProvider, { settings: themeSettings, children: _jsx(Box, { flexDirection: "column", children: selectingSession ? (_jsx(SessionPicker, { sessions: filteredPickerChoices, selectedIndex: selectedIndex, screenReader: axScreenReader, query: sessionSearch })) : (_jsxs(_Fragment, { children: [!axScreenReader && history.length === 0 && !sessionId ? (_jsx(WelcomePanel, { display: runtimeDisplay, width: width })) : null, sessionId ? (_jsxs(Text, { dimColor: true, children: ["Session ", sessionId.slice(0, 8)] })) : null, _jsx(Transcript, { items: history, activeText: activeText, activeThinking: activeThinking, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded || runtimeSettings.verbose, screenReader: axScreenReader }), externalEditorRequest !== null ||
5629
+ return (_jsx(TuiThemeProvider, { settings: themeSettings, children: _jsx(Box, { flexDirection: "column", children: selectingSession ? (_jsx(SessionPicker, { sessions: filteredPickerChoices, selectedIndex: selectedIndex, screenReader: axScreenReader, query: sessionSearch })) : (_jsxs(_Fragment, { children: [!axScreenReader && history.length === 0 && !sessionId ? (_jsx(WelcomePanel, { display: runtimeDisplay, width: width, showTips: runtimeSettings.tips })) : null, sessionId ? (_jsxs(Text, { dimColor: true, children: ["Session ", sessionId.slice(0, 8)] })) : null, _jsx(Transcript, { items: history, activeText: activeText, activeThinking: activeThinking, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded || runtimeSettings.verbose, screenReader: axScreenReader }), externalEditorRequest !== null ||
5630
5630
  keybindingsEditing ||
5631
5631
  memoryEditorRequest !== null ? (_jsx(ExternalEditorWait, { screenReader: axScreenReader })) : permission ? (permission.kind === 'tool' && toolPermissionModel ? (_jsx(ToolPermissionDialog, { model: toolPermissionModel, selection: permissionSelection, feedbackMode: permissionFeedbackMode, feedback: input, ruleEditor: permissionRuleEditor, screenReader: axScreenReader })) : (_jsxs(DialogFrame, { title: `Retry interrupted ${permission.call.name}?`, screenReader: axScreenReader, children: [_jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: _jsx(Text, { bold: true, children: describeTool(permission.call, sensitiveValues) }) }), _jsx(Text, { children: "Do you want to proceed?" }), _jsxs(Text, { inverse: !axScreenReader && permissionSelection === 0, children: [selectionPrefix(permissionSelection === 0, axScreenReader), "1. Yes"] }), _jsxs(Text, { inverse: !axScreenReader && permissionSelection === 1, children: [selectionPrefix(permissionSelection === 1, axScreenReader), "2. No"] }), permissionFeedbackMode ? (_jsxs(Text, { children: ["\u203A", ' ', input ||
5632
5632
  (permissionSelection === 0
@@ -71,10 +71,11 @@ export interface TuiBtwEntry {
71
71
  error?: string;
72
72
  }
73
73
  export declare function useTerminalWidth(override?: number): number;
74
- export declare function WelcomePanel({ display, width, }: {
74
+ export declare function WelcomePanel({ display, width, showTips, }: {
75
75
  display: TuiDisplayMetadata;
76
76
  width: number;
77
- }): import("react").JSX.Element;
77
+ showTips: boolean;
78
+ }): import("react").JSX.Element | null;
78
79
  export declare function MarkdownText({ text }: {
79
80
  text: string;
80
81
  }): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
@@ -49,17 +49,23 @@ function selectionPrefix(selected, screenReader) {
49
49
  return screenReader ? 'Selected: ' : ' ❯ ';
50
50
  return screenReader ? '' : ' ';
51
51
  }
52
- export function WelcomePanel({ display, width, }) {
52
+ export function WelcomePanel({ display, width, showTips, }) {
53
53
  const palette = useTuiPalette();
54
+ if (!showTips)
55
+ return null;
54
56
  const panelWidth = Math.min(100, Math.max(32, width));
55
57
  const wide = panelWidth >= 68;
56
58
  const brand = 'Praxis';
57
- // Claude 2.1.208 embeds the title in the top border row:
58
- // ╭───Claude Code v2.1.208 ───...───╮
59
- // Title block = "╭" + "───" + "Praxis Code vX.Y.Z" + " " = 4 + title.length + 1 chars.
60
- const title = `${brand} Code v${display.version}`;
61
- const fill = Math.max(1, panelWidth - title.length - 6);
62
- return (_jsxs(Box, { flexDirection: "column", width: panelWidth, children: [_jsxs(Text, { color: palette.muted, children: ['╭───', _jsx(Text, { color: palette.brand, bold: true, children: brand }), ` Code v${display.version} `, _jsx(Text, { dimColor: true, children: '─'.repeat(fill) }), '╮'] }), _jsx(Box, { borderStyle: "round", borderColor: palette.muted, borderTop: false, flexDirection: "column", width: panelWidth, paddingX: 1, children: _jsxs(Box, { flexDirection: wide ? 'row' : 'column', children: [_jsxs(Box, { alignItems: wide ? 'center' : undefined, flexDirection: "column", width: wide ? '50%' : '100%', children: [_jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Welcome back!" }), _jsx(Text, { children: " " }), _jsx(Text, { color: palette.brand, bold: true, children: "\u2590\u259B\u2588\u2588\u2588\u259C\u258C" }), _jsx(Text, { color: palette.brand, children: "\u259D\u259C\u2588\u2588\u2588\u2588\u2588\u259B\u2598" }), _jsx(Text, { color: palette.brand, children: " \u2598\u2598 \u259D\u259D" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [display.model ?? 'provider default', display.effort ? (_jsxs(Text, { dimColor: true, children: [" \u00B7 ", display.effort, " effort"] })) : null] }), _jsx(Text, { dimColor: true, children: compactPath(display.cwd) })] }), _jsxs(Box, { flexDirection: "column", width: wide ? '50%' : '100%', marginTop: wide ? 0 : 1, children: [_jsx(Text, { bold: true, children: "Tips for getting started" }), _jsx(Text, { children: "Run /init to create a CLAUDE.md file with instructions for Claude" }), _jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx(Text, { bold: true, children: "What's new" }), _jsx(Text, { children: 'Subagent forking is now on by default: a `subagent_type: "fork"` subagent inherits the full conversation and prompt cache, and non-teammate agent spawns in interactive sessions now run in the background by default' }), _jsx(Text, { children: 'Type `@` in the prompt to mention another Claude session by name; Claude then uses `SendMessage` to reach that session directly' }), _jsx(Text, { children: '`SendMessage` now delivers to a bare name that exactly matches one live session, instead of asking to confirm with a ref first' }), _jsx(Text, { dimColor: true, children: "/release-notes for more" })] })] }) })] }));
59
+ const model = display.model ?? 'provider default';
60
+ const effort = display.effort ? ` · ${display.effort} effort` : '';
61
+ const cwd = compactPath(display.cwd);
62
+ // Title lives in the top border row:
63
+ // ╭───Praxis Code vX.Y.Z ───...───╮
64
+ // Fixed prefix/suffix = "╭───"(4) + "Praxis"(6) + " Code vX.Y.Z "(8 + version)
65
+ // + "╮"(1), so the fill keeps the row exactly at panelWidth.
66
+ const fill = Math.max(1, panelWidth - display.version.length - 19);
67
+ const identity = `Welcome to ${brand} · ${model}${effort} · ${cwd}`;
68
+ return (_jsxs(Box, { flexDirection: "column", width: panelWidth, children: [_jsxs(Text, { color: palette.muted, children: ['╭───', _jsx(Text, { color: palette.brand, bold: true, children: brand }), ` Code v${display.version} `, _jsx(Text, { dimColor: true, children: '─'.repeat(fill) }), '╮'] }), wide ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: identity }), _jsx(Text, { children: "/init to create CLAUDE.md \u00B7 /config to open settings" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["Welcome to ", brand] }), _jsxs(Text, { children: [model, effort] }), _jsx(Text, { children: cwd }), _jsx(Text, { children: "/init to create CLAUDE.md" }), _jsx(Text, { children: "/config to open settings" })] }))] }));
63
69
  }
64
70
  const INLINE_SEGMENT_CACHE_MAX = 4096;
65
71
  const inlineSegmentCache = new Map();
@@ -7,6 +7,7 @@ import { type ModelDocument, type ModelImage, type ModelProvider, type ModelTool
7
7
  import type { InteractiveResumeOptions, InteractiveServiceFactory } from './cli/interactive.js';
8
8
  import type { TuiSlashCommand } from './cli/tui/slash-commands.js';
9
9
  import { type TuiHookConfiguration } from './cli/tui/hook-settings.js';
10
+ import { type PraxisRuntimeSettings } from './cli/tui/runtime-settings.js';
10
11
  import { type ClaudePermissionMode } from './permissions/claude-permission-resolver.js';
11
12
  import { type ClaudeMcpServerStatus, type ClaudeMcpToolInspection } from './mcp/claude-mcp-tools.js';
12
13
  import { authenticateMcpServer } from './mcp/claude-mcp-oauth.js';
@@ -164,6 +165,14 @@ export interface CliDependencies extends InteractiveServiceFactory {
164
165
  force?: boolean;
165
166
  }) => Promise<SelfUpdateResult>;
166
167
  }
168
+ /**
169
+ * Shared runtime model precedence used by every consumer (provider
170
+ * construction, status/doctor output, and the interactive display):
171
+ * explicit CLI selection > non-empty PRAXIS_MODEL > Praxis settings model,
172
+ * with the configured default (settings.model === 'default') falling through
173
+ * to the existing default behavior (undefined).
174
+ */
175
+ export declare function resolveRuntimeModel(explicitModel: string | undefined, environment: NodeJS.ProcessEnv, settings: PraxisRuntimeSettings | undefined): string | undefined;
167
176
  export declare function createDefaultDependencies(entrypoint?: string): CliDependencies;
168
177
  export declare function createBackgroundWorkerRuntime(workerSink: RuntimeEventSink, dispatch: {
169
178
  argv: string[];
@@ -695,6 +695,20 @@ const consoleIO = {
695
695
  isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
696
696
  readStdinLines: () => process.stdin,
697
697
  };
698
+ /**
699
+ * Shared runtime model precedence used by every consumer (provider
700
+ * construction, status/doctor output, and the interactive display):
701
+ * explicit CLI selection > non-empty PRAXIS_MODEL > Praxis settings model,
702
+ * with the configured default (settings.model === 'default') falling through
703
+ * to the existing default behavior (undefined).
704
+ */
705
+ export function resolveRuntimeModel(explicitModel, environment, settings) {
706
+ const envModel = environment.PRAXIS_MODEL;
707
+ const settingsModel = settings && settings.model !== 'default' ? settings.model : undefined;
708
+ return (explicitModel ??
709
+ (envModel !== undefined && envModel.trim() !== '' ? envModel : undefined) ??
710
+ settingsModel);
711
+ }
698
712
  const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = false, approveRecovery, approveTool, agent, model: interactiveModel, effort: interactiveEffort, permissionMode: interactivePermissionMode, isSessionActionApproved, controls = DEFAULT_CLI_CONTROLS, interactive = false, sessionKind, signal, exposeToolRegistry = false, onElicitation, askUser, approvePlan, emitToolUseSummaries = false, cwd: requestedCwd, sandboxOriginalCwd, configRoot: requestedConfigRoot, environment, providerEnvironment: requestedProviderEnvironment, }) => {
699
713
  const runtimeEnvironment = requestedProviderEnvironment ?? process.env;
700
714
  const sandboxEnvironment = { ...runtimeEnvironment, ...environment };
@@ -740,7 +754,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
740
754
  let providerForMainModel;
741
755
  const context = parseContextEnvironment(runtimeEnvironment);
742
756
  const apiKey = runtimeEnvironment.PRAXIS_API_KEY;
743
- const model = cli.model ?? runtimeEnvironment.PRAXIS_MODEL;
757
+ const model = resolveRuntimeModel(interactiveModel ?? controls.model, runtimeEnvironment, runtimeSettings);
744
758
  const providerEnvironment = apiKey && model
745
759
  ? parseProviderEnvironment(runtimeEnvironment)
746
760
  : cli.fileResources.length > 0
@@ -1300,7 +1314,9 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1300
1314
  ];
1301
1315
  const runtimeInfo = {
1302
1316
  cwd: workspace.cwd(),
1303
- model: provider?.model ?? runtimeEnvironment.PRAXIS_MODEL ?? 'unknown',
1317
+ model: provider?.model ??
1318
+ resolveRuntimeModel(interactiveModel ?? controls.model, runtimeEnvironment, runtimeSettings) ??
1319
+ 'unknown',
1304
1320
  ...(provider?.capabilities.contextWindowTokens === undefined
1305
1321
  ? {}
1306
1322
  : { contextWindowTokens: provider.capabilities.contextWindowTokens }),
@@ -1457,7 +1473,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1457
1473
  cwd: workspace.cwd(),
1458
1474
  model: service.model() ??
1459
1475
  provider?.model ??
1460
- runtimeEnvironment.PRAXIS_MODEL ??
1476
+ resolveRuntimeModel(interactiveModel ?? controls.model, runtimeEnvironment, runtimeSettings) ??
1461
1477
  'unknown',
1462
1478
  };
1463
1479
  delete staticInfo.contextWindowTokens;
@@ -1498,7 +1514,12 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1498
1514
  };
1499
1515
  const createDefaultAutoModeCritic = async ({ model }) => {
1500
1516
  const apiKey = process.env.PRAXIS_API_KEY;
1501
- const selectedModel = model ?? process.env.PRAXIS_MODEL;
1517
+ const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR ?? resolve(homedir(), '.claude'));
1518
+ const runtimeSettings = await loadRuntimeSettings({
1519
+ configRoot,
1520
+ statePath: join(configRoot, '.claude.json'),
1521
+ });
1522
+ const selectedModel = resolveRuntimeModel(model, process.env, runtimeSettings);
1502
1523
  if (!apiKey || !selectedModel) {
1503
1524
  throw new Error('PRAXIS_API_KEY and a model (--model or PRAXIS_MODEL) are required');
1504
1525
  }
@@ -1657,6 +1678,18 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
1657
1678
  const { runInteractive } = await import('./cli/interactive.js');
1658
1679
  const interactiveControls = controls ?? DEFAULT_CLI_CONTROLS;
1659
1680
  const initialAdditionalDirectories = interactiveControls.addDirectories.map((directory) => realpathSync(resolve(process.cwd(), directory)));
1681
+ const configuredRoot = process.env.CLAUDE_CONFIG_DIR || undefined;
1682
+ const interactiveConfigRoot = resolve(configuredRoot ?? resolve(homedir(), '.claude'));
1683
+ const interactiveStatePath = configuredRoot
1684
+ ? join(interactiveConfigRoot, '.claude.json')
1685
+ : resolve(homedir(), '.claude.json');
1686
+ const interactiveRuntimeSettings = interactiveControls.safeMode || interactiveControls.bare
1687
+ ? undefined
1688
+ : await loadRuntimeSettings({
1689
+ configRoot: interactiveConfigRoot,
1690
+ statePath: interactiveStatePath,
1691
+ });
1692
+ const effectiveModel = resolveRuntimeModel(interactiveControls.model, process.env, interactiveRuntimeSettings);
1660
1693
  return runInteractive({
1661
1694
  factory: {
1662
1695
  createService: ({ additionalDirectories, cwd, ...options }) => createDefaultService({
@@ -1670,8 +1703,7 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
1670
1703
  },
1671
1704
  interactive: true,
1672
1705
  }),
1673
- scheduledPrompts: Boolean(process.env.PRAXIS_API_KEY &&
1674
- (interactiveControls.model ?? process.env.PRAXIS_MODEL)),
1706
+ scheduledPrompts: Boolean(process.env.PRAXIS_API_KEY && effectiveModel),
1675
1707
  },
1676
1708
  ...(signal ? { signal } : {}),
1677
1709
  ...(initialPrompt === undefined ? {} : { initialPrompt }),
@@ -1686,11 +1718,7 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
1686
1718
  display: {
1687
1719
  version: VERSION,
1688
1720
  cwd: process.cwd(),
1689
- ...(controls?.model
1690
- ? { model: controls.model }
1691
- : process.env.PRAXIS_MODEL
1692
- ? { model: process.env.PRAXIS_MODEL }
1693
- : {}),
1721
+ ...(effectiveModel === undefined ? {} : { model: effectiveModel }),
1694
1722
  effort: controls?.effort ?? 'high',
1695
1723
  permissionMode: controls?.dangerouslySkipPermissions
1696
1724
  ? 'bypassPermissions'
@@ -2090,6 +2118,7 @@ async function executeDoctorCommand(args, invocation, io) {
2090
2118
  configRoot,
2091
2119
  statePath: claudeStatePath,
2092
2120
  });
2121
+ const effectiveModel = resolveRuntimeModel(invocation.model, process.env, runtimeSettings);
2093
2122
  const report = await runDoctor({
2094
2123
  version: VERSION,
2095
2124
  executablePath: fileURLToPath(import.meta.url),
@@ -2098,7 +2127,9 @@ async function executeDoctorCommand(args, invocation, io) {
2098
2127
  configRoot,
2099
2128
  claudeStatePath,
2100
2129
  cwd: process.cwd(),
2101
- environment: process.env,
2130
+ environment: effectiveModel === undefined
2131
+ ? process.env
2132
+ : { ...process.env, PRAXIS_MODEL: effectiveModel },
2102
2133
  autoUpdateChannel: runtimeSettings.autoUpdatesChannel,
2103
2134
  ...(process.argv[1] === undefined
2104
2135
  ? {}
@@ -4053,6 +4084,10 @@ async function execute(argv, io, dependencies, signal) {
4053
4084
  const startedAt = Date.now();
4054
4085
  const sessionId = invocation.sessionId ?? randomUUID();
4055
4086
  const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'));
4087
+ const runtimeSettings = await loadRuntimeSettings({
4088
+ configRoot,
4089
+ statePath: join(configRoot, '.claude.json'),
4090
+ });
4056
4091
  const text = dependencies.loadReleaseNotes
4057
4092
  ? await dependencies.loadReleaseNotes(configRoot)
4058
4093
  : await loadClaudeReleaseNotes({ configRoot });
@@ -4063,7 +4098,8 @@ async function execute(argv, io, dependencies, signal) {
4063
4098
  };
4064
4099
  const info = {
4065
4100
  cwd: process.cwd(),
4066
- model: invocation.model ?? process.env.PRAXIS_MODEL ?? 'unknown',
4101
+ model: resolveRuntimeModel(invocation.model, process.env, runtimeSettings) ??
4102
+ 'unknown',
4067
4103
  tools: [],
4068
4104
  mcpServers: [],
4069
4105
  permissionMode: invocation.permissionMode,
@@ -4218,9 +4254,15 @@ async function execute(argv, io, dependencies, signal) {
4218
4254
  io.stdout(transcript);
4219
4255
  return 0;
4220
4256
  }
4257
+ const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'));
4258
+ const runtimeSettings = await loadRuntimeSettings({
4259
+ configRoot,
4260
+ statePath: join(configRoot, '.claude.json'),
4261
+ });
4221
4262
  const runtimeInfo = service.runtimeInfo?.() ?? {
4222
4263
  cwd: process.cwd(),
4223
- model: process.env.PRAXIS_MODEL ?? 'unknown',
4264
+ model: resolveRuntimeModel(invocation.model, process.env, runtimeSettings) ??
4265
+ 'unknown',
4224
4266
  tools: [],
4225
4267
  mcpServers: [],
4226
4268
  permissionMode: 'default',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.20.14",
3
+ "version": "0.20.16",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",