pi-code 1.0.47 → 1.0.49
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 +3 -2
- package/extensions/claude-rules.ts +2 -1
- package/extensions/commands.ts +2 -2
- package/extensions/context-imports.ts +13 -12
- package/extensions/git-checkpoint.ts +26 -0
- package/extensions/goal.ts +522 -0
- package/extensions/hooks/index.ts +2 -7
- package/extensions/internal/command-file.ts +22 -11
- package/extensions/internal/goal-evaluator.ts +262 -0
- package/extensions/internal/html-markdown.ts +37 -9
- package/extensions/internal/model-lookup.ts +20 -0
- package/extensions/internal/output-guard.ts +6 -5
- package/extensions/internal/strip-comments.ts +3 -3
- package/extensions/memory.ts +26 -1
- package/extensions/plan-mode/index.ts +3 -1
- package/extensions/question.ts +1 -1
- package/extensions/subagent/background.ts +33 -10
- package/extensions/todo.ts +6 -2
- package/extensions/web.ts +2 -6
- package/package.json +2 -2
|
@@ -24,6 +24,8 @@ export interface BackgroundRun {
|
|
|
24
24
|
stderr?: string
|
|
25
25
|
/** Claude's partial marker: the run stopped at its maxTurns limit. */
|
|
26
26
|
partial?: boolean
|
|
27
|
+
/** Temp dir holding a prompt file rebuilt for a resume; removed with the run. */
|
|
28
|
+
rebuiltPromptDir?: string
|
|
27
29
|
/** Set while running so the run can be cancelled; cleared on completion. */
|
|
28
30
|
kill?: () => void
|
|
29
31
|
/** True until the child process actually closes: a cancelled child that ignores
|
|
@@ -65,7 +67,7 @@ export const MAX_FINISHED_RUNS = 20
|
|
|
65
67
|
/** Grace between the cancel SIGTERM and the SIGKILL that ends a child ignoring it. */
|
|
66
68
|
const CANCEL_KILL_GRACE_MS = 5000
|
|
67
69
|
|
|
68
|
-
/**
|
|
70
|
+
/** Characters of stderr kept per run, enough for the boot error without buffering logs. */
|
|
69
71
|
const STDERR_TAIL_CHARS = 2048
|
|
70
72
|
|
|
71
73
|
export function activeBackgroundRuns(): number {
|
|
@@ -79,6 +81,7 @@ let finishSequence = 0
|
|
|
79
81
|
/** Test seam: the registry is module state, so tests reset it between cases to
|
|
80
82
|
* stay order-independent. */
|
|
81
83
|
export function resetBackgroundRuns(): void {
|
|
84
|
+
for (const run of runs.values()) removeRebuiltPrompt(run)
|
|
82
85
|
runs.clear()
|
|
83
86
|
finishSequence = 0
|
|
84
87
|
}
|
|
@@ -86,7 +89,10 @@ export function resetBackgroundRuns(): void {
|
|
|
86
89
|
function evictFinishedRuns(): void {
|
|
87
90
|
const finished = [...runs.values()].filter((run) => !run.live && run.state !== 'running')
|
|
88
91
|
finished.sort((a, b) => (a.finishedAt ?? 0) - (b.finishedAt ?? 0))
|
|
89
|
-
for (const stale of finished.slice(0, Math.max(0, finished.length - MAX_FINISHED_RUNS)))
|
|
92
|
+
for (const stale of finished.slice(0, Math.max(0, finished.length - MAX_FINISHED_RUNS))) {
|
|
93
|
+
removeRebuiltPrompt(stale)
|
|
94
|
+
runs.delete(stale.id)
|
|
95
|
+
}
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
/** Line-by-line parser keeping only the last assistant text and a turn count, so a
|
|
@@ -162,6 +168,8 @@ export function cancelAllBackgroundRuns(): number {
|
|
|
162
168
|
for (const id of Array.from(runs.keys())) {
|
|
163
169
|
if (cancelBackgroundRun(id) === 'cancelled') count++
|
|
164
170
|
}
|
|
171
|
+
// Called at quit: nothing in this registry is resumable once pi exits.
|
|
172
|
+
for (const run of runs.values()) removeRebuiltPrompt(run)
|
|
165
173
|
return count
|
|
166
174
|
}
|
|
167
175
|
|
|
@@ -190,38 +198,53 @@ export function resumeBackgroundRun(id: string, task: string, onComplete: (run:
|
|
|
190
198
|
// Persisted so the rebuild happens once: rebuilding per resume leaked one temp
|
|
191
199
|
// prompt dir every follow-up.
|
|
192
200
|
const rebuilt = withRebuiltPrompt(run.spawn)
|
|
193
|
-
run.spawn = { ...run.spawn, args: rebuilt }
|
|
194
|
-
|
|
201
|
+
run.spawn = { ...run.spawn, args: rebuilt.args }
|
|
202
|
+
if (rebuilt.dir) run.rebuiltPromptDir = rebuilt.dir
|
|
203
|
+
const args = rebuilt.args.map((arg) => (arg.startsWith('Task: ') ? `Task: ${task}` : arg))
|
|
195
204
|
run.state = 'running'
|
|
196
205
|
run.task = task
|
|
197
206
|
run.output = undefined
|
|
198
207
|
run.exitCode = undefined
|
|
199
208
|
run.stderr = undefined
|
|
200
209
|
run.finishedAt = undefined
|
|
210
|
+
// Both belong to the child that ran: a resume that dies before its first turn would
|
|
211
|
+
// otherwise report the previous count, and a clean follow-up to a maxTurns-capped run
|
|
212
|
+
// would still be offered as partial.
|
|
213
|
+
run.turns = 0
|
|
214
|
+
run.partial = undefined
|
|
201
215
|
driveRun(run, { ...run.spawn, args }, onComplete)
|
|
202
216
|
return 'resumed'
|
|
203
217
|
}
|
|
204
218
|
|
|
205
|
-
/** Re-point --system-prompt at a fresh file when the original is gone
|
|
206
|
-
|
|
219
|
+
/** Re-point --system-prompt at a fresh file when the original is gone; `dir` is the
|
|
220
|
+
* temp dir created for it, which the run then owns. */
|
|
221
|
+
function withRebuiltPrompt(spawnSpec: BackgroundSpawn): { args: string[]; dir?: string } {
|
|
207
222
|
const flag = spawnSpec.args.indexOf('--system-prompt')
|
|
208
|
-
if (flag === -1 || !spawnSpec.promptBody) return spawnSpec.args
|
|
223
|
+
if (flag === -1 || !spawnSpec.promptBody) return { args: spawnSpec.args }
|
|
209
224
|
const current = spawnSpec.args[flag + 1]
|
|
210
|
-
if (current && fs.existsSync(current)) return spawnSpec.args
|
|
225
|
+
if (current && fs.existsSync(current)) return { args: spawnSpec.args }
|
|
211
226
|
try {
|
|
212
227
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pi-subagent-'))
|
|
213
228
|
const file = path.join(dir, 'prompt.md')
|
|
214
229
|
fs.writeFileSync(file, spawnSpec.promptBody, { mode: 0o600 })
|
|
215
230
|
const rebuilt = [...spawnSpec.args]
|
|
216
231
|
rebuilt[flag + 1] = file
|
|
217
|
-
return rebuilt
|
|
232
|
+
return { args: rebuilt, dir }
|
|
218
233
|
} catch {
|
|
219
234
|
// Cannot rewrite it: drop the pair rather than hand pi a path it will treat as
|
|
220
235
|
// prompt text, which would replace the agent persona with a temp path.
|
|
221
|
-
return spawnSpec.args.filter((_arg, i) => i !== flag && i !== flag + 1)
|
|
236
|
+
return { args: spawnSpec.args.filter((_arg, i) => i !== flag && i !== flag + 1) }
|
|
222
237
|
}
|
|
223
238
|
}
|
|
224
239
|
|
|
240
|
+
/** The rebuilt prompt lives as long as its run can be resumed, so it goes when the run
|
|
241
|
+
* leaves the registry: eviction, quit, or the test reset. */
|
|
242
|
+
function removeRebuiltPrompt(run: BackgroundRun): void {
|
|
243
|
+
if (!run.rebuiltPromptDir) return
|
|
244
|
+
fs.rmSync(run.rebuiltPromptDir, { recursive: true, force: true })
|
|
245
|
+
run.rebuiltPromptDir = undefined
|
|
246
|
+
}
|
|
247
|
+
|
|
225
248
|
export function startBackgroundRun(agent: string, task: string, invocation: BackgroundSpawn, onComplete: (run: BackgroundRun) => void, presetId?: string): string | null {
|
|
226
249
|
// Checked here, synchronously with registration: callers await temp-file writes
|
|
227
250
|
// between any check of their own and this call, so a parallel tool-call batch
|
package/extensions/todo.ts
CHANGED
|
@@ -387,8 +387,12 @@ export default function todoExtension(pi: ExtensionAPI) {
|
|
|
387
387
|
content: [{ type: 'text' as const, text }],
|
|
388
388
|
details,
|
|
389
389
|
})
|
|
390
|
-
|
|
391
|
-
|
|
390
|
+
// pi keeps a result's details by reference in the live session, and start/complete
|
|
391
|
+
// mutate the todo objects in place, so each result carries its own copies: without
|
|
392
|
+
// them a rewind replays, and the TUI re-renders, today's statuses on earlier calls.
|
|
393
|
+
const snapshot = () => todos.map((t) => ({ ...t }))
|
|
394
|
+
const ok = (action: TodoAction, text: string) => toolMessage(text, { action, todos: snapshot(), nextId })
|
|
395
|
+
const fail = (action: TodoAction, error: string) => toolMessage(`Error: ${error}`, { action, todos: snapshot(), nextId, error })
|
|
392
396
|
|
|
393
397
|
const handleAdd = (params: TodoParamsType) => {
|
|
394
398
|
if (!params.text) return fail('add', 'text required for add')
|
package/extensions/web.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { Usage } from '@earendil-works/pi-ai'
|
|
|
13
13
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'
|
|
14
14
|
import { Type } from 'typebox'
|
|
15
15
|
|
|
16
|
-
import { htmlToMarkdown } from './internal/html-markdown.js'
|
|
16
|
+
import { htmlToMarkdown, removeTags } from './internal/html-markdown.js'
|
|
17
17
|
import { completeText } from './internal/model-complete.js'
|
|
18
18
|
import { capForContext } from './internal/output-guard.js'
|
|
19
19
|
import { httpFetch } from './internal/web-transport.js'
|
|
@@ -40,11 +40,7 @@ export function decodeEntities(text: string): string {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function stripTags(html: string): string {
|
|
43
|
-
|
|
44
|
-
// instead of rescanning to end of input, which is what makes the strip linear.
|
|
45
|
-
return decodeEntities(html.replace(/<[^<>]*>/g, ''))
|
|
46
|
-
.replace(/\s+/g, ' ')
|
|
47
|
-
.trim()
|
|
43
|
+
return decodeEntities(removeTags(html)).replace(/\s+/g, ' ').trim()
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
/** Resolve DuckDuckGo's redirect links (/l/?uddg=<encoded>) to the target URL. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-code",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Claude Code experience for the pi coding agent: reads your .claude config (rules, commands, skills, hooks, output styles, MCP servers, agents) and adds todo, checkpoints, memory, web, and
|
|
3
|
+
"version": "1.0.49",
|
|
4
|
+
"description": "Claude Code experience for the pi coding agent: reads your .claude config (rules, commands, skills, hooks, output styles, MCP servers, agents) and adds todo, checkpoints, memory, web, subagents, and goals",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
7
7
|
"pi-package",
|