praxis-agent 0.20.4 → 0.20.7
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.
- package/dist/application/background-agent-manager.js +3 -14
- package/dist/cli/tui/claude-style.d.ts +2 -1
- package/dist/cli/tui/claude-style.js +226 -44
- package/dist/cli/tui/git-diff.js +67 -0
- package/dist/cli-runtime.d.ts +172 -0
- package/dist/cli-runtime.js +4434 -0
- package/dist/cli.d.ts +5 -164
- package/dist/cli.js +17 -4418
- package/dist/compatibility/claude/file-resources.js +6 -2
- package/dist/permissions/bash-path-safety.d.ts +1 -0
- package/dist/permissions/bash-path-safety.js +14 -0
- package/dist/permissions/bypass-immune-paths.d.ts +11 -0
- package/dist/permissions/bypass-immune-paths.js +86 -0
- package/dist/tools/local-tools.d.ts +7 -0
- package/dist/tools/local-tools.js +42 -1
- package/package.json +1 -1
|
@@ -493,28 +493,17 @@ export class BackgroundAgentManager {
|
|
|
493
493
|
}
|
|
494
494
|
formatNotification(task, notification) {
|
|
495
495
|
const result = notification.result?.text ?? notification.error ?? '';
|
|
496
|
+
const status = notification.status === 'stopped' ? 'killed' : notification.status;
|
|
496
497
|
const usage = notification.result
|
|
497
|
-
? `<usage><
|
|
498
|
+
? `<usage><total_tokens>${notification.result.usage.inputTokens + notification.result.usage.outputTokens}</total_tokens><tool_uses>${notification.result.toolUseCount}</tool_uses><duration_ms>${notification.result.durationMs}</duration_ms></usage>`
|
|
498
499
|
: '';
|
|
499
500
|
return [
|
|
500
501
|
'<task-notification>',
|
|
501
502
|
`<task-id>${task.spec.agentId}</task-id>`,
|
|
502
503
|
`<tool-use-id>${escapeXml(notification.toolUseId)}</tool-use-id>`,
|
|
503
504
|
`<output-file>${escapeXml(task.spec.outputFile)}</output-file>`,
|
|
504
|
-
`<status>${
|
|
505
|
+
`<status>${status}</status>`,
|
|
505
506
|
`<summary>Agent "${escapeXml(task.spec.description)}" finished</summary>`,
|
|
506
|
-
'<note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note>',
|
|
507
|
-
...(notification.result?.isolationPath
|
|
508
|
-
? [
|
|
509
|
-
`<worktree-path>${escapeXml(notification.result.isolationPath)}</worktree-path>`,
|
|
510
|
-
`<worktree-retained>${String(notification.result.isolationRetained)}</worktree-retained>`,
|
|
511
|
-
...(notification.result.isolationWarning
|
|
512
|
-
? [
|
|
513
|
-
`<worktree-warning>${escapeXml(notification.result.isolationWarning)}</worktree-warning>`,
|
|
514
|
-
]
|
|
515
|
-
: []),
|
|
516
|
-
]
|
|
517
|
-
: []),
|
|
518
507
|
`<result>${escapeXml(result)}</result>`,
|
|
519
508
|
usage,
|
|
520
509
|
'</task-notification>',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
1
2
|
import type { ModelToolCall, ModelUsage } from '../../core/runtime.js';
|
|
2
3
|
import type { AgentColorName } from '../../compatibility/claude/agent-color.js';
|
|
3
4
|
import type { TuiFileEntry, TuiMentionEntry } from './file-picker.js';
|
|
@@ -76,7 +77,7 @@ export declare function WelcomePanel({ display, width, }: {
|
|
|
76
77
|
}): import("react").JSX.Element;
|
|
77
78
|
export declare function MarkdownText({ text }: {
|
|
78
79
|
text: string;
|
|
79
|
-
}): import("react").
|
|
80
|
+
}): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
80
81
|
export declare function Transcript({ items, activeText, activeThinking, thinkingExpanded, detailedTranscript, screenReader, }: {
|
|
81
82
|
items: readonly TranscriptItem[];
|
|
82
83
|
activeText: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { cloneElement, useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text, useStdout } from 'ink';
|
|
4
4
|
import { composerEditorSegments } from './composer-editor.js';
|
|
5
5
|
import { visiblePatchLines } from './git-diff.js';
|
|
@@ -61,35 +61,114 @@ export function WelcomePanel({ display, width, }) {
|
|
|
61
61
|
const fill = Math.max(1, panelWidth - title.length - 6);
|
|
62
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" })] })] }) })] }));
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
64
|
+
const INLINE_SEGMENT_CACHE_MAX = 4096;
|
|
65
|
+
const inlineSegmentCache = new Map();
|
|
66
|
+
function cachedInlineSegments(text) {
|
|
67
|
+
const cached = inlineSegmentCache.get(text);
|
|
68
|
+
if (cached)
|
|
69
|
+
return cached;
|
|
70
|
+
const segments = text
|
|
71
|
+
.split(/(`[^`]+`|\*\*[^*]+\*\*|\[[^\]]+\]\([^)]+\))/u)
|
|
72
|
+
.map((token) => {
|
|
73
|
+
if (token.startsWith('`') && token.endsWith('`'))
|
|
74
|
+
return { kind: 'code', text: token.slice(1, -1) };
|
|
75
|
+
if (token.startsWith('**') && token.endsWith('**'))
|
|
76
|
+
return { kind: 'bold', text: token.slice(2, -2) };
|
|
77
|
+
const link = /^\[([^\]]+)\]\(([^)]+)\)$/u.exec(token);
|
|
78
|
+
if (link) {
|
|
79
|
+
const linkText = link[1];
|
|
80
|
+
if (linkText !== undefined)
|
|
81
|
+
return { kind: 'link', text: linkText };
|
|
82
|
+
}
|
|
83
|
+
return { kind: 'plain', text: token };
|
|
84
|
+
});
|
|
85
|
+
inlineSegmentCache.set(text, segments);
|
|
86
|
+
if (inlineSegmentCache.size > INLINE_SEGMENT_CACHE_MAX) {
|
|
87
|
+
const oldest = inlineSegmentCache.keys().next().value;
|
|
88
|
+
if (oldest !== undefined)
|
|
89
|
+
inlineSegmentCache.delete(oldest);
|
|
90
|
+
}
|
|
91
|
+
return segments;
|
|
92
|
+
}
|
|
93
|
+
function inlineTextElement(text, palette) {
|
|
94
|
+
return (_jsx(Text, { children: cachedInlineSegments(text).map((segment, index) => {
|
|
95
|
+
if (segment.kind === 'code')
|
|
96
|
+
return (_jsx(Text, { color: palette.info, children: segment.text }, index));
|
|
97
|
+
if (segment.kind === 'bold')
|
|
98
|
+
return (_jsx(Text, { bold: true, children: segment.text }, index));
|
|
99
|
+
if (segment.kind === 'link')
|
|
100
|
+
return (_jsx(Text, { color: palette.link, underline: true, children: segment.text }, index));
|
|
101
|
+
return segment.text;
|
|
76
102
|
}) }));
|
|
77
103
|
}
|
|
78
|
-
|
|
79
|
-
|
|
104
|
+
const MARKDOWN_LINE_CACHE_MAX = 4096;
|
|
105
|
+
const markdownLineCache = new Map();
|
|
106
|
+
function cachedMarkdownLineShape(line) {
|
|
107
|
+
const cached = markdownLineCache.get(line);
|
|
108
|
+
if (cached)
|
|
109
|
+
return cached;
|
|
110
|
+
let shape;
|
|
80
111
|
if (line.startsWith('### '))
|
|
81
|
-
|
|
82
|
-
if (line.startsWith('## '))
|
|
83
|
-
|
|
84
|
-
if (line.startsWith('# '))
|
|
85
|
-
|
|
86
|
-
if (/^[-*] /u.test(line))
|
|
87
|
-
|
|
88
|
-
if (/^\d+\. /u.test(line))
|
|
89
|
-
|
|
90
|
-
if (line.startsWith('> '))
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
shape = { role: 'h3', content: line.slice(4) };
|
|
113
|
+
else if (line.startsWith('## '))
|
|
114
|
+
shape = { role: 'h2', content: line.slice(3) };
|
|
115
|
+
else if (line.startsWith('# '))
|
|
116
|
+
shape = { role: 'h1', content: line.slice(2) };
|
|
117
|
+
else if (/^[-*] /u.test(line))
|
|
118
|
+
shape = { role: 'bullet', content: line.slice(2) };
|
|
119
|
+
else if (/^\d+\. /u.test(line))
|
|
120
|
+
shape = { role: 'ordered', content: line };
|
|
121
|
+
else if (line.startsWith('> '))
|
|
122
|
+
shape = { role: 'quote', content: line.slice(2) };
|
|
123
|
+
else if (line === '')
|
|
124
|
+
shape = { role: 'empty' };
|
|
125
|
+
else
|
|
126
|
+
shape = { role: 'plain', content: line };
|
|
127
|
+
markdownLineCache.set(line, shape);
|
|
128
|
+
if (markdownLineCache.size > MARKDOWN_LINE_CACHE_MAX) {
|
|
129
|
+
const oldest = markdownLineCache.keys().next().value;
|
|
130
|
+
if (oldest !== undefined)
|
|
131
|
+
markdownLineCache.delete(oldest);
|
|
132
|
+
}
|
|
133
|
+
return shape;
|
|
134
|
+
}
|
|
135
|
+
const ELEMENT_CACHE_KEY_SEPARATOR = String.fromCharCode(0);
|
|
136
|
+
function markdownLineStyleSignature(palette) {
|
|
137
|
+
return [palette.accent, palette.brand, palette.info, palette.link].join(ELEMENT_CACHE_KEY_SEPARATOR);
|
|
138
|
+
}
|
|
139
|
+
const MARKDOWN_LINE_ELEMENT_CACHE_MAX = 4096;
|
|
140
|
+
const markdownLineElementCache = new Map();
|
|
141
|
+
function buildMarkdownLine(line, palette) {
|
|
142
|
+
const shape = cachedMarkdownLineShape(line);
|
|
143
|
+
if (shape.role === 'h3')
|
|
144
|
+
return _jsx(Text, { bold: true, children: inlineTextElement(shape.content, palette) });
|
|
145
|
+
if (shape.role === 'h2')
|
|
146
|
+
return (_jsx(Text, { bold: true, color: palette.accent, children: inlineTextElement(shape.content, palette) }));
|
|
147
|
+
if (shape.role === 'h1')
|
|
148
|
+
return (_jsx(Text, { bold: true, color: palette.brand, children: inlineTextElement(shape.content, palette) }));
|
|
149
|
+
if (shape.role === 'bullet')
|
|
150
|
+
return (_jsxs(Text, { children: [' • ', inlineTextElement(shape.content, palette)] }));
|
|
151
|
+
if (shape.role === 'ordered')
|
|
152
|
+
return (_jsxs(Text, { children: [' ', inlineTextElement(shape.content, palette)] }));
|
|
153
|
+
if (shape.role === 'quote')
|
|
154
|
+
return (_jsxs(Text, { dimColor: true, children: ['│ ', inlineTextElement(shape.content, palette)] }));
|
|
155
|
+
if (shape.role === 'plain')
|
|
156
|
+
return inlineTextElement(shape.content, palette);
|
|
157
|
+
return _jsx(Text, { children: " " });
|
|
158
|
+
}
|
|
159
|
+
function cachedMarkdownLineElement(line, palette) {
|
|
160
|
+
const key = [line, markdownLineStyleSignature(palette)].join(ELEMENT_CACHE_KEY_SEPARATOR);
|
|
161
|
+
const cached = markdownLineElementCache.get(key);
|
|
162
|
+
if (cached)
|
|
163
|
+
return cached;
|
|
164
|
+
const element = buildMarkdownLine(line, palette);
|
|
165
|
+
markdownLineElementCache.set(key, element);
|
|
166
|
+
if (markdownLineElementCache.size > MARKDOWN_LINE_ELEMENT_CACHE_MAX) {
|
|
167
|
+
const oldest = markdownLineElementCache.keys().next().value;
|
|
168
|
+
if (oldest !== undefined)
|
|
169
|
+
markdownLineElementCache.delete(oldest);
|
|
170
|
+
}
|
|
171
|
+
return element;
|
|
93
172
|
}
|
|
94
173
|
const CODE_KEYWORDS = new Set([
|
|
95
174
|
'async',
|
|
@@ -112,25 +191,73 @@ const CODE_KEYWORDS = new Set([
|
|
|
112
191
|
'var',
|
|
113
192
|
]);
|
|
114
193
|
const CODE_TOKEN_PATTERN = /("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\b(?:async|await|class|const|else|export|false|from|function|if|import|let|new|null|return|true|undefined|var)\b|\b[A-Za-z_$][\w$]*(?=\s*\())/gu;
|
|
115
|
-
|
|
116
|
-
|
|
194
|
+
const SYNTAX_TOKEN_CACHE_MAX = 2048;
|
|
195
|
+
const syntaxTokenCache = new Map();
|
|
196
|
+
function cachedSyntaxTokens(text) {
|
|
197
|
+
const cached = syntaxTokenCache.get(text);
|
|
198
|
+
if (cached)
|
|
199
|
+
return cached;
|
|
200
|
+
const tokens = text.split(CODE_TOKEN_PATTERN).map((token) => ({
|
|
201
|
+
kind: token.startsWith('"') || token.startsWith("'")
|
|
202
|
+
? 'string'
|
|
203
|
+
: CODE_KEYWORDS.has(token)
|
|
204
|
+
? 'keyword'
|
|
205
|
+
: /^[A-Za-z_$][\w$]*$/u.test(token)
|
|
206
|
+
? 'identifier'
|
|
207
|
+
: 'text',
|
|
208
|
+
token,
|
|
209
|
+
}));
|
|
210
|
+
syntaxTokenCache.set(text, tokens);
|
|
211
|
+
if (syntaxTokenCache.size > SYNTAX_TOKEN_CACHE_MAX) {
|
|
212
|
+
const oldest = syntaxTokenCache.keys().next().value;
|
|
213
|
+
if (oldest !== undefined)
|
|
214
|
+
syntaxTokenCache.delete(oldest);
|
|
215
|
+
}
|
|
216
|
+
return tokens;
|
|
217
|
+
}
|
|
218
|
+
function syntaxStyleSignature(palette) {
|
|
219
|
+
const syntax = palette.syntax;
|
|
220
|
+
return [
|
|
221
|
+
palette.syntaxHighlightingDisabled ? '1' : '0',
|
|
222
|
+
syntax.text,
|
|
223
|
+
syntax.keyword,
|
|
224
|
+
syntax.identifier,
|
|
225
|
+
syntax.string,
|
|
226
|
+
syntax.removedBackground ?? '',
|
|
227
|
+
syntax.addedBackground ?? '',
|
|
228
|
+
].join(ELEMENT_CACHE_KEY_SEPARATOR);
|
|
229
|
+
}
|
|
230
|
+
const SYNTAX_LINE_ELEMENT_CACHE_MAX = 4096;
|
|
231
|
+
const syntaxLineElementCache = new Map();
|
|
232
|
+
function buildSyntaxCodeLine(text, prefix, change, palette) {
|
|
117
233
|
if (palette.syntaxHighlightingDisabled) {
|
|
118
234
|
return (_jsxs(Text, { children: [prefix, text || ' '] }));
|
|
119
235
|
}
|
|
120
236
|
const lineStyle = tuiSyntaxStyle(palette, 'text', change);
|
|
121
|
-
return (_jsxs(Text, { children: [prefix, _jsx(Text, { ...lineStyle, children: text
|
|
122
|
-
const kind = token.startsWith('"') || token.startsWith("'")
|
|
123
|
-
? 'string'
|
|
124
|
-
: CODE_KEYWORDS.has(token)
|
|
125
|
-
? 'keyword'
|
|
126
|
-
: /^[A-Za-z_$][\w$]*$/u.test(token)
|
|
127
|
-
? 'identifier'
|
|
128
|
-
: 'text';
|
|
237
|
+
return (_jsxs(Text, { children: [prefix, _jsx(Text, { ...lineStyle, children: cachedSyntaxTokens(text).map(({ kind, token }, index) => {
|
|
129
238
|
if (kind === 'text')
|
|
130
239
|
return token;
|
|
131
240
|
return (_jsx(Text, { ...tuiSyntaxStyle(palette, kind), children: token }, index));
|
|
132
241
|
}) })] }));
|
|
133
242
|
}
|
|
243
|
+
function cachedSyntaxCodeLine(text, prefix, change, palette) {
|
|
244
|
+
const key = [text, prefix, change ?? '', syntaxStyleSignature(palette)].join(ELEMENT_CACHE_KEY_SEPARATOR);
|
|
245
|
+
const cached = syntaxLineElementCache.get(key);
|
|
246
|
+
if (cached)
|
|
247
|
+
return cached;
|
|
248
|
+
const element = buildSyntaxCodeLine(text, prefix, change, palette);
|
|
249
|
+
syntaxLineElementCache.set(key, element);
|
|
250
|
+
if (syntaxLineElementCache.size > SYNTAX_LINE_ELEMENT_CACHE_MAX) {
|
|
251
|
+
const oldest = syntaxLineElementCache.keys().next().value;
|
|
252
|
+
if (oldest !== undefined)
|
|
253
|
+
syntaxLineElementCache.delete(oldest);
|
|
254
|
+
}
|
|
255
|
+
return element;
|
|
256
|
+
}
|
|
257
|
+
function SyntaxCodeLine({ text, prefix = '', change, }) {
|
|
258
|
+
const palette = useTuiPalette();
|
|
259
|
+
return cachedSyntaxCodeLine(text, prefix, change, palette);
|
|
260
|
+
}
|
|
134
261
|
function ToolResultText({ text, prefix = '', }) {
|
|
135
262
|
const palette = useTuiPalette();
|
|
136
263
|
return (_jsx(Box, { flexDirection: "column", children: text.split('\n').map((line, index) => line.startsWith('+') && !line.startsWith('+++') ? (_jsx(SyntaxCodeLine, { prefix: index === 0 ? prefix : prefix ? ' ' : '', text: line, change: "added" }, index)) : line.startsWith('-') && !line.startsWith('---') ? (_jsx(SyntaxCodeLine, { prefix: index === 0 ? prefix : prefix ? ' ' : '', text: line, change: "removed" }, index)) : (_jsxs(Text, { ...(line.startsWith('@@')
|
|
@@ -205,17 +332,72 @@ function ContextUsageBlock({ usedTokens, contextWindowTokens, model, skills, mem
|
|
|
205
332
|
const cells = Array.from({ length: 25 }, (_, index) => index < usedCells ? '⛁' : index >= 25 - bufferCells ? '⛝' : '⛶');
|
|
206
333
|
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2, children: [_jsx(Text, { bold: true, children: "Context Usage" }), _jsxs(Box, { children: [_jsx(Box, { flexDirection: "column", marginRight: 2, children: Array.from({ length: 5 }, (_, row) => (_jsx(Text, { children: cells.slice(row * 5, row * 5 + 5).join(' ') }, row))) }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: [model ?? 'provider default', " \u00B7 ", compactTokens(usedTokens), "/", compactTokens(totalTokens), " tokens (", percent(usedTokens, totalTokens), ")"] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, italic: true, children: "Estimated usage by category" }), _jsxs(Text, { children: ["\u26C1 Messages and other context: ", compactTokens(usedTokens), " tokens (", percent(usedTokens, totalTokens), ")"] }), _jsxs(Text, { children: ["\u26F6 Free space: ", compactTokens(Math.max(0, usable - usedTokens)), " (", percent(Math.max(0, usable - usedTokens), totalTokens), ")"] }), _jsxs(Text, { children: ["\u26DD Autocompact buffer: ", compactTokens(compactBuffer), " tokens (", percent(compactBuffer, totalTokens), ")"] })] })] }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Memory files \u00B7 /memory" }), memoryFiles.length === 0 ? (_jsx(Text, { dimColor: true, children: "\u2514 No memory files" })) : (memoryFiles.map((file, index) => (_jsxs(Text, { dimColor: true, children: [index === memoryFiles.length - 1 ? '└' : '├', " ", file.path, ":", ' ', file.tokens, " tokens"] }, file.path)))), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Skills \u00B7 /skills" }), _jsx(Text, { children: " " }), _jsx(Text, { children: "Loaded" }), skills.length === 0 ? (_jsx(Text, { dimColor: true, children: "\u2514 No skills loaded" })) : (skills.map((skill, index) => (_jsxs(Text, { dimColor: true, children: [index === skills.length - 1 ? '└' : '├', " ", skill.name, ": ~", skill.tokens, " tokens"] }, skill.name))))] }));
|
|
207
334
|
}
|
|
208
|
-
|
|
335
|
+
const MARKDOWN_TEXT_CACHE_MAX = 2048;
|
|
336
|
+
const markdownTextCache = new Map();
|
|
337
|
+
function cachedMarkdownText(text) {
|
|
338
|
+
const cached = markdownTextCache.get(text);
|
|
339
|
+
if (cached)
|
|
340
|
+
return cached;
|
|
209
341
|
const lines = text.split('\n');
|
|
342
|
+
const parsed = [];
|
|
210
343
|
let code = false;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
344
|
+
for (const line of lines) {
|
|
345
|
+
if (line.startsWith('```')) {
|
|
346
|
+
code = !code;
|
|
347
|
+
parsed.push(code
|
|
348
|
+
? { type: 'fence-open', label: line.slice(3) || 'code' }
|
|
349
|
+
: { type: 'fence-close' });
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
parsed.push(code ? { type: 'code', text: line } : { type: 'markdown', line });
|
|
353
|
+
}
|
|
354
|
+
markdownTextCache.set(text, parsed);
|
|
355
|
+
if (markdownTextCache.size > MARKDOWN_TEXT_CACHE_MAX) {
|
|
356
|
+
const oldest = markdownTextCache.keys().next().value;
|
|
357
|
+
if (oldest !== undefined)
|
|
358
|
+
markdownTextCache.delete(oldest);
|
|
359
|
+
}
|
|
360
|
+
return parsed;
|
|
361
|
+
}
|
|
362
|
+
function markdownTextStyleSignature(palette) {
|
|
363
|
+
return [
|
|
364
|
+
syntaxStyleSignature(palette),
|
|
365
|
+
markdownLineStyleSignature(palette),
|
|
366
|
+
].join(ELEMENT_CACHE_KEY_SEPARATOR);
|
|
367
|
+
}
|
|
368
|
+
const MARKDOWN_TEXT_ELEMENT_CACHE_MAX = 4096;
|
|
369
|
+
const markdownTextElementCache = new Map();
|
|
370
|
+
function buildMarkdownText(text, palette) {
|
|
371
|
+
return (_jsx(Box, { flexDirection: "column", children: cachedMarkdownText(text).map((line, index) => {
|
|
372
|
+
if (line.type === 'fence-open')
|
|
373
|
+
return (_jsx(Text, { dimColor: true, children: `╭─ ${line.label}` }, index));
|
|
374
|
+
if (line.type === 'fence-close')
|
|
375
|
+
return (_jsx(Text, { dimColor: true, children: '╰─' }, index));
|
|
376
|
+
if (line.type === 'code')
|
|
377
|
+
return cloneElement(cachedSyntaxCodeLine(line.text, '│ ', undefined, palette), { key: index });
|
|
378
|
+
return cloneElement(cachedMarkdownLineElement(line.line, palette), {
|
|
379
|
+
key: index,
|
|
380
|
+
});
|
|
217
381
|
}) }));
|
|
218
382
|
}
|
|
383
|
+
function cachedMarkdownTextElement(text, palette) {
|
|
384
|
+
const key = [text, markdownTextStyleSignature(palette)].join(ELEMENT_CACHE_KEY_SEPARATOR);
|
|
385
|
+
const cached = markdownTextElementCache.get(key);
|
|
386
|
+
if (cached)
|
|
387
|
+
return cached;
|
|
388
|
+
const element = buildMarkdownText(text, palette);
|
|
389
|
+
markdownTextElementCache.set(key, element);
|
|
390
|
+
if (markdownTextElementCache.size > MARKDOWN_TEXT_ELEMENT_CACHE_MAX) {
|
|
391
|
+
const oldest = markdownTextElementCache.keys().next().value;
|
|
392
|
+
if (oldest !== undefined)
|
|
393
|
+
markdownTextElementCache.delete(oldest);
|
|
394
|
+
}
|
|
395
|
+
return element;
|
|
396
|
+
}
|
|
397
|
+
export function MarkdownText({ text }) {
|
|
398
|
+
const palette = useTuiPalette();
|
|
399
|
+
return cachedMarkdownTextElement(text, palette);
|
|
400
|
+
}
|
|
219
401
|
export function Transcript({ items, activeText, activeThinking = '', thinkingExpanded = false, detailedTranscript = false, screenReader, }) {
|
|
220
402
|
const palette = useTuiPalette();
|
|
221
403
|
const detailed = thinkingExpanded || detailedTranscript;
|
package/dist/cli/tui/git-diff.js
CHANGED
|
@@ -28,7 +28,74 @@ export function visiblePatchLines(patch) {
|
|
|
28
28
|
!isNewFileHeader(line) &&
|
|
29
29
|
!line.startsWith('@@ '));
|
|
30
30
|
}
|
|
31
|
+
async function hasHead(cwd) {
|
|
32
|
+
try {
|
|
33
|
+
await execFileAsync('git', ['-C', cwd, 'rev-parse', '--verify', '--quiet', 'HEAD'], { encoding: 'utf8' });
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function diffUnbornPath(cwd, path) {
|
|
41
|
+
try {
|
|
42
|
+
const result = await execFileAsync('git', [
|
|
43
|
+
'-C',
|
|
44
|
+
cwd,
|
|
45
|
+
'diff',
|
|
46
|
+
'--no-index',
|
|
47
|
+
'--no-ext-diff',
|
|
48
|
+
'--no-color',
|
|
49
|
+
'--unified=3',
|
|
50
|
+
'--',
|
|
51
|
+
'/dev/null',
|
|
52
|
+
path,
|
|
53
|
+
], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
|
|
54
|
+
return result.stdout;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const execError = error;
|
|
58
|
+
// `git diff --no-index` exits with code 1 when the inputs differ.
|
|
59
|
+
if (execError.code === 1) {
|
|
60
|
+
return execError.stdout ?? '';
|
|
61
|
+
}
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function loadUnbornDiff(cwd) {
|
|
66
|
+
const stagedResult = await execFileAsync('git', ['-C', cwd, 'diff', '--cached', '--name-only', '-z', '--'], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
|
|
67
|
+
const untrackedResult = await execFileAsync('git', ['-C', cwd, 'ls-files', '--others', '--exclude-standard', '-z'], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
|
|
68
|
+
const stagedPaths = stagedResult.stdout.split('\0').filter(Boolean);
|
|
69
|
+
const untrackedPaths = untrackedResult.stdout.split('\0').filter(Boolean);
|
|
70
|
+
const files = [];
|
|
71
|
+
for (const path of stagedPaths) {
|
|
72
|
+
const result = await execFileAsync('git', [
|
|
73
|
+
'-C',
|
|
74
|
+
cwd,
|
|
75
|
+
'diff',
|
|
76
|
+
'--cached',
|
|
77
|
+
'--no-ext-diff',
|
|
78
|
+
'--no-color',
|
|
79
|
+
'--unified=3',
|
|
80
|
+
'--',
|
|
81
|
+
path,
|
|
82
|
+
], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
|
|
83
|
+
files.push({ path, patch: result.stdout, ...changedLines(result.stdout) });
|
|
84
|
+
}
|
|
85
|
+
for (const path of untrackedPaths) {
|
|
86
|
+
const patch = await diffUnbornPath(cwd, path);
|
|
87
|
+
files.push({ path, patch, ...changedLines(patch) });
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
files,
|
|
91
|
+
additions: files.reduce((total, file) => total + file.additions, 0),
|
|
92
|
+
deletions: files.reduce((total, file) => total + file.deletions, 0),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
31
95
|
export async function loadGitDiff(cwd) {
|
|
96
|
+
if (!(await hasHead(cwd))) {
|
|
97
|
+
return loadUnbornDiff(cwd);
|
|
98
|
+
}
|
|
32
99
|
const names = await execFileAsync('git', ['-C', cwd, 'diff', '--name-only', '-z', 'HEAD', '--'], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
|
|
33
100
|
const paths = names.stdout.split('\0').filter(Boolean);
|
|
34
101
|
const files = [];
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type ForkResult, type ManualCompactResult, type ManualCompactSelection, type RewindPoint, type SessionForkCheckpoint, type SessionInspection, type SessionRunResult, type SessionSummary, type SideQuestionForkResult, type SideQuestionResult } from './application/session-service.js';
|
|
3
|
+
import type { ClaudeSessionCostSnapshot } from './application/session-cost-tracker.js';
|
|
4
|
+
import { type AgentColorName, type AgentColorSelection } from './compatibility/claude/agent-color.js';
|
|
5
|
+
import type { ClaudeDisplayTranscriptItem } from './compatibility/claude/projection.js';
|
|
6
|
+
import { type ModelDocument, type ModelImage, type ModelProvider, type ModelToolCall, type PermissionApproval, type PermissionDecision, type ToolRegistry, type RuntimeEventSink } from './core/runtime.js';
|
|
7
|
+
import type { InteractiveResumeOptions, InteractiveServiceFactory } from './cli/interactive.js';
|
|
8
|
+
import type { TuiSlashCommand } from './cli/tui/slash-commands.js';
|
|
9
|
+
import { type TuiHookConfiguration } from './cli/tui/hook-settings.js';
|
|
10
|
+
import { type ClaudePermissionMode } from './permissions/claude-permission-resolver.js';
|
|
11
|
+
import { type ClaudeMcpServerStatus, type ClaudeMcpToolInspection } from './mcp/claude-mcp-tools.js';
|
|
12
|
+
import { authenticateMcpServer } from './mcp/claude-mcp-oauth.js';
|
|
13
|
+
import { servePraxisMcpStdio } from './mcp/praxis-mcp-server.js';
|
|
14
|
+
import { parseContextEnvironment, parseProviderEnvironment } from './providers/environment.js';
|
|
15
|
+
import { type ClaudeInteractiveToolCallbacks } from './tools/claude-interactive-tools.js';
|
|
16
|
+
import { type TopLevelAgentSummary } from './application/top-level-agent-manager.js';
|
|
17
|
+
import { launchTmuxWorktree } from './platform/tmux-worktree.js';
|
|
18
|
+
import { type CliControls, type CliRuntimeInfo, type CliElicitationRequest, type CliElicitationResult } from './cli/protocol.js';
|
|
19
|
+
import { type PluginEvalDependencies } from './plugins/claude-plugin-eval.js';
|
|
20
|
+
import { type SelfUpdateResult } from './maintenance/self-update.js';
|
|
21
|
+
export { parseContextEnvironment, parseProviderEnvironment };
|
|
22
|
+
export interface CliIO {
|
|
23
|
+
stdout(message: string | Uint8Array): void;
|
|
24
|
+
stderr(message: string): void;
|
|
25
|
+
isTTY?: boolean;
|
|
26
|
+
readStdinLines?: () => AsyncIterable<string | Uint8Array>;
|
|
27
|
+
}
|
|
28
|
+
interface SessionCommands {
|
|
29
|
+
toolRegistry?: ToolRegistry;
|
|
30
|
+
run(prompt: string, signal?: AbortSignal, sessionId?: string, name?: string, images?: readonly ModelImage[], documents?: readonly ModelDocument[]): Promise<SessionRunResult>;
|
|
31
|
+
resume(sessionId: string, prompt: string, signal?: AbortSignal, name?: string, images?: readonly ModelImage[], documents?: readonly ModelDocument[]): Promise<SessionRunResult>;
|
|
32
|
+
runShell?(command: string, signal?: AbortSignal, sessionId?: string, name?: string): Promise<SessionRunResult>;
|
|
33
|
+
resumeShell?(sessionId: string, command: string, signal?: AbortSignal, name?: string): Promise<SessionRunResult>;
|
|
34
|
+
fork(sessionId: string, targetSessionId?: string, resumeSessionAt?: string): Promise<ForkResult>;
|
|
35
|
+
ensureFork?(sessionId: string, targetSessionId: string, checkpoint?: SessionForkCheckpoint): Promise<ForkResult>;
|
|
36
|
+
setPermissionMode?(sessionId: string, mode: ClaudePermissionMode): Promise<void>;
|
|
37
|
+
rewindFiles?(sessionId: string, userMessageId: string): Promise<void>;
|
|
38
|
+
rewindPoints?(sessionId: string): Promise<RewindPoint[]>;
|
|
39
|
+
changeCwd?(sessionId: string | undefined, cwd: string): Promise<string>;
|
|
40
|
+
recordCdUsage?(sessionId: string): Promise<void>;
|
|
41
|
+
approveRecentlyDenied?(sessionId: string, display: string): Promise<void>;
|
|
42
|
+
retryRecentlyDenied?(sessionId: string, display: string, signal?: AbortSignal): Promise<SessionRunResult>;
|
|
43
|
+
answerSideQuestion?(sessionId: string | undefined, question: string, signal?: AbortSignal, onDelta?: (delta: string) => void, permissionMode?: ClaudePermissionMode): Promise<SideQuestionResult>;
|
|
44
|
+
recordBtwUsage?(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
45
|
+
recordColorUsage?(sessionId: string | undefined, selection: AgentColorSelection, display: string, permissionMode?: ClaudePermissionMode, options?: {
|
|
46
|
+
createSession?: boolean;
|
|
47
|
+
}): Promise<string>;
|
|
48
|
+
agentColor?(sessionId: string): Promise<AgentColorName | undefined>;
|
|
49
|
+
recordBackgroundUsage?(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
50
|
+
recordBackgroundLaunch?(sessionId: string): Promise<SessionForkCheckpoint>;
|
|
51
|
+
forkSideQuestion?(sessionId: string, question: string, signal?: AbortSignal): Promise<SideQuestionForkResult>;
|
|
52
|
+
lifecycle?(trigger: 'init' | 'maintenance', options?: {
|
|
53
|
+
sessionStart?: boolean;
|
|
54
|
+
sessionId?: string;
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
sessions(): Promise<SessionSummary[]>;
|
|
57
|
+
inspect(sessionId: string): Promise<SessionInspection>;
|
|
58
|
+
export(sessionId: string): Promise<Buffer>;
|
|
59
|
+
transcript?(sessionId: string): Promise<ClaudeDisplayTranscriptItem[]>;
|
|
60
|
+
costSnapshot?(sessionId: string): Promise<ClaudeSessionCostSnapshot>;
|
|
61
|
+
compact?(sessionId: string, signal?: AbortSignal, selection?: ManualCompactSelection): Promise<ManualCompactResult>;
|
|
62
|
+
rename?(sessionId: string, name: string): Promise<void>;
|
|
63
|
+
sessionNameSuggestion?(sessionId: string, signal?: AbortSignal): Promise<string | null>;
|
|
64
|
+
nextScheduledPrompt?(signal?: AbortSignal): Promise<{
|
|
65
|
+
id: string;
|
|
66
|
+
prompt: string;
|
|
67
|
+
} | null>;
|
|
68
|
+
slashCommands?(): readonly TuiSlashCommand[];
|
|
69
|
+
hookConfiguration?(): Promise<TuiHookConfiguration>;
|
|
70
|
+
mcpInspect?(): Promise<readonly ClaudeMcpServerStatus[]>;
|
|
71
|
+
mcpReconnect?(name: string): Promise<void>;
|
|
72
|
+
mcpAuthenticate?(name: string): Promise<void>;
|
|
73
|
+
mcpReload?(): Promise<void>;
|
|
74
|
+
mcpTools?(name: string): Promise<readonly ClaudeMcpToolInspection[]>;
|
|
75
|
+
close?(): Promise<void>;
|
|
76
|
+
runtimeInfo?(): CliRuntimeInfo;
|
|
77
|
+
initialAgentPrompt?(): string | undefined;
|
|
78
|
+
agentDefinitions?(): readonly {
|
|
79
|
+
name: string;
|
|
80
|
+
description: string;
|
|
81
|
+
}[];
|
|
82
|
+
promptSuggestion?(sessionId: string, signal?: AbortSignal): Promise<string | null>;
|
|
83
|
+
}
|
|
84
|
+
interface TopLevelAgentCommands {
|
|
85
|
+
launch(options: {
|
|
86
|
+
prompt: string;
|
|
87
|
+
argv: string[];
|
|
88
|
+
resumeSessionId?: string;
|
|
89
|
+
cwd?: string;
|
|
90
|
+
deferInitialTurn?: boolean;
|
|
91
|
+
sourceSessionId?: string;
|
|
92
|
+
sourceCheckpoint?: SessionForkCheckpoint;
|
|
93
|
+
initialDetail?: string;
|
|
94
|
+
}): Promise<{
|
|
95
|
+
id: string;
|
|
96
|
+
sessionId: string;
|
|
97
|
+
}>;
|
|
98
|
+
list(options: {
|
|
99
|
+
cwd?: string;
|
|
100
|
+
all: boolean;
|
|
101
|
+
}): Promise<TopLevelAgentSummary[]>;
|
|
102
|
+
logs(id: string): Promise<string>;
|
|
103
|
+
stop(id: string): Promise<void>;
|
|
104
|
+
attach(id: string, input: AsyncIterable<string | Uint8Array>, output: (text: string) => void, signal?: AbortSignal): Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
export interface CliDependencies extends InteractiveServiceFactory {
|
|
107
|
+
loadReleaseNotes?(configRoot: string): Promise<string>;
|
|
108
|
+
/** CLI entrypoint used when this runtime spawns child processes. */
|
|
109
|
+
cliPath?: string;
|
|
110
|
+
createService(options: {
|
|
111
|
+
eventSink: RuntimeEventSink;
|
|
112
|
+
requireProvider: boolean;
|
|
113
|
+
hooksOnly?: boolean;
|
|
114
|
+
approveRecovery?: (call: ModelToolCall) => boolean | Promise<boolean>;
|
|
115
|
+
approveTool?: (call: ModelToolCall, originalCall?: ModelToolCall, decision?: PermissionDecision) => PermissionApproval | Promise<PermissionApproval>;
|
|
116
|
+
agent?: string;
|
|
117
|
+
model?: string;
|
|
118
|
+
effort?: CliControls['effort'];
|
|
119
|
+
permissionMode?: ClaudePermissionMode;
|
|
120
|
+
isSessionActionApproved?: (call: ModelToolCall) => boolean;
|
|
121
|
+
controls?: CliControls;
|
|
122
|
+
interactive?: boolean;
|
|
123
|
+
sessionKind?: 'bg';
|
|
124
|
+
signal?: AbortSignal;
|
|
125
|
+
exposeToolRegistry?: boolean;
|
|
126
|
+
emitToolUseSummaries?: boolean;
|
|
127
|
+
cwd?: string;
|
|
128
|
+
sandboxOriginalCwd?: string;
|
|
129
|
+
configRoot?: string;
|
|
130
|
+
environment?: Readonly<Record<string, string>>;
|
|
131
|
+
providerEnvironment?: NodeJS.ProcessEnv;
|
|
132
|
+
onElicitation?: (request: CliElicitationRequest) => Promise<CliElicitationResult>;
|
|
133
|
+
askUser?: ClaudeInteractiveToolCallbacks['askUser'];
|
|
134
|
+
approvePlan?: ClaudeInteractiveToolCallbacks['approvePlan'];
|
|
135
|
+
}): Promise<SessionCommands>;
|
|
136
|
+
createAutoModeCritic?(options: {
|
|
137
|
+
model?: string;
|
|
138
|
+
}): Promise<ModelProvider>;
|
|
139
|
+
pluginEval?: PluginEvalDependencies;
|
|
140
|
+
runInteractive?(options: {
|
|
141
|
+
agent?: string;
|
|
142
|
+
controls?: CliControls;
|
|
143
|
+
initialPrompt?: string;
|
|
144
|
+
resume?: InteractiveResumeOptions & {
|
|
145
|
+
fromPr?: string | true;
|
|
146
|
+
};
|
|
147
|
+
signal?: AbortSignal;
|
|
148
|
+
}): Promise<number>;
|
|
149
|
+
runAgentsDashboard?(options: {
|
|
150
|
+
manager: TopLevelAgentCommands;
|
|
151
|
+
defaults: {
|
|
152
|
+
argv: readonly string[];
|
|
153
|
+
cwd?: string;
|
|
154
|
+
};
|
|
155
|
+
signal?: AbortSignal;
|
|
156
|
+
}): Promise<number>;
|
|
157
|
+
topLevelAgents?: TopLevelAgentCommands;
|
|
158
|
+
launchTmux?: typeof launchTmuxWorktree;
|
|
159
|
+
mcpAuthenticate?: typeof authenticateMcpServer;
|
|
160
|
+
mcpServe?: typeof servePraxisMcpStdio;
|
|
161
|
+
selfUpdate?: (options: {
|
|
162
|
+
operation: 'install' | 'update';
|
|
163
|
+
target?: string;
|
|
164
|
+
force?: boolean;
|
|
165
|
+
}) => Promise<SelfUpdateResult>;
|
|
166
|
+
}
|
|
167
|
+
export declare function createDefaultDependencies(entrypoint?: string): CliDependencies;
|
|
168
|
+
export declare function createBackgroundWorkerRuntime(workerSink: RuntimeEventSink, dispatch: {
|
|
169
|
+
argv: string[];
|
|
170
|
+
}, createService?: CliDependencies['createService']): Promise<Awaited<ReturnType<CliDependencies['createService']>>>;
|
|
171
|
+
export declare function run(argv: readonly string[], io?: CliIO, dependencies?: CliDependencies, signal?: AbortSignal): Promise<number>;
|
|
172
|
+
//# sourceMappingURL=cli-runtime.d.ts.map
|