Skip to content

Commit 958e2d1

Browse files
committed
Generate diff markdown for agentic CLI edit-file runs instead of prompting for edit format
- the 'edit files' cli prompt should not have 'output formatting' instructions, instead it should edit files (in the temp directory) by itself and afterwards, the system should scan the temp dir and compile markdown text adhering to our EDIT_FORMAT_INSTRUCTIONS_DIFF - in 'edit files', agent sometimes output also some text to the user - is it captured an dincluded as plain text on top of the diff markdown? - when including plain text [Pasted text], strip markdown links - diff markdown-constructing logic should be extracted to a local util - as we replace markdown links, wrap resulting text in backticks - agent sometimes repeat changes to the user, e.g. [Pasted text] - in such case we're only interested in "[Pasted text]" └── apps └── editor └── src ├── constants │ ├── edit-format-instructions.ts │ └── instructions.ts ├── utils │ └── agentic-cli-invocation │ ├── agents │ │ ├── antigravity.ts │ │ ├── claude.ts │ │ ├── codex.ts │ │ ├── cursor.ts │ │ ├── grok.ts │ │ ├── index.ts │ │ ├── muse.ts │ │ └── opencode.ts │ ├── index.ts │ ├── invoke-agentic-cli.ts │ ├── types.ts │ └── utils │ ├── check-command-exists.ts │ ├── format-duration.ts │ ├── generate-diff-markdown.ts │ └── get-progress-dots.ts └── views └── prompt └── backend └── message-handlers ├── handle-invoke-agentic-cli.ts └── utils └── build-cli-prompt.ts
1 parent 0f2aad2 commit 958e2d1

6 files changed

Lines changed: 166 additions & 38 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,9 @@ Whenever showing a new, updated, renamed, or deleted file, provide a brief expla
170170
171171
[SELECTED FILES]
172172
173-
# Output formatting
174-
175-
[EDIT FORMAT INSTRUCTIONS]
176-
177173
# Requirements
178174
179-
* This is a read-only session.
180-
* Read all the listed files provided as project-relative paths in full, and nothing else.
181-
* Formulate response adhering to the output formatting, in plain text.
175+
Before you proceed with [the task](#task), you must read all files provided as project-relative paths in [files](#files), and nothing else.
182176
183177
# Task
184178
@@ -216,7 +210,7 @@ _Multi-purpose: explanations, code reviews, you name it._
216210
217211
# Requirements
218212
219-
Tool calling is disabled with exception to reading files provided as project-relative paths...
213+
Before you proceed with [the task](#task), you must read all files provided as project-relative paths in [files](#files), and nothing else.
220214
221215
# Task
222216

‎apps/editor/src/constants/instructions.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ These files contain the core greeting logic and module exports.`
6969
export const voice_input_instructions =
7070
'Respond with a transcription of the following audio recording or text "INAUDIBLE", and nothing else.'
7171

72-
export const cli_requirements = {
72+
export const cli_edit_ask_requirements = {
7373
preloaded_files:
74-
'Tool calling is disabled with exception to media/web access in [task](#task).',
74+
'All files necessary for [the task](#task) are listed in [files](#files).',
7575
referenced_files_with_some_preloaded:
76-
'Tool calling is disabled with exception to reading large files provided as project-relative paths in [files](#files) and media/web access in [task](#task). You must begin by reading large files listed in [files](#files) in full.',
76+
'Before you proceed with [the task](#task), you must read all \'large files\' listed as project-relative paths in [files](#files), and nothing else.',
7777
referenced_files_only:
78-
'Tool calling is disabled with exception to reading files provided as project-relative paths in [files](#files) and media/web access in [task](#task). You must begin by reading files listed in [files](#files) in full.'
78+
'Before you proceed with [the task](#task), you must read all files provided as project-relative paths in [files](#files), and nothing else.'
7979
}

‎apps/editor/src/utils/agentic-cli-invocation/invoke-agentic-cli.ts‎

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { t } from '@/i18n'
77
import { spawn } from 'child_process'
88
import { CLI_AGENTS } from './agents'
99
import { Logger } from '@shared/utils/logger'
10+
import { generate_diff_markdown } from './utils/generate-diff-markdown'
1011
import { ConfigAgentConfigurationFormat } from '@/utils/cli-configuration-format-converters'
1112
import { AGENTS } from '@/constants/agents'
1213

@@ -300,9 +301,11 @@ export const invoke_agentic_cli = async (params: {
300301

301302
while (true) {
302303
let selected_root: string | undefined
304+
let temp_dir_path: string | undefined
305+
const file_mappings = new Map<string, { original: string; dest_rel: string }>()
303306

304307
if (params.panel_prompt_type) {
305-
const temp_dir_path = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'cwc-cli-'))
308+
temp_dir_path = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'cwc-cli-'))
306309
const checked_files = params.workspace_provider.get_checked_files()
307310

308311
for (const file_path of checked_files) {
@@ -314,6 +317,8 @@ export const invoke_agentic_cli = async (params: {
314317

315318
await fs.promises.mkdir(path.dirname(dest_path), { recursive: true })
316319
await fs.promises.copyFile(file_path, dest_path)
320+
321+
file_mappings.set(dest_path, { original: file_path, dest_rel: dest_rel.replace(/\\/g, '/') })
317322
}
318323

319324
selected_root = temp_dir_path
@@ -539,7 +544,7 @@ export const invoke_agentic_cli = async (params: {
539544
resolve()
540545
})
541546

542-
child.on('close', () => {
547+
child.on('close', async () => {
543548
if (agent_info.parse_final_output && raw_stream_output.trim()) {
544549
try {
545550
const parsed = JSON.parse(raw_stream_output.trim())
@@ -550,6 +555,35 @@ export const invoke_agentic_cli = async (params: {
550555
// Ignore
551556
}
552557
}
558+
559+
if (params.panel_prompt_type == 'edit-files' && temp_dir_path) {
560+
try {
561+
const diff_markdown = await generate_diff_markdown(
562+
temp_dir_path,
563+
file_mappings
564+
)
565+
566+
const trimmed_output = agent_output
567+
.trim()
568+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '`$1`')
569+
.replace(/```[\s\S]*?```/g, '')
570+
.trim()
571+
const trimmed_diff = diff_markdown.trim()
572+
573+
if (trimmed_output && trimmed_diff) {
574+
agent_output = trimmed_output + '\n\n' + trimmed_diff
575+
} else {
576+
agent_output = trimmed_diff || trimmed_output
577+
}
578+
} catch (e) {
579+
Logger.error({
580+
function_name: 'invoke_agentic_cli',
581+
message: 'Failed to generate diff',
582+
data: e
583+
})
584+
}
585+
}
586+
553587
Logger.info({
554588
function_name: 'invoke_agentic_cli',
555589
message: "Agent's response",
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import * as path from 'path'
2+
import * as fs from 'fs'
3+
import { promisify } from 'util'
4+
import { execFile } from 'child_process'
5+
6+
const execFileAsync = promisify(execFile)
7+
8+
async function get_all_files(dir: string): Promise<string[]> {
9+
const dirents = await fs.promises.readdir(dir, { withFileTypes: true })
10+
const files = await Promise.all(
11+
dirents.map(async (dirent) => {
12+
const res = path.resolve(dir, dirent.name)
13+
return dirent.isDirectory() ? get_all_files(res) : res
14+
})
15+
)
16+
return Array.prototype.concat(...files) as string[]
17+
}
18+
19+
export const generate_diff_markdown = async (
20+
temp_dir_path: string,
21+
file_mappings: Map<string, { original: string; dest_rel: string }>
22+
): Promise<string> => {
23+
let diff_markdown = ''
24+
const temp_files = await get_all_files(temp_dir_path)
25+
26+
for (const temp_file of temp_files) {
27+
const rel_path = path.relative(temp_dir_path, temp_file)
28+
const unix_rel_path = rel_path.replace(/\\/g, '/')
29+
30+
const temp_file_resolved = path.resolve(temp_file)
31+
let mapping = file_mappings.get(temp_file)
32+
if (!mapping) {
33+
for (const [dest_path, map] of file_mappings.entries()) {
34+
if (path.resolve(dest_path) === temp_file_resolved) {
35+
mapping = map
36+
break
37+
}
38+
}
39+
}
40+
41+
if (mapping) {
42+
const original_content = await fs.promises.readFile(
43+
mapping.original,
44+
'utf-8'
45+
)
46+
const new_content = await fs.promises.readFile(temp_file, 'utf-8')
47+
48+
if (original_content !== new_content) {
49+
try {
50+
await execFileAsync('git', [
51+
'diff',
52+
'--no-index',
53+
mapping.original,
54+
temp_file
55+
])
56+
} catch (err: any) {
57+
if (err.code === 1 && err.stdout) {
58+
const stdout: string = err.stdout
59+
const diff_lines = stdout.split('\n')
60+
const at_at_index = diff_lines.findIndex((l) =>
61+
l.startsWith('@@ ')
62+
)
63+
if (at_at_index !== -1) {
64+
const diff_body = diff_lines.slice(at_at_index).join('\n')
65+
diff_markdown +=
66+
'\n### Updated file: `' +
67+
unix_rel_path +
68+
'`\n\n```diff\n--- a/' +
69+
unix_rel_path +
70+
'\n+++ b/' +
71+
unix_rel_path +
72+
'\n' +
73+
diff_body +
74+
(diff_body.endsWith('\n') ? '' : '\n') +
75+
'```\n'
76+
} else {
77+
diff_markdown +=
78+
'\n### Updated file: `' +
79+
unix_rel_path +
80+
'`\n\n```\n' +
81+
new_content +
82+
(new_content.endsWith('\n') ? '' : '\n') +
83+
'```\n'
84+
}
85+
} else {
86+
diff_markdown +=
87+
'\n### Updated file: `' +
88+
unix_rel_path +
89+
'`\n\n```\n' +
90+
new_content +
91+
(new_content.endsWith('\n') ? '' : '\n') +
92+
'```\n'
93+
}
94+
}
95+
}
96+
} else {
97+
const new_content = await fs.promises.readFile(temp_file, 'utf-8')
98+
diff_markdown +=
99+
'\n### Created file: `' +
100+
unix_rel_path +
101+
'`\n\n```\n' +
102+
new_content +
103+
(new_content.endsWith('\n') ? '' : '\n') +
104+
'```\n'
105+
}
106+
}
107+
108+
for (const [dest_path, mapping] of file_mappings.entries()) {
109+
const normalized_dest = path.resolve(dest_path)
110+
const is_kept = temp_files.some(
111+
(t) => path.resolve(t) === normalized_dest
112+
)
113+
if (!is_kept) {
114+
diff_markdown += '\n### Deleted file: `' + mapping.dest_rel + '`\n'
115+
}
116+
}
117+
118+
return diff_markdown
119+
}

‎apps/editor/src/views/prompt/backend/message-handlers/handle-invoke-agentic-cli.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ export const handle_invoke_agentic_cli = async (
7777
vscode.commands.executeCommand('codeWebChat.applyResponse', {
7878
response: agent_output,
7979
raw_instructions: current_instructions,
80-
edit_format: prompt_view_provider.edit_format
8180
})
8281
}
8382
}

‎apps/editor/src/views/prompt/backend/message-handlers/utils/build-cli-prompt.ts‎

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@ import * as path from 'path'
22
import * as fs from 'fs/promises'
33
import { PromptViewProvider } from '@/views/prompt/backend/prompt-view-provider'
44
import { replace_symbols } from '@/views/prompt/backend/utils/symbols/replace-symbols'
5-
import { cli_requirements } from '@/constants/instructions'
6-
import {
7-
EDIT_FORMAT_INSTRUCTIONS_DIFF,
8-
EDIT_FORMAT_INSTRUCTIONS_SEARCH_REPLACE,
9-
EDIT_FORMAT_INSTRUCTIONS_TRUNCATED,
10-
EDIT_FORMAT_INSTRUCTIONS_WHOLE
11-
} from '@/constants/edit-format-instructions'
5+
import { cli_edit_ask_requirements } from '@/constants/instructions'
126
import { LAST_SELECTED_WORKSPACE_IN_AGENTIC_CLI_STATE_KEY } from '@/constants/state-keys'
137

148
export const build_cli_prompt = async (params: {
@@ -110,25 +104,13 @@ export const build_cli_prompt = async (params: {
110104
files_section = `# Files\n\n${file_blocks.join('\n\n')}`
111105
}
112106

113-
let output_formatting_section = ''
114-
if (prompt_view_provider.cli_prompt_type == 'edit-files') {
115-
const edit_format_instructions = {
116-
whole: EDIT_FORMAT_INSTRUCTIONS_WHOLE,
117-
truncated: EDIT_FORMAT_INSTRUCTIONS_TRUNCATED,
118-
'search-replace': EDIT_FORMAT_INSTRUCTIONS_SEARCH_REPLACE,
119-
diff: EDIT_FORMAT_INSTRUCTIONS_DIFF
120-
}[prompt_view_provider.edit_format]
121-
122-
if (edit_format_instructions) {
123-
output_formatting_section = `# Output formatting\n\n${edit_format_instructions}`
124-
}
125-
}
107+
const output_formatting_section = ''
126108

127109
const requirement = are_all_files_preloaded
128-
? cli_requirements.preloaded_files
110+
? cli_edit_ask_requirements.preloaded_files
129111
: are_any_files_preloaded
130-
? cli_requirements.referenced_files_with_some_preloaded
131-
: cli_requirements.referenced_files_only
112+
? cli_edit_ask_requirements.referenced_files_with_some_preloaded
113+
: cli_edit_ask_requirements.referenced_files_only
132114

133115
const requirements_section = `# Requirements\n\n${requirement}`
134116
const task_section = `# Task\n\n${processed_query}`

0 commit comments

Comments
 (0)