Skip to content

Commit 491b3e5

Browse files
authored
fix: pi-ai output bugs and add agentic tool use (#52)
1 parent 89dc01a commit 491b3e5

7 files changed

Lines changed: 298 additions & 128 deletions

File tree

src/agent/clis/index.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,30 @@ export function createToolProgress(log: ToolProgressLog): (progress: StreamProgr
7070
}
7171
}
7272

73-
return ({ type, chunk, section }) => {
73+
return ({ type, chunk, text, section }) => {
7474
if (type === 'text') {
7575
const key = section ?? ''
7676
const now = Date.now()
7777
const last = lastTextEmit.get(key) ?? 0
7878
if (now - last < TEXT_THROTTLE_MS)
7979
return
8080
lastTextEmit.set(key, now)
81-
emit(`${section ? `\x1B[90m[${section}]\x1B[0m ` : ''}Writing...`)
81+
const prefix = section ? `\x1B[90m[${section}]\x1B[0m ` : ''
82+
// Count bullet items in accumulated text for meaningful progress
83+
const items = text ? (text.match(/^- (?:BREAKING|DEPRECATED|NEW|CHANGED|REMOVED|Use |Do |Set |Add |Avoid |Always |Never |Prefer |Check |Ensure )/gm)?.length ?? 0) : 0
84+
emit(items > 0 ? `${prefix}Writing... \x1B[90m(${items} items)\x1B[0m` : `${prefix}Writing...`)
8285
return
8386
}
8487
if (type !== 'reasoning' || !chunk.startsWith('['))
8588
return
8689

90+
// Handle status messages like [starting...], [retrying...], [cached]
91+
if (/^\[(?:starting|retrying|cached)/.test(chunk)) {
92+
const prefix = section ? `\x1B[90m[${section}]\x1B[0m ` : ''
93+
emit(`${prefix}${chunk.slice(1, -1)}`)
94+
return
95+
}
96+
8797
// Parse individual tool names and hints from "[Read: path]" or "[Read, Glob: path1, path2]"
8898
const match = chunk.match(/^\[([^:[\]]+)(?::\s(.+))?\]$/)
8999
if (!match)
@@ -322,6 +332,12 @@ async function optimizeSectionViaPiAi(opts: {
322332
const { section, prompt, outputFile, skillDir, model, onProgress, timeout, debug } = opts
323333
const skilldDir = join(skillDir, '.skilld')
324334
const outputPath = join(skilldDir, outputFile)
335+
const logsDir = join(skilldDir, 'logs')
336+
const logName = section.toUpperCase().replace(/-/g, '_')
337+
338+
// Remove stale output so we don't read a leftover from a previous run
339+
if (existsSync(outputPath))
340+
unlinkSync(outputPath)
325341

326342
// Write prompt for debugging (same as CLI path)
327343
writeFileSync(join(skilldDir, `PROMPT_${section}.md`), prompt)
@@ -334,11 +350,10 @@ async function optimizeSectionViaPiAi(opts: {
334350

335351
const raw = result.text.trim()
336352

337-
// Debug logging
353+
// Debug logging — match CLI path: actual prompt sent + raw output
338354
if (debug) {
339-
const logsDir = join(skilldDir, 'logs')
340-
const logName = section.toUpperCase().replace(/-/g, '_')
341355
mkdirSync(logsDir, { recursive: true })
356+
writeFileSync(join(skilldDir, `PROMPT_${section}.md`), result.fullPrompt)
342357
if (raw)
343358
writeFileSync(join(logsDir, `${logName}.md`), raw)
344359
}
@@ -349,6 +364,7 @@ async function optimizeSectionViaPiAi(opts: {
349364

350365
const content = cleanSectionOutput(raw)
351366

367+
// Always write cleaned content to output file (matches CLI path)
352368
if (content)
353369
writeFileSync(outputPath, content)
354370

@@ -366,7 +382,13 @@ async function optimizeSectionViaPiAi(opts: {
366382
}
367383
}
368384
catch (err) {
369-
return { section, content: '', wasOptimized: false, error: (err as Error).message }
385+
// Write error to logs on failure (matches CLI stderr logging)
386+
const errMsg = (err as Error).message
387+
if (debug || errMsg) {
388+
mkdirSync(logsDir, { recursive: true })
389+
writeFileSync(join(logsDir, `${logName}.stderr.log`), errMsg)
390+
}
391+
return { section, content: '', wasOptimized: false, error: errMsg }
370392
}
371393
}
372394

@@ -822,15 +844,11 @@ export function cleanSectionOutput(content: string): string {
822844
}
823845
}
824846

825-
// Strip raw code preamble before first section marker (defense against LLMs dumping source)
847+
// Strip preamble before first section marker (LLM reasoning, fake tool calls, code dumps)
826848
// Section markers: ## heading, BREAKING/DEPRECATED/NEW labels
827849
const firstMarker = cleaned.match(/^(##\s|- (?:BREAKING|DEPRECATED|NEW): )/m)
828850
if (firstMarker?.index && firstMarker.index > 0) {
829-
const preamble = cleaned.slice(0, firstMarker.index)
830-
// Only strip if preamble looks like code (contains function/const/export/return patterns)
831-
if (/\b(?:function|const |let |var |export |return |import |async |class )\b/.test(preamble)) {
832-
cleaned = cleaned.slice(firstMarker.index).trim()
833-
}
851+
cleaned = cleaned.slice(firstMarker.index).trim()
834852
}
835853

836854
// Strip duplicate section headings (LLM echoing the format example before real content)

0 commit comments

Comments
 (0)