praxis-agent 0.57.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/cli/interactive.d.ts +3 -2
- package/dist/cli/interactive.js +15 -6
- package/dist/cli/tui/diff-surface-model.js +2 -2
- package/dist/cli/tui/git-diff.d.ts +5 -0
- package/dist/cli/tui/git-diff.js +276 -89
- package/dist/cli/tui/transcript-presentation.d.ts +15 -1
- package/dist/cli/tui/transcript-presentation.js +45 -0
- package/dist/cli/tui/transcript-window-model.d.ts +7 -0
- package/dist/cli/tui/transcript-window-model.js +66 -1
- package/dist/tools/glob.d.ts +30 -2
- package/dist/tools/glob.js +84 -75
- package/dist/tools/local-tools.d.ts +4 -0
- package/dist/tools/local-tools.js +34 -34
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ sessions, configuration, or compatibility directories.
|
|
|
27
27
|
|
|
28
28
|
- macOS or Linux
|
|
29
29
|
- Node.js 24 or newer
|
|
30
|
-
- [`ripgrep`](https://github.com/BurntSushi/ripgrep) (`rg`) for the Grep
|
|
30
|
+
- [`ripgrep`](https://github.com/BurntSushi/ripgrep) (`rg`) for the Grep and Glob tools
|
|
31
31
|
- an API key and model ID for an Anthropic, OpenAI-compatible, or OpenAI
|
|
32
32
|
Responses provider (the stable setup), or the explicitly enabled experimental
|
|
33
33
|
ChatGPT-backed Codex subscription integration
|
|
@@ -124,7 +124,9 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
124
124
|
- **Local agent runtime** — C+ Quiet Operator responsive TUI with a linear
|
|
125
125
|
`❯` user / `⏺` assistant conversation, `✻` thinking activity, and `!` shell
|
|
126
126
|
composer grammar, compact stable tool rows, responsive density,
|
|
127
|
-
terminal-native background, and a minimal composer/status row.
|
|
127
|
+
terminal-native background, and a minimal composer/status row. Successful
|
|
128
|
+
background Bash completion bursts collapse in normal reading, while
|
|
129
|
+
failed/stopped notifications remain detailed. Interactive
|
|
128
130
|
surfaces share the same presentation across terminals, with English
|
|
129
131
|
permission/configuration choices and a taught `❯` / Up/Down / Enter / Esc
|
|
130
132
|
interaction grammar. While a regular turn is active, the composer remains
|
|
@@ -167,7 +169,8 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
167
169
|
`Ctrl+V` text/image clipboard paste, `Ctrl+Z` shell suspension and `fg`
|
|
168
170
|
recovery, permission-gated provider-free `!` shell turns that persist shell
|
|
169
171
|
input/output for later ordinary prompts without creating an assistant turn,
|
|
170
|
-
navigable current
|
|
172
|
+
navigable session-start/current-per-turn Git diff views with bounded per-file
|
|
173
|
+
patches and readable binary/conflict/transient-path notes, semantic plan/question decision panels with complete
|
|
171
174
|
screen-reader actions, semantic screen projection across selectable surfaces,
|
|
172
175
|
deterministic resize-aware URL/form elicitation rendering, and measured
|
|
173
176
|
context budgets; print mode,
|
|
@@ -9,7 +9,7 @@ import { type ClaudePermissionMode } from '../permissions/claude-permission-reso
|
|
|
9
9
|
import { type TranscriptItem, type TuiDisplayMetadata } from './tui/claude-style.js';
|
|
10
10
|
import { type TuiMemoryFiles } from './tui/memory-files.js';
|
|
11
11
|
import { type TuiHistoryChange } from './tui/tui-view-model.js';
|
|
12
|
-
import { type TuiDiffSnapshot } from './tui/git-diff.js';
|
|
12
|
+
import { type TuiGitDiffSession, type TuiDiffSnapshot } from './tui/git-diff.js';
|
|
13
13
|
import { type TuiPermissionBehavior, type TuiPermissionRule } from './tui/permission-settings.js';
|
|
14
14
|
import { type RecentlyDeniedStore } from './tui/recently-denied.js';
|
|
15
15
|
import { type AgentColorSelection } from '../core/agent-color.js';
|
|
@@ -149,6 +149,7 @@ interface InteractiveAppProps {
|
|
|
149
149
|
allowDangerouslySkipPermissions?: boolean;
|
|
150
150
|
additionalDirectories?: readonly string[];
|
|
151
151
|
diffLoader?: () => Promise<TuiDiffSnapshot>;
|
|
152
|
+
gitDiffSession?: TuiGitDiffSession;
|
|
152
153
|
doctorLoader?: (onProgress?: DoctorProgressListener) => Promise<DoctorReport>;
|
|
153
154
|
fileLoader?: () => Promise<readonly TuiFileEntry[]>;
|
|
154
155
|
externalEditor?: (prompt: string, options: TuiEditorOptions) => Promise<TuiEditorResult>;
|
|
@@ -209,7 +210,7 @@ export interface InteractiveHistoryState {
|
|
|
209
210
|
}
|
|
210
211
|
/** Advances the React-owned transcript plus its exact local mutation fact. */
|
|
211
212
|
export declare function advanceInteractiveHistoryState(current: InteractiveHistoryState, items: TranscriptItem[], changedFrom: number): InteractiveHistoryState;
|
|
212
|
-
export declare function InteractiveApp({ dataPlane, configRoot: suppliedConfigRoot, statePath: suppliedStatePath, factory, initialSessions, initialPrompt, initialHistory, initialSessionColor, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader, ansiRenderer, allowNewSession, resume, display, terminalWidth, slashCommands, agents, allowDangerouslySkipPermissions, additionalDirectories, diffLoader, doctorLoader, fileLoader, externalEditor, keybindingsConfigRoot, keybindingsFile, keybindingsLoader, keybindingsEditor, memoryFilesLoader, memoryEditor, memoryFolderOpener, suspendProcess, clipboardReader, clipboardWriter, sideQuestionClipboardWriter, exportWriter, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver, workspaceDirectoryCompleter, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, notificationDelayMs, elicitationUrlOpener, releaseNotesLoader, settingSources, }: InteractiveAppProps): import("react").JSX.Element;
|
|
213
|
+
export declare function InteractiveApp({ dataPlane, configRoot: suppliedConfigRoot, statePath: suppliedStatePath, factory, initialSessions, initialPrompt, initialHistory, initialSessionColor, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader, ansiRenderer, allowNewSession, resume, display, terminalWidth, slashCommands, agents, allowDangerouslySkipPermissions, additionalDirectories, diffLoader, gitDiffSession: suppliedGitDiffSession, doctorLoader, fileLoader, externalEditor, keybindingsConfigRoot, keybindingsFile, keybindingsLoader, keybindingsEditor, memoryFilesLoader, memoryEditor, memoryFolderOpener, suspendProcess, clipboardReader, clipboardWriter, sideQuestionClipboardWriter, exportWriter, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver, workspaceDirectoryCompleter, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, notificationDelayMs, elicitationUrlOpener, releaseNotesLoader, settingSources, }: InteractiveAppProps): import("react").JSX.Element;
|
|
213
214
|
export declare function runInteractive(options: {
|
|
214
215
|
dataPlane?: DataPlane;
|
|
215
216
|
configRoot?: string;
|
package/dist/cli/interactive.js
CHANGED
|
@@ -33,7 +33,7 @@ import { QuietInkFrame } from './tui/quiet-frame-adapter.js';
|
|
|
33
33
|
import { projectQuietScreenFrame, } from './tui/quiet-screen-projector.js';
|
|
34
34
|
import { projectTuiHelpSurface } from './tui/help-surface-model.js';
|
|
35
35
|
import { projectTuiHooksSurface, } from './tui/hooks-surface-model.js';
|
|
36
|
-
import {
|
|
36
|
+
import { createTuiGitDiffSession, } from './tui/git-diff.js';
|
|
37
37
|
import { projectTuiDiffSurface, } from './tui/diff-surface-model.js';
|
|
38
38
|
import { filterTuiSessionPickerChoices, projectTuiSessionPicker, tuiSessionPickerChoiceId, } from './tui/session-picker-model.js';
|
|
39
39
|
import { addTuiPermissionRule, loadTuiPermissionRules, removeTuiPermissionRule, } from './tui/permission-settings.js';
|
|
@@ -257,7 +257,7 @@ export function advanceInteractiveHistoryState(current, items, changedFrom) {
|
|
|
257
257
|
: createTuiHistoryChange(revision, changedFrom, items, current.items),
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
|
-
export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: suppliedConfigRoot, statePath: suppliedStatePath, factory, initialSessions, initialPrompt, initialHistory = [], initialSessionColor, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader = false, ansiRenderer = false, allowNewSession = true, resume, display = { version: 'dev', cwd: process.cwd() }, terminalWidth, slashCommands = EMPTY_SLASH_COMMANDS, agents = EMPTY_AGENTS, allowDangerouslySkipPermissions = false, additionalDirectories = [], diffLoader, doctorLoader, fileLoader, externalEditor = editTuiPrompt, keybindingsConfigRoot, keybindingsFile = ensureTuiKeybindingsFile, keybindingsLoader = loadTuiKeybindings, keybindingsEditor = openTuiEditorFile, memoryFilesLoader, memoryEditor = openTuiEditorFile, memoryFolderOpener = openTuiMemoryFolder, suspendProcess = suspendTuiProcess, clipboardReader = readTuiClipboard, clipboardWriter = writeTuiClipboard, sideQuestionClipboardWriter = writeTuiOsc52Clipboard, exportWriter = writeConversationExport, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver = resolveTuiWorkspaceDirectory, workspaceDirectoryCompleter = completeTuiWorkspaceDirectory, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, notificationDelayMs = 6_000, elicitationUrlOpener = openTuiUrl, releaseNotesLoader = (configRoot) => loadClaudeReleaseNotes({ configRoot }), settingSources, }) {
|
|
260
|
+
export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: suppliedConfigRoot, statePath: suppliedStatePath, factory, initialSessions, initialPrompt, initialHistory = [], initialSessionColor, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader = false, ansiRenderer = false, allowNewSession = true, resume, display = { version: 'dev', cwd: process.cwd() }, terminalWidth, slashCommands = EMPTY_SLASH_COMMANDS, agents = EMPTY_AGENTS, allowDangerouslySkipPermissions = false, additionalDirectories = [], diffLoader, gitDiffSession: suppliedGitDiffSession, doctorLoader, fileLoader, externalEditor = editTuiPrompt, keybindingsConfigRoot, keybindingsFile = ensureTuiKeybindingsFile, keybindingsLoader = loadTuiKeybindings, keybindingsEditor = openTuiEditorFile, memoryFilesLoader, memoryEditor = openTuiEditorFile, memoryFolderOpener = openTuiMemoryFolder, suspendProcess = suspendTuiProcess, clipboardReader = readTuiClipboard, clipboardWriter = writeTuiClipboard, sideQuestionClipboardWriter = writeTuiOsc52Clipboard, exportWriter = writeConversationExport, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver = resolveTuiWorkspaceDirectory, workspaceDirectoryCompleter = completeTuiWorkspaceDirectory, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, notificationDelayMs = 6_000, elicitationUrlOpener = openTuiUrl, releaseNotesLoader = (configRoot) => loadClaudeReleaseNotes({ configRoot }), settingSources, }) {
|
|
261
261
|
const { exit, suspendTerminal, waitUntilRenderFlush } = useApp();
|
|
262
262
|
const settingsDirectory = '.praxis';
|
|
263
263
|
const keybindingsRoot = useMemo(() => resolve(suppliedConfigRoot ?? keybindingsConfigRoot ?? resolveDataPlaneRoot()), [dataPlane, keybindingsConfigRoot, suppliedConfigRoot]);
|
|
@@ -278,7 +278,10 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
278
278
|
const [runtimeCwd, setRuntimeCwd] = useState(display.cwd);
|
|
279
279
|
const runtimeCwdRef = useRef(display.cwd);
|
|
280
280
|
const runtimeGitignoreRef = useRef(true);
|
|
281
|
-
const
|
|
281
|
+
const gitDiffSession = useMemo(() => suppliedGitDiffSession ?? createTuiGitDiffSession(display.cwd, signal), [display.cwd, signal, suppliedGitDiffSession]);
|
|
282
|
+
const loadDiffSnapshot = useMemo(() => diffLoader
|
|
283
|
+
? () => diffLoader()
|
|
284
|
+
: (signal) => gitDiffSession.load(runtimeCwdRef.current, signal), [diffLoader, gitDiffSession]);
|
|
282
285
|
const loadDoctorReport = useMemo(() => doctorLoader ??
|
|
283
286
|
(async (onProgress) => {
|
|
284
287
|
const runtimeSettings = await loadRuntimeSettings(configTarget);
|
|
@@ -1959,6 +1962,10 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
1959
1962
|
append({
|
|
1960
1963
|
kind: event.status === 'failed' ? 'warning' : 'notice',
|
|
1961
1964
|
text: `Task ${event.status} · ${event.summary}`,
|
|
1965
|
+
taskNotification: {
|
|
1966
|
+
taskId: event.taskId,
|
|
1967
|
+
status: event.status,
|
|
1968
|
+
},
|
|
1962
1969
|
});
|
|
1963
1970
|
break;
|
|
1964
1971
|
case 'compact-boundary':
|
|
@@ -2817,6 +2824,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
2817
2824
|
setBusy(true);
|
|
2818
2825
|
setStatus('changing directory');
|
|
2819
2826
|
const cwd = await commands.changeCwd(sessionId ?? undefined, inspection.canonicalTarget, inspection.canonicalTarget);
|
|
2827
|
+
await gitDiffSession.prepare(cwd, signal);
|
|
2820
2828
|
activeSessionTrust.add(cwd);
|
|
2821
2829
|
runtimeCwdRef.current = cwd;
|
|
2822
2830
|
setRuntimeCwd(cwd);
|
|
@@ -3427,7 +3435,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
3427
3435
|
}
|
|
3428
3436
|
if (turnMutatedFilesRef.current) {
|
|
3429
3437
|
try {
|
|
3430
|
-
const snapshot = await loadDiffSnapshot();
|
|
3438
|
+
const snapshot = await loadDiffSnapshot(signal);
|
|
3431
3439
|
setTurnDiffs((current) => [
|
|
3432
3440
|
...current.filter((source) => source.label !== `T${turnNumber}`),
|
|
3433
3441
|
{ label: `T${turnNumber}`, snapshot },
|
|
@@ -6679,7 +6687,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
6679
6687
|
setBusy(true);
|
|
6680
6688
|
setStatus('loading diff');
|
|
6681
6689
|
try {
|
|
6682
|
-
const current = await loadDiffSnapshot();
|
|
6690
|
+
const current = await loadDiffSnapshot(signal);
|
|
6683
6691
|
updateMenu({
|
|
6684
6692
|
kind: 'diff',
|
|
6685
6693
|
snapshots: [
|
|
@@ -7027,6 +7035,7 @@ export async function runInteractive(options) {
|
|
|
7027
7035
|
const signal = options.signal
|
|
7028
7036
|
? AbortSignal.any([options.signal, controller.signal])
|
|
7029
7037
|
: controller.signal;
|
|
7038
|
+
const gitDiffSession = createTuiGitDiffSession(options.display?.cwd ?? process.cwd(), signal);
|
|
7030
7039
|
let currentResume = options.resume;
|
|
7031
7040
|
let currentInitialPrompt = options.initialPrompt;
|
|
7032
7041
|
let initialAgentPromptResolved = false;
|
|
@@ -7145,7 +7154,7 @@ export async function runInteractive(options) {
|
|
|
7145
7154
|
const ansiRendererEnabled = currentRenderer === 'fullscreen' &&
|
|
7146
7155
|
interactiveTty &&
|
|
7147
7156
|
options.axScreenReader !== true;
|
|
7148
|
-
const instance = render(_jsx(InteractiveApp, { dataPlane: dataPlane, configRoot: configRoot, statePath: statePath, factory: options.factory, initialSessions: initialSessions, slashCommands: initialSlashCommands, agents: initialAgents, initialHistory: history, ...(initialSessionColor === undefined ? {} : { initialSessionColor }), runtimeSettings: currentRuntimeSettings, runtimeSettingsTarget: runtimeSettingsTarget, ...(options.settingSources === undefined
|
|
7157
|
+
const instance = render(_jsx(InteractiveApp, { dataPlane: dataPlane, configRoot: configRoot, statePath: statePath, factory: options.factory, gitDiffSession: gitDiffSession, initialSessions: initialSessions, slashCommands: initialSlashCommands, agents: initialAgents, initialHistory: history, ...(initialSessionColor === undefined ? {} : { initialSessionColor }), runtimeSettings: currentRuntimeSettings, runtimeSettingsTarget: runtimeSettingsTarget, ...(options.settingSources === undefined
|
|
7149
7158
|
? {}
|
|
7150
7159
|
: { settingSources: options.settingSources }), initialThemeSettings: initialThemeSettings, ...(initialThemeLoadError === undefined
|
|
7151
7160
|
? {}
|
|
@@ -46,7 +46,7 @@ export function projectTuiDiffSurface(input) {
|
|
|
46
46
|
if (!input.viewingFile || files.length === 0) {
|
|
47
47
|
return {
|
|
48
48
|
kind: 'diff',
|
|
49
|
-
title: '
|
|
49
|
+
title: 'Changes since session start',
|
|
50
50
|
sourceTabs,
|
|
51
51
|
currentSource: selectedSource
|
|
52
52
|
? { index: sourceIndex, label: source.label }
|
|
@@ -101,7 +101,7 @@ export function projectTuiDiffSurface(input) {
|
|
|
101
101
|
}));
|
|
102
102
|
return {
|
|
103
103
|
kind: 'diff',
|
|
104
|
-
title: '
|
|
104
|
+
title: 'Changes since session start',
|
|
105
105
|
sourceTabs,
|
|
106
106
|
currentSource: selectedSource
|
|
107
107
|
? { index: sourceIndex, label: source.label }
|
|
@@ -9,6 +9,11 @@ export interface TuiDiffSnapshot {
|
|
|
9
9
|
additions: number;
|
|
10
10
|
deletions: number;
|
|
11
11
|
}
|
|
12
|
+
export interface TuiGitDiffSession {
|
|
13
|
+
prepare(cwd: string, signal?: AbortSignal): Promise<void>;
|
|
14
|
+
load(cwd: string, signal?: AbortSignal): Promise<TuiDiffSnapshot>;
|
|
15
|
+
}
|
|
12
16
|
export declare function visiblePatchLines(patch: string): readonly string[];
|
|
17
|
+
export declare function createTuiGitDiffSession(initialCwd: string, sessionSignal?: AbortSignal): TuiGitDiffSession;
|
|
13
18
|
export declare function loadGitDiff(cwd: string): Promise<TuiDiffSnapshot>;
|
|
14
19
|
//# sourceMappingURL=git-diff.d.ts.map
|
package/dist/cli/tui/git-diff.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { realpath } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { BoundedProcessRunner } from '../../platform/bounded-process-runner.js';
|
|
4
|
+
function isOldFileHeader(line) {
|
|
5
|
+
return /^--- (?:a\/|"a\/|\/dev\/null$)/u.test(line);
|
|
6
|
+
}
|
|
7
|
+
function isNewFileHeader(line) {
|
|
8
|
+
return /^\+\+\+ (?:b\/|"b\/|\/dev\/null$)/u.test(line);
|
|
9
|
+
}
|
|
4
10
|
function changedLines(patch) {
|
|
5
11
|
let additions = 0;
|
|
6
12
|
let deletions = 0;
|
|
@@ -12,12 +18,6 @@ function changedLines(patch) {
|
|
|
12
18
|
}
|
|
13
19
|
return { additions, deletions };
|
|
14
20
|
}
|
|
15
|
-
function isOldFileHeader(line) {
|
|
16
|
-
return /^--- (?:a\/|"a\/|\/dev\/null$)/u.test(line);
|
|
17
|
-
}
|
|
18
|
-
function isNewFileHeader(line) {
|
|
19
|
-
return /^\+\+\+ (?:b\/|"b\/|\/dev\/null$)/u.test(line);
|
|
20
|
-
}
|
|
21
21
|
export function visiblePatchLines(patch) {
|
|
22
22
|
return patch
|
|
23
23
|
.split('\n')
|
|
@@ -28,95 +28,282 @@ export function visiblePatchLines(patch) {
|
|
|
28
28
|
!isNewFileHeader(line) &&
|
|
29
29
|
!line.startsWith('@@ '));
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
31
|
+
const CONTROL_LIMIT = 8 * 1024 * 1024;
|
|
32
|
+
const PATCH_LIMIT = 256 * 1024;
|
|
33
|
+
function makeRunner(root, limit = PATCH_LIMIT) {
|
|
34
|
+
return new BoundedProcessRunner({ cwd: root, maxOutputBytes: limit });
|
|
39
35
|
}
|
|
40
|
-
|
|
36
|
+
function note(path, text) {
|
|
37
|
+
return { path, additions: 0, deletions: 0, patch: text };
|
|
38
|
+
}
|
|
39
|
+
async function discover(cwd, signal) {
|
|
41
40
|
try {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
'
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
const process = makeRunner(cwd, CONTROL_LIMIT);
|
|
42
|
+
const rootPromise = process.run({
|
|
43
|
+
command: 'git',
|
|
44
|
+
args: ['-C', cwd, 'rev-parse', '--show-toplevel'],
|
|
45
|
+
timeoutMs: 5_000,
|
|
46
|
+
...(signal ? { signal } : {}),
|
|
47
|
+
});
|
|
48
|
+
const headPromise = process.run({
|
|
49
|
+
command: 'git',
|
|
50
|
+
args: ['-C', cwd, 'rev-parse', '--verify', '--quiet', 'HEAD'],
|
|
51
|
+
timeoutMs: 5_000,
|
|
52
|
+
...(signal ? { signal } : {}),
|
|
53
|
+
});
|
|
54
|
+
const [probe, head] = await Promise.all([rootPromise, headPromise]);
|
|
55
|
+
if (probe.code !== 0 ||
|
|
56
|
+
probe.timedOut ||
|
|
57
|
+
probe.truncated ||
|
|
58
|
+
!probe.stdout.trim())
|
|
59
|
+
throw new Error('Not a Git repository.');
|
|
60
|
+
const root = await realpath(resolve(probe.stdout.trim()));
|
|
61
|
+
if (head.timedOut || head.truncated)
|
|
62
|
+
throw new Error('Git HEAD inspection failed.');
|
|
63
|
+
if (head.code === 0 && head.stdout.trim())
|
|
64
|
+
return { root, oid: head.stdout.trim() };
|
|
65
|
+
if (head.code === 1 && !head.stdout.trim() && !head.stderr.trim())
|
|
66
|
+
return { root, oid: null };
|
|
67
|
+
throw new Error('Git HEAD inspection failed.');
|
|
55
68
|
}
|
|
56
69
|
catch (error) {
|
|
57
|
-
|
|
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;
|
|
70
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
63
71
|
}
|
|
64
72
|
}
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
export function createTuiGitDiffSession(initialCwd, sessionSignal) {
|
|
74
|
+
const baselines = new Map();
|
|
75
|
+
const discoveries = new Map();
|
|
76
|
+
const initialKey = resolve(initialCwd);
|
|
77
|
+
const beginDiscovery = (key, signal) => {
|
|
78
|
+
let pending = discoveries.get(key);
|
|
79
|
+
if (!pending) {
|
|
80
|
+
pending = discover(key, signal).then((result) => {
|
|
81
|
+
if (!('error' in result) && !baselines.has(result.root)) {
|
|
82
|
+
baselines.set(result.root, Promise.resolve(result));
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
});
|
|
86
|
+
discoveries.set(key, pending);
|
|
87
|
+
}
|
|
88
|
+
return pending;
|
|
89
|
+
};
|
|
90
|
+
// Start both repository and HEAD probes before returning the session so a
|
|
91
|
+
// later commit cannot move the initial baseline while the TUI is mounting.
|
|
92
|
+
const initialReady = beginDiscovery(initialKey, sessionSignal).then(() => undefined);
|
|
93
|
+
const prepare = async (cwd, signal = sessionSignal) => {
|
|
94
|
+
const key = resolve(cwd);
|
|
95
|
+
if (key !== initialKey)
|
|
96
|
+
await initialReady;
|
|
97
|
+
await beginDiscovery(key, signal);
|
|
98
|
+
};
|
|
99
|
+
const load = async (cwd, signal) => {
|
|
100
|
+
await prepare(cwd, signal);
|
|
101
|
+
if (signal?.aborted)
|
|
102
|
+
throw new DOMException('Tool execution aborted', 'AbortError');
|
|
103
|
+
const discovery = await discoveries.get(resolve(cwd));
|
|
104
|
+
const captured = discovery && !('error' in discovery)
|
|
105
|
+
? await baselines.get(discovery.root)
|
|
106
|
+
: discovery;
|
|
107
|
+
if (!captured)
|
|
108
|
+
throw new Error('Git baseline unavailable.');
|
|
109
|
+
if ('error' in captured)
|
|
110
|
+
throw captured.error;
|
|
111
|
+
const { root, oid } = captured;
|
|
112
|
+
const git = makeRunner(root);
|
|
113
|
+
const control = makeRunner(root, CONTROL_LIMIT);
|
|
114
|
+
const runGit = (args) => git.run({
|
|
115
|
+
command: 'git',
|
|
116
|
+
args,
|
|
117
|
+
timeoutMs: 5_000,
|
|
118
|
+
...(signal ? { signal } : {}),
|
|
119
|
+
});
|
|
120
|
+
const list = async (args) => {
|
|
121
|
+
const result = await control.run({
|
|
122
|
+
command: 'git',
|
|
123
|
+
args,
|
|
124
|
+
timeoutMs: 5_000,
|
|
125
|
+
...(signal ? { signal } : {}),
|
|
126
|
+
});
|
|
127
|
+
if (result.code !== 0 || result.timedOut || result.truncated)
|
|
128
|
+
throw new Error('Git diff enumeration failed.');
|
|
129
|
+
return result.stdout.split('\0').filter(Boolean);
|
|
130
|
+
};
|
|
131
|
+
const untracked = await list([
|
|
73
132
|
'-C',
|
|
74
|
-
|
|
75
|
-
'
|
|
76
|
-
'--
|
|
77
|
-
'--
|
|
78
|
-
'
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
133
|
+
root,
|
|
134
|
+
'ls-files',
|
|
135
|
+
'--others',
|
|
136
|
+
'--exclude-standard',
|
|
137
|
+
'-z',
|
|
138
|
+
]);
|
|
139
|
+
const untrackedSet = new Set(untracked);
|
|
140
|
+
let paths;
|
|
141
|
+
if (oid === null) {
|
|
142
|
+
const cached = await list(['-C', root, 'ls-files', '--cached', '-z']);
|
|
143
|
+
const deleted = new Set(await list(['-C', root, 'ls-files', '--deleted', '-z']));
|
|
144
|
+
paths = [...new Set([...cached, ...untracked])]
|
|
145
|
+
.filter((path) => !deleted.has(path))
|
|
146
|
+
.sort();
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
paths = [
|
|
150
|
+
...new Set([
|
|
151
|
+
...(await list([
|
|
152
|
+
'-C',
|
|
153
|
+
root,
|
|
154
|
+
'diff',
|
|
155
|
+
'--no-ext-diff',
|
|
156
|
+
'--no-renames',
|
|
157
|
+
'--name-only',
|
|
158
|
+
'-z',
|
|
159
|
+
oid,
|
|
160
|
+
'--',
|
|
161
|
+
])),
|
|
162
|
+
...(await list([
|
|
163
|
+
'-C',
|
|
164
|
+
root,
|
|
165
|
+
'diff',
|
|
166
|
+
'--cached',
|
|
167
|
+
'--no-ext-diff',
|
|
168
|
+
'--no-renames',
|
|
169
|
+
'--name-only',
|
|
170
|
+
'-z',
|
|
171
|
+
oid,
|
|
172
|
+
'--',
|
|
173
|
+
])),
|
|
174
|
+
...untracked,
|
|
175
|
+
]),
|
|
176
|
+
].sort();
|
|
177
|
+
}
|
|
178
|
+
const baselineOid = oid ?? '';
|
|
179
|
+
const files = [];
|
|
180
|
+
for (const path of paths) {
|
|
181
|
+
if (signal?.aborted)
|
|
182
|
+
throw new DOMException('Tool execution aborted', 'AbortError');
|
|
183
|
+
try {
|
|
184
|
+
const conflict = await runGit([
|
|
185
|
+
'-C',
|
|
186
|
+
root,
|
|
187
|
+
'ls-files',
|
|
188
|
+
'-u',
|
|
189
|
+
'--',
|
|
190
|
+
path,
|
|
191
|
+
]);
|
|
192
|
+
if (conflict.code !== 0 || conflict.timedOut || conflict.truncated) {
|
|
193
|
+
files.push(note(path, 'Diff unavailable; Git inspection failed.'));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (conflict.stdout.trim()) {
|
|
197
|
+
files.push(note(path, 'Merge conflict; patch omitted.'));
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const numstatArgs = oid === null || untrackedSet.has(path)
|
|
201
|
+
? [
|
|
202
|
+
'-C',
|
|
203
|
+
root,
|
|
204
|
+
'diff',
|
|
205
|
+
'--no-index',
|
|
206
|
+
'--no-ext-diff',
|
|
207
|
+
'--numstat',
|
|
208
|
+
'--',
|
|
209
|
+
'/dev/null',
|
|
210
|
+
path,
|
|
211
|
+
]
|
|
212
|
+
: [
|
|
213
|
+
'-C',
|
|
214
|
+
root,
|
|
215
|
+
'diff',
|
|
216
|
+
'--no-ext-diff',
|
|
217
|
+
'--no-renames',
|
|
218
|
+
'--numstat',
|
|
219
|
+
baselineOid,
|
|
220
|
+
'--',
|
|
221
|
+
path,
|
|
222
|
+
];
|
|
223
|
+
const numstat = await (untrackedSet.has(path) ? control : git).run({
|
|
224
|
+
command: 'git',
|
|
225
|
+
args: numstatArgs,
|
|
226
|
+
timeoutMs: 5_000,
|
|
227
|
+
...(signal ? { signal } : {}),
|
|
228
|
+
});
|
|
229
|
+
const validNumstatDifference = (oid === null || untrackedSet.has(path)) &&
|
|
230
|
+
numstat.code === 1 &&
|
|
231
|
+
!/error|fatal|permission|cannot|no such/iu.test(numstat.stderr);
|
|
232
|
+
if ((numstat.code !== 0 && !validNumstatDifference) ||
|
|
233
|
+
numstat.timedOut ||
|
|
234
|
+
numstat.truncated) {
|
|
235
|
+
files.push(note(path, 'Diff unavailable; Git inspection failed.'));
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (/^-\s+-\s+/u.test(numstat.stdout)) {
|
|
239
|
+
files.push(note(path, 'Binary file; patch omitted.'));
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const args = oid === null || untrackedSet.has(path)
|
|
243
|
+
? [
|
|
244
|
+
'-C',
|
|
245
|
+
root,
|
|
246
|
+
'diff',
|
|
247
|
+
'--no-index',
|
|
248
|
+
'--no-ext-diff',
|
|
249
|
+
'--no-color',
|
|
250
|
+
'--unified=3',
|
|
251
|
+
'--',
|
|
252
|
+
'/dev/null',
|
|
253
|
+
path,
|
|
254
|
+
]
|
|
255
|
+
: [
|
|
256
|
+
'-C',
|
|
257
|
+
root,
|
|
258
|
+
'diff',
|
|
259
|
+
'--no-ext-diff',
|
|
260
|
+
'--no-color',
|
|
261
|
+
'--unified=3',
|
|
262
|
+
baselineOid,
|
|
263
|
+
'--',
|
|
264
|
+
path,
|
|
265
|
+
];
|
|
266
|
+
const result = await runGit(args);
|
|
267
|
+
if (result.timedOut) {
|
|
268
|
+
files.push(note(path, 'Diff unavailable; Git inspection timed out.'));
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const noIndexDifference = (oid === null || untrackedSet.has(path)) &&
|
|
272
|
+
result.code === 1 &&
|
|
273
|
+
!/error|fatal|permission|cannot|no such/iu.test(result.stderr);
|
|
274
|
+
if (result.code !== 0 && !noIndexDifference) {
|
|
275
|
+
const disappeared = /no such|cannot stat|does not exist|not found/iu.test(result.stderr);
|
|
276
|
+
files.push(note(path, disappeared
|
|
277
|
+
? 'Diff unavailable; path changed during inspection.'
|
|
278
|
+
: 'Diff unavailable; Git inspection failed.'));
|
|
279
|
+
}
|
|
280
|
+
else
|
|
281
|
+
files.push({
|
|
282
|
+
path,
|
|
283
|
+
patch: result.stdout,
|
|
284
|
+
...changedLines(result.stdout),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
if (error instanceof DOMException && error.name === 'AbortError')
|
|
289
|
+
throw error;
|
|
290
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
291
|
+
files.push(note(path, /no such|cannot stat|does not exist|not found/iu.test(message)
|
|
292
|
+
? 'Diff unavailable; path changed during inspection.'
|
|
293
|
+
: 'Diff unavailable; Git inspection failed.'));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
files,
|
|
298
|
+
additions: files.reduce((n, f) => n + f.additions, 0),
|
|
299
|
+
deletions: files.reduce((n, f) => n + f.deletions, 0),
|
|
300
|
+
};
|
|
93
301
|
};
|
|
302
|
+
return { prepare, load };
|
|
94
303
|
}
|
|
95
304
|
export async function loadGitDiff(cwd) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const names = await execFileAsync('git', ['-C', cwd, 'diff', '--name-only', '-z', 'HEAD', '--'], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
|
|
100
|
-
const paths = names.stdout.split('\0').filter(Boolean);
|
|
101
|
-
const files = [];
|
|
102
|
-
for (const path of paths) {
|
|
103
|
-
const result = await execFileAsync('git', [
|
|
104
|
-
'-C',
|
|
105
|
-
cwd,
|
|
106
|
-
'diff',
|
|
107
|
-
'--no-ext-diff',
|
|
108
|
-
'--no-color',
|
|
109
|
-
'--unified=3',
|
|
110
|
-
'HEAD',
|
|
111
|
-
'--',
|
|
112
|
-
path,
|
|
113
|
-
], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
|
|
114
|
-
files.push({ path, patch: result.stdout, ...changedLines(result.stdout) });
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
files,
|
|
118
|
-
additions: files.reduce((total, file) => total + file.additions, 0),
|
|
119
|
-
deletions: files.reduce((total, file) => total + file.deletions, 0),
|
|
120
|
-
};
|
|
305
|
+
const session = createTuiGitDiffSession(cwd);
|
|
306
|
+
await session.prepare(cwd);
|
|
307
|
+
return session.load(cwd);
|
|
121
308
|
}
|
|
122
309
|
//# sourceMappingURL=git-diff.js.map
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { ModelToolCall } from '../../core/runtime.js';
|
|
2
|
+
export type TaskNotificationMetadata = {
|
|
3
|
+
readonly taskId: string;
|
|
4
|
+
readonly status: 'completed' | 'failed' | 'stopped';
|
|
5
|
+
};
|
|
2
6
|
export type TranscriptItem = {
|
|
3
|
-
kind: 'user' | 'assistant'
|
|
7
|
+
kind: 'user' | 'assistant';
|
|
8
|
+
text: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'notice' | 'warning';
|
|
4
11
|
text: string;
|
|
12
|
+
taskNotification?: TaskNotificationMetadata;
|
|
5
13
|
} | {
|
|
6
14
|
kind: 'local-result';
|
|
7
15
|
text: string;
|
|
@@ -102,6 +110,12 @@ export type TranscriptPresentationEntry = TranscriptPresentationEntryValue & {
|
|
|
102
110
|
export declare function transcriptPresentationEntryKey(item: TranscriptItem, sourceIndex: number): string;
|
|
103
111
|
/** Stable renderer key for a Normal-mode contiguous Read summary. */
|
|
104
112
|
export declare function transcriptReadSummaryKey(sourceIndex: number): string;
|
|
113
|
+
export declare function isCompletedBackgroundBashNotification(item: TranscriptItem): Extract<TranscriptItem, {
|
|
114
|
+
kind: 'notice' | 'warning';
|
|
115
|
+
}> | undefined;
|
|
116
|
+
export declare function completedBackgroundBashSummary(count: number): Extract<TranscriptItem, {
|
|
117
|
+
kind: 'notice' | 'warning';
|
|
118
|
+
}>;
|
|
105
119
|
export declare function projectTranscriptPresentation(items: readonly TranscriptItem[], mode: TranscriptPresentationMode): readonly TranscriptPresentationEntry[];
|
|
106
120
|
export {};
|
|
107
121
|
//# sourceMappingURL=transcript-presentation.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBackgroundBashTaskId } from '../../application/background-task-id.js';
|
|
1
2
|
/** Stable renderer key for one source Transcript item. */
|
|
2
3
|
export function transcriptPresentationEntryKey(item, sourceIndex) {
|
|
3
4
|
if (item.kind === 'tool')
|
|
@@ -14,6 +15,24 @@ export function transcriptPresentationEntryKey(item, sourceIndex) {
|
|
|
14
15
|
export function transcriptReadSummaryKey(sourceIndex) {
|
|
15
16
|
return `read-summary-${sourceIndex}`;
|
|
16
17
|
}
|
|
18
|
+
export function isCompletedBackgroundBashNotification(item) {
|
|
19
|
+
if (item.kind !== 'notice' && item.kind !== 'warning')
|
|
20
|
+
return undefined;
|
|
21
|
+
const metadata = item.taskNotification;
|
|
22
|
+
if (!metadata ||
|
|
23
|
+
metadata.status !== 'completed' ||
|
|
24
|
+
!isBackgroundBashTaskId(metadata.taskId))
|
|
25
|
+
return undefined;
|
|
26
|
+
return item;
|
|
27
|
+
}
|
|
28
|
+
export function completedBackgroundBashSummary(count) {
|
|
29
|
+
if (!Number.isInteger(count) || count <= 0)
|
|
30
|
+
throw new RangeError('count must be a positive integer');
|
|
31
|
+
return {
|
|
32
|
+
kind: 'notice',
|
|
33
|
+
text: `${count} background commands completed`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
17
36
|
function pairResults(items) {
|
|
18
37
|
const toolResults = new Map();
|
|
19
38
|
const toolResultIndexes = new Map();
|
|
@@ -124,6 +143,32 @@ export function projectTranscriptPresentation(items, mode) {
|
|
|
124
143
|
const item = items[index];
|
|
125
144
|
if (!item)
|
|
126
145
|
continue;
|
|
146
|
+
if (mode === 'normal') {
|
|
147
|
+
const notification = isCompletedBackgroundBashNotification(item);
|
|
148
|
+
if (notification) {
|
|
149
|
+
const notifications = [notification];
|
|
150
|
+
let end = index + 1;
|
|
151
|
+
while (end < items.length) {
|
|
152
|
+
const next = items[end];
|
|
153
|
+
const nextNotification = next
|
|
154
|
+
? isCompletedBackgroundBashNotification(next)
|
|
155
|
+
: undefined;
|
|
156
|
+
if (!nextNotification)
|
|
157
|
+
break;
|
|
158
|
+
notifications.push(nextNotification);
|
|
159
|
+
end += 1;
|
|
160
|
+
}
|
|
161
|
+
if (notifications.length >= 2) {
|
|
162
|
+
entries.push({
|
|
163
|
+
kind: 'item',
|
|
164
|
+
key: transcriptPresentationEntryKey(item, index),
|
|
165
|
+
item: completedBackgroundBashSummary(notifications.length),
|
|
166
|
+
});
|
|
167
|
+
index = end - 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
127
172
|
if (mode === 'normal' &&
|
|
128
173
|
item.kind === 'tool' &&
|
|
129
174
|
item.call.name === 'Read') {
|
|
@@ -76,6 +76,12 @@ interface TailReadRun {
|
|
|
76
76
|
readonly calls: readonly ReadRunCall[];
|
|
77
77
|
readonly summarized: boolean;
|
|
78
78
|
}
|
|
79
|
+
interface TailCompletedBackgroundBashRun {
|
|
80
|
+
readonly startSourceIndex: number;
|
|
81
|
+
readonly startPresentationIndex: number;
|
|
82
|
+
readonly nextSourceIndex: number;
|
|
83
|
+
readonly count: number;
|
|
84
|
+
}
|
|
79
85
|
export interface TranscriptWindowState {
|
|
80
86
|
readonly history: readonly TranscriptItem[];
|
|
81
87
|
readonly mode: TranscriptPresentationMode;
|
|
@@ -84,6 +90,7 @@ export interface TranscriptWindowState {
|
|
|
84
90
|
readonly pendingTools?: PendingNode;
|
|
85
91
|
readonly pendingShells?: PendingNode;
|
|
86
92
|
readonly tailReadRun?: TailReadRun;
|
|
93
|
+
readonly tailCompletedBackgroundBashRun?: TailCompletedBackgroundBashRun;
|
|
87
94
|
readonly revision: number;
|
|
88
95
|
}
|
|
89
96
|
export interface TranscriptWindowInput {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { projectTranscriptPresentation, transcriptPresentationEntryKey, transcriptReadSummaryKey, } from './transcript-presentation.js';
|
|
1
|
+
import { completedBackgroundBashSummary, isCompletedBackgroundBashNotification, projectTranscriptPresentation, transcriptPresentationEntryKey, transcriptReadSummaryKey, } from './transcript-presentation.js';
|
|
2
2
|
import { createTranscriptEntryViewportIndex, estimateTranscriptEntryLines, projectTranscriptEntryRange, } from './transcript-viewport.js';
|
|
3
3
|
const historyChangeLineage = Symbol('tui-history-change-lineage');
|
|
4
4
|
function retainedHistoryChange(revision, changedFrom, history, previousHistory) {
|
|
@@ -465,6 +465,29 @@ function coldTailReadRun(history, pairing) {
|
|
|
465
465
|
summarized,
|
|
466
466
|
};
|
|
467
467
|
}
|
|
468
|
+
function coldTailCompletedBackgroundBashRun(history, entries) {
|
|
469
|
+
let start = history.length;
|
|
470
|
+
while (start > 0) {
|
|
471
|
+
const candidate = history[start - 1];
|
|
472
|
+
if (!candidate || !isCompletedBackgroundBashNotification(candidate))
|
|
473
|
+
break;
|
|
474
|
+
start -= 1;
|
|
475
|
+
}
|
|
476
|
+
if (start >= history.length)
|
|
477
|
+
return undefined;
|
|
478
|
+
const first = history[start];
|
|
479
|
+
if (!first)
|
|
480
|
+
return undefined;
|
|
481
|
+
const startPresentationIndex = entries.findIndex((entry) => entry.key === transcriptPresentationEntryKey(first, start));
|
|
482
|
+
if (startPresentationIndex < 0)
|
|
483
|
+
return undefined;
|
|
484
|
+
return {
|
|
485
|
+
startSourceIndex: start,
|
|
486
|
+
startPresentationIndex,
|
|
487
|
+
nextSourceIndex: history.length,
|
|
488
|
+
count: history.length - start,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
468
491
|
function canReuseEntry(previous, next) {
|
|
469
492
|
if (previous.kind !== next.kind || previous.key !== next.key)
|
|
470
493
|
return false;
|
|
@@ -508,6 +531,9 @@ function coldState(input, previous) {
|
|
|
508
531
|
const tailReadRun = input.mode === 'normal'
|
|
509
532
|
? coldTailReadRun(input.history, pairing)
|
|
510
533
|
: undefined;
|
|
534
|
+
const tailCompletedBackgroundBashRun = input.mode === 'normal'
|
|
535
|
+
? coldTailCompletedBackgroundBashRun(input.history, entries)
|
|
536
|
+
: undefined;
|
|
511
537
|
return {
|
|
512
538
|
history: input.history,
|
|
513
539
|
mode: input.mode,
|
|
@@ -516,6 +542,9 @@ function coldState(input, previous) {
|
|
|
516
542
|
...(pairing.pendingTools ? { pendingTools: pairing.pendingTools } : {}),
|
|
517
543
|
...(pairing.pendingShells ? { pendingShells: pairing.pendingShells } : {}),
|
|
518
544
|
...(tailReadRun ? { tailReadRun } : {}),
|
|
545
|
+
...(tailCompletedBackgroundBashRun
|
|
546
|
+
? { tailCompletedBackgroundBashRun }
|
|
547
|
+
: {}),
|
|
519
548
|
revision: input.revision,
|
|
520
549
|
};
|
|
521
550
|
}
|
|
@@ -565,6 +594,7 @@ function appendState(input, previous, change) {
|
|
|
565
594
|
let pendingTools = previous.pendingTools;
|
|
566
595
|
let pendingShells = previous.pendingShells;
|
|
567
596
|
let tailReadRun = previous.tailReadRun;
|
|
597
|
+
let tailCompletedBackgroundBashRun = previous.tailCompletedBackgroundBashRun;
|
|
568
598
|
for (let offset = 0; offset < suffix.length; offset += 1) {
|
|
569
599
|
const sourceIndex = previousLength + offset;
|
|
570
600
|
const item = suffix[offset];
|
|
@@ -582,6 +612,38 @@ function appendState(input, previous, change) {
|
|
|
582
612
|
tailReadRun.calls.some((call) => call.sourceIndex === pendingTool.sourceIndex)));
|
|
583
613
|
if (!continuesReadRun)
|
|
584
614
|
tailReadRun = undefined;
|
|
615
|
+
const notification = input.mode === 'normal'
|
|
616
|
+
? isCompletedBackgroundBashNotification(item)
|
|
617
|
+
: undefined;
|
|
618
|
+
if (notification) {
|
|
619
|
+
const continuesRun = tailCompletedBackgroundBashRun?.nextSourceIndex === sourceIndex;
|
|
620
|
+
if (continuesRun && tailCompletedBackgroundBashRun) {
|
|
621
|
+
const count = tailCompletedBackgroundBashRun.count + 1;
|
|
622
|
+
const firstItem = input.history[tailCompletedBackgroundBashRun.startSourceIndex] ?? item;
|
|
623
|
+
tree = updateEntry(tree, tailCompletedBackgroundBashRun.startPresentationIndex, {
|
|
624
|
+
kind: 'item',
|
|
625
|
+
key: transcriptPresentationEntryKey(firstItem, tailCompletedBackgroundBashRun.startSourceIndex),
|
|
626
|
+
item: completedBackgroundBashSummary(count),
|
|
627
|
+
}, input.width, input.mode);
|
|
628
|
+
tailCompletedBackgroundBashRun = {
|
|
629
|
+
...tailCompletedBackgroundBashRun,
|
|
630
|
+
nextSourceIndex: sourceIndex + 1,
|
|
631
|
+
count,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
const presentationIndex = tree.entryCount;
|
|
636
|
+
tree = appendEntry(tree, entryForItem(item, sourceIndex), input.width, input.mode);
|
|
637
|
+
tailCompletedBackgroundBashRun = {
|
|
638
|
+
startSourceIndex: sourceIndex,
|
|
639
|
+
startPresentationIndex: presentationIndex,
|
|
640
|
+
nextSourceIndex: sourceIndex + 1,
|
|
641
|
+
count: 1,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
tailCompletedBackgroundBashRun = undefined;
|
|
585
647
|
if (item.kind === 'tool') {
|
|
586
648
|
if (item.call.name === 'Read' && input.mode === 'normal') {
|
|
587
649
|
if (tailReadRun?.summarized) {
|
|
@@ -704,6 +766,9 @@ function appendState(input, previous, change) {
|
|
|
704
766
|
...(pendingTools ? { pendingTools } : {}),
|
|
705
767
|
...(pendingShells ? { pendingShells } : {}),
|
|
706
768
|
...(tailReadRun ? { tailReadRun } : {}),
|
|
769
|
+
...(tailCompletedBackgroundBashRun
|
|
770
|
+
? { tailCompletedBackgroundBashRun }
|
|
771
|
+
: {}),
|
|
707
772
|
revision: input.revision,
|
|
708
773
|
};
|
|
709
774
|
}
|
package/dist/tools/glob.d.ts
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ProcessResult } from '../platform/bounded-process-runner.js';
|
|
2
|
+
export interface GlobSearchRequest {
|
|
2
3
|
root: string;
|
|
3
4
|
displayRoot: string;
|
|
4
5
|
absoluteRoot: string;
|
|
5
6
|
pattern: string;
|
|
6
7
|
signal?: AbortSignal;
|
|
7
8
|
}
|
|
8
|
-
export
|
|
9
|
+
export interface GlobSearchResult {
|
|
10
|
+
content: string;
|
|
11
|
+
isError: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface GlobSearch {
|
|
14
|
+
search(request: GlobSearchRequest): Promise<GlobSearchResult>;
|
|
15
|
+
}
|
|
16
|
+
export interface RipgrepGlobSearchOptions {
|
|
17
|
+
cwd: string;
|
|
18
|
+
timeoutMs: number;
|
|
19
|
+
environment?: Readonly<Record<string, string>>;
|
|
20
|
+
runner?: {
|
|
21
|
+
run(options: {
|
|
22
|
+
command: string;
|
|
23
|
+
args: readonly string[];
|
|
24
|
+
cwd?: string;
|
|
25
|
+
timeoutMs: number;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
env?: Readonly<Record<string, string>>;
|
|
28
|
+
}): Promise<ProcessResult>;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare class RipgrepGlobSearch implements GlobSearch {
|
|
32
|
+
private readonly options;
|
|
33
|
+
private readonly runner;
|
|
34
|
+
constructor(options: RipgrepGlobSearchOptions);
|
|
35
|
+
search(request: GlobSearchRequest): Promise<GlobSearchResult>;
|
|
36
|
+
}
|
|
9
37
|
//# sourceMappingURL=glob.d.ts.map
|
package/dist/tools/glob.js
CHANGED
|
@@ -1,88 +1,97 @@
|
|
|
1
|
-
import { lstat, opendir } from 'node:fs/promises';
|
|
2
1
|
import { isAbsolute, join, resolve, sep } from 'node:path';
|
|
3
2
|
import { minimatch } from 'minimatch';
|
|
3
|
+
import { BoundedProcessRunner, joinedProcessOutput, } from '../platform/bounded-process-runner.js';
|
|
4
4
|
const MAX_RESULTS = 100;
|
|
5
|
+
const MAX_ENUMERATION_BYTES = 2 * 1024 * 1024;
|
|
6
|
+
function portable(value) {
|
|
7
|
+
return sep === '/' ? value : value.split(sep).join('/');
|
|
8
|
+
}
|
|
5
9
|
function abortError() {
|
|
6
10
|
return new DOMException('Tool execution aborted', 'AbortError');
|
|
7
11
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const current = matches[middle];
|
|
20
|
-
if (current && compareMatches(current, candidate) <= 0)
|
|
21
|
-
low = middle + 1;
|
|
22
|
-
else
|
|
23
|
-
high = middle;
|
|
12
|
+
export class RipgrepGlobSearch {
|
|
13
|
+
options;
|
|
14
|
+
runner;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.runner =
|
|
18
|
+
options.runner ??
|
|
19
|
+
new BoundedProcessRunner({
|
|
20
|
+
cwd: options.cwd,
|
|
21
|
+
maxOutputBytes: MAX_ENUMERATION_BYTES,
|
|
22
|
+
});
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
matches.pop();
|
|
28
|
-
}
|
|
29
|
-
export async function globFiles(options) {
|
|
30
|
-
const pattern = portablePath(options.pattern);
|
|
31
|
-
const absolutePattern = isAbsolute(options.pattern);
|
|
32
|
-
const matchBase = !absolutePattern && !pattern.includes('/');
|
|
33
|
-
const matches = [];
|
|
34
|
-
const directories = [''];
|
|
35
|
-
let matchCount = 0;
|
|
36
|
-
let order = 0;
|
|
37
|
-
while (directories.length > 0) {
|
|
38
|
-
if (options.signal?.aborted)
|
|
24
|
+
async search(request) {
|
|
25
|
+
if (request.signal?.aborted)
|
|
39
26
|
throw abortError();
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let metadata;
|
|
59
|
-
try {
|
|
60
|
-
metadata = await lstat(join(options.root, relativePath));
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
if (error.code === 'ENOENT')
|
|
64
|
-
continue;
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
if (!metadata.isFile())
|
|
68
|
-
continue;
|
|
69
|
-
const displayPath = absolutePattern
|
|
70
|
-
? absolutePath
|
|
71
|
-
: portablePath(join(options.displayRoot, relativePath));
|
|
72
|
-
insertOldest(matches, {
|
|
73
|
-
path: displayPath,
|
|
74
|
-
mtimeMs: metadata.mtimeMs,
|
|
75
|
-
order,
|
|
27
|
+
const hidden = this.options.environment?.CLAUDE_CODE_GLOB_HIDDEN !== 'false';
|
|
28
|
+
const noIgnore = this.options.environment?.CLAUDE_CODE_GLOB_NO_IGNORE !== 'false';
|
|
29
|
+
let result;
|
|
30
|
+
try {
|
|
31
|
+
result = await this.runner.run({
|
|
32
|
+
command: 'rg',
|
|
33
|
+
args: [
|
|
34
|
+
'--files',
|
|
35
|
+
'--null',
|
|
36
|
+
'--sort',
|
|
37
|
+
'modified',
|
|
38
|
+
...(hidden ? ['--hidden'] : []),
|
|
39
|
+
...(noIgnore ? ['--no-ignore'] : []),
|
|
40
|
+
],
|
|
41
|
+
cwd: request.root,
|
|
42
|
+
timeoutMs: this.options.timeoutMs,
|
|
43
|
+
...(request.signal ? { signal: request.signal } : {}),
|
|
44
|
+
...(this.options.environment ? { env: this.options.environment } : {}),
|
|
76
45
|
});
|
|
77
|
-
matchCount += 1;
|
|
78
|
-
order += 1;
|
|
79
46
|
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error instanceof Error && error.name === 'AbortError')
|
|
49
|
+
throw error;
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
throw new Error(`Glob enumeration failed: ${message}`);
|
|
52
|
+
}
|
|
53
|
+
if (result.timedOut)
|
|
54
|
+
return {
|
|
55
|
+
content: `Search timed out after ${this.options.timeoutMs}ms`,
|
|
56
|
+
isError: true,
|
|
57
|
+
};
|
|
58
|
+
if (result.truncated)
|
|
59
|
+
throw new Error('Glob enumeration failed: output truncated');
|
|
60
|
+
if (result.code !== 0) {
|
|
61
|
+
if (result.code === 1 && !result.stdout && !result.stderr)
|
|
62
|
+
return { content: 'No files found', isError: false };
|
|
63
|
+
const output = joinedProcessOutput(result);
|
|
64
|
+
throw new Error(`Glob enumeration failed with exit code ${result.code}${output ? `: ${output}` : ''}`);
|
|
65
|
+
}
|
|
66
|
+
const pattern = portable(request.pattern);
|
|
67
|
+
const absolutePattern = isAbsolute(request.pattern);
|
|
68
|
+
const matchBase = !absolutePattern && !pattern.includes('/');
|
|
69
|
+
const paths = result.stdout.split('\0').filter(Boolean);
|
|
70
|
+
const matched = paths.filter((path) => {
|
|
71
|
+
const relativePath = portable(path);
|
|
72
|
+
const absolutePath = portable(resolve(request.absoluteRoot, relativePath));
|
|
73
|
+
return (pattern === '' ||
|
|
74
|
+
minimatch(absolutePattern ? absolutePath : relativePath, pattern, {
|
|
75
|
+
dot: true,
|
|
76
|
+
matchBase,
|
|
77
|
+
noext: true,
|
|
78
|
+
}));
|
|
79
|
+
});
|
|
80
|
+
if (matched.length === 0)
|
|
81
|
+
return { content: 'No files found', isError: false };
|
|
82
|
+
const display = matched.slice(0, MAX_RESULTS).map((path) => {
|
|
83
|
+
const relativePath = portable(path);
|
|
84
|
+
if (absolutePattern)
|
|
85
|
+
return portable(resolve(request.absoluteRoot, relativePath));
|
|
86
|
+
return portable(join(request.displayRoot, relativePath));
|
|
87
|
+
});
|
|
88
|
+
const content = display.join('\n');
|
|
89
|
+
return {
|
|
90
|
+
content: matched.length > MAX_RESULTS
|
|
91
|
+
? `${content}\n(Showing 100 of ${matched.length} matching files; ${matched.length - 100} more are not listed. Narrow the pattern or path to see the rest.)`
|
|
92
|
+
: content,
|
|
93
|
+
isError: false,
|
|
94
|
+
};
|
|
80
95
|
}
|
|
81
|
-
if (matchCount === 0)
|
|
82
|
-
return 'No files found';
|
|
83
|
-
const content = matches.map((match) => match.path).join('\n');
|
|
84
|
-
return matchCount > MAX_RESULTS
|
|
85
|
-
? `${content}\n(Showing ${MAX_RESULTS} of ${matchCount} matching files; ${matchCount - MAX_RESULTS} more are not listed. Narrow the pattern or path to see the rest.)`
|
|
86
|
-
: content;
|
|
87
96
|
}
|
|
88
97
|
//# sourceMappingURL=glob.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ModelToolCall, ModelToolDefinition, ToolExecutionContext, ToolExecutionResult, ToolRegistry } from '../core/runtime.js';
|
|
2
|
+
import { type GlobSearch } from './glob.js';
|
|
2
3
|
import type { DataPlane } from '../persistence/data-plane.js';
|
|
3
4
|
export interface LocalToolRegistryOptions {
|
|
4
5
|
cwd: string;
|
|
@@ -17,6 +18,7 @@ export interface LocalToolRegistryOptions {
|
|
|
17
18
|
sandbox?: BashSandboxRuntime;
|
|
18
19
|
homeDirectory?: string;
|
|
19
20
|
configRoot?: string;
|
|
21
|
+
globSearch?: GlobSearch;
|
|
20
22
|
}
|
|
21
23
|
export interface BashSandboxRuntime {
|
|
22
24
|
shouldUseSandbox(input: {
|
|
@@ -46,6 +48,7 @@ export declare class LocalToolRegistry implements ToolRegistry {
|
|
|
46
48
|
private readonly maxShellTimeoutMs;
|
|
47
49
|
private readonly maxSearchTimeoutMs;
|
|
48
50
|
private readonly processRunner;
|
|
51
|
+
private readonly globSearch;
|
|
49
52
|
private readonly enableReportFindings;
|
|
50
53
|
private readonly environment;
|
|
51
54
|
private readonly sessionEnvironment;
|
|
@@ -55,6 +58,7 @@ export declare class LocalToolRegistry implements ToolRegistry {
|
|
|
55
58
|
private readonly sessionCwds;
|
|
56
59
|
private readonly protectedWriteReason;
|
|
57
60
|
private readonly mutationTargetExisted;
|
|
61
|
+
private readonly preparedGlobRoots;
|
|
58
62
|
constructor(options: LocalToolRegistryOptions);
|
|
59
63
|
private assertProtectedWritePath;
|
|
60
64
|
private assertProtectedBashCommand;
|
|
@@ -7,7 +7,7 @@ import sharp from 'sharp';
|
|
|
7
7
|
import { countTokens } from '@anthropic-ai/tokenizer';
|
|
8
8
|
import { commandShell, commandShellArguments, } from '../platform/command-shell.js';
|
|
9
9
|
import { BoundedProcessRunner, joinedProcessOutput, } from '../platform/bounded-process-runner.js';
|
|
10
|
-
import {
|
|
10
|
+
import { RipgrepGlobSearch } from './glob.js';
|
|
11
11
|
import { countLineChanges } from './line-changes.js';
|
|
12
12
|
import { editNotebook, formatNotebookForRead } from './notebook.js';
|
|
13
13
|
import { openPdf } from './pdf.js';
|
|
@@ -537,6 +537,7 @@ export class LocalToolRegistry {
|
|
|
537
537
|
maxShellTimeoutMs;
|
|
538
538
|
maxSearchTimeoutMs;
|
|
539
539
|
processRunner;
|
|
540
|
+
globSearch;
|
|
540
541
|
enableReportFindings;
|
|
541
542
|
environment;
|
|
542
543
|
sessionEnvironment;
|
|
@@ -546,6 +547,7 @@ export class LocalToolRegistry {
|
|
|
546
547
|
sessionCwds = new Map();
|
|
547
548
|
protectedWriteReason;
|
|
548
549
|
mutationTargetExisted = new WeakMap();
|
|
550
|
+
preparedGlobRoots = new WeakMap();
|
|
549
551
|
constructor(options) {
|
|
550
552
|
this.cwd = resolve(options.cwd);
|
|
551
553
|
this.cwdProvider = options.cwdProvider;
|
|
@@ -576,6 +578,13 @@ export class LocalToolRegistry {
|
|
|
576
578
|
cwd: this.cwd,
|
|
577
579
|
maxOutputBytes: this.maxOutputBytes,
|
|
578
580
|
});
|
|
581
|
+
this.globSearch =
|
|
582
|
+
options.globSearch ??
|
|
583
|
+
new RipgrepGlobSearch({
|
|
584
|
+
cwd: this.cwd,
|
|
585
|
+
timeoutMs: this.maxSearchTimeoutMs,
|
|
586
|
+
...(this.environment ? { environment: this.environment } : {}),
|
|
587
|
+
});
|
|
579
588
|
}
|
|
580
589
|
assertProtectedWritePath(filePath) {
|
|
581
590
|
const reason = this.protectedWriteReason(filePath);
|
|
@@ -805,14 +814,16 @@ export class LocalToolRegistry {
|
|
|
805
814
|
case 'Glob': {
|
|
806
815
|
const pathInput = call.input.path;
|
|
807
816
|
const requestedPath = pathInput === undefined ? '.' : stringInput(call.input, 'path');
|
|
808
|
-
await this.globRoot(requestedPath, context);
|
|
809
|
-
|
|
817
|
+
const root = await this.globRoot(requestedPath, context);
|
|
818
|
+
const prepared = {
|
|
810
819
|
...call,
|
|
811
820
|
input: {
|
|
812
821
|
pattern: stringInput(call.input, 'pattern', true),
|
|
813
822
|
...(pathInput === undefined ? {} : { path: requestedPath }),
|
|
814
823
|
},
|
|
815
824
|
};
|
|
825
|
+
this.preparedGlobRoots.set(prepared, root);
|
|
826
|
+
return prepared;
|
|
816
827
|
}
|
|
817
828
|
case 'Grep': {
|
|
818
829
|
const glob = optionalString(call.input, 'glob');
|
|
@@ -857,6 +868,11 @@ export class LocalToolRegistry {
|
|
|
857
868
|
if (JSON.stringify(prepared.input) !== JSON.stringify(call.input)) {
|
|
858
869
|
throw new Error('Tool input changed after permission approval');
|
|
859
870
|
}
|
|
871
|
+
const approvedGlobRoot = this.preparedGlobRoots.get(call);
|
|
872
|
+
if (approvedGlobRoot !== undefined &&
|
|
873
|
+
this.preparedGlobRoots.get(prepared) !== approvedGlobRoot) {
|
|
874
|
+
throw new Error('Tool input changed after permission approval');
|
|
875
|
+
}
|
|
860
876
|
const executionTargetExisted = this.mutationTargetExisted.get(prepared);
|
|
861
877
|
if (approvedTargetExisted !== undefined &&
|
|
862
878
|
executionTargetExisted !== approvedTargetExisted) {
|
|
@@ -1466,37 +1482,21 @@ export class LocalToolRegistry {
|
|
|
1466
1482
|
}
|
|
1467
1483
|
async glob(call, context) {
|
|
1468
1484
|
const requestedPath = call.input.path === undefined ? '.' : stringInput(call.input, 'path');
|
|
1469
|
-
const root =
|
|
1470
|
-
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1473
|
-
:
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
return {
|
|
1485
|
-
content: truncateOutput(content, this.maxOutputBytes),
|
|
1486
|
-
isError: false,
|
|
1487
|
-
};
|
|
1488
|
-
}
|
|
1489
|
-
catch (error) {
|
|
1490
|
-
if (context.signal?.aborted)
|
|
1491
|
-
throw abortError();
|
|
1492
|
-
if (timeoutSignal.aborted) {
|
|
1493
|
-
return {
|
|
1494
|
-
content: `Search timed out after ${this.maxSearchTimeoutMs}ms`,
|
|
1495
|
-
isError: true,
|
|
1496
|
-
};
|
|
1497
|
-
}
|
|
1498
|
-
throw error;
|
|
1499
|
-
}
|
|
1485
|
+
const root = this.preparedGlobRoots.get(call) ??
|
|
1486
|
+
(await this.globRoot(requestedPath, context));
|
|
1487
|
+
const result = await this.globSearch.search({
|
|
1488
|
+
root,
|
|
1489
|
+
displayRoot: call.input.path === undefined ? '.' : requestedPath,
|
|
1490
|
+
absoluteRoot: isAbsolute(requestedPath)
|
|
1491
|
+
? resolve(requestedPath)
|
|
1492
|
+
: resolve(this.currentCwd(context), requestedPath),
|
|
1493
|
+
pattern: stringInput(call.input, 'pattern', true),
|
|
1494
|
+
...(context.signal ? { signal: context.signal } : {}),
|
|
1495
|
+
});
|
|
1496
|
+
return {
|
|
1497
|
+
content: truncateOutput(result.content, this.maxOutputBytes),
|
|
1498
|
+
isError: result.isError,
|
|
1499
|
+
};
|
|
1500
1500
|
}
|
|
1501
1501
|
async grep(call, context) {
|
|
1502
1502
|
const workspaceRoot = await realpath(this.currentCwd(context));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "praxis-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"description": "Local-first, single-user general agent for the command line.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "wuqisen",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"verify:ci-coverage": "node scripts/verify-ci-coverage.mjs",
|
|
63
63
|
"verify:fixture-contracts": "node scripts/verify-fixture-contracts.mjs",
|
|
64
64
|
"test:fixtures": "node scripts/run-fixture-contracts.mjs",
|
|
65
|
-
"test:eval:baseline": "vitest run src/evals/coding-baseline.test.ts src/evals/project-eval-comparison.test.ts src/evals/apply-patch-admission.test.ts src/evals/lsp-diagnostics-admission.test.ts"
|
|
65
|
+
"test:eval:baseline": "vitest run src/evals/coding-baseline.test.ts src/evals/project-eval-comparison.test.ts src/evals/apply-patch-admission.test.ts src/evals/lsp-diagnostics-admission.test.ts src/evals/glob-ripgrep-admission.test.ts"
|
|
66
66
|
},
|
|
67
67
|
"engines": {
|
|
68
68
|
"node": ">=24"
|