pi-code 1.0.63 → 1.0.65

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.
Files changed (50) hide show
  1. package/README.md +1 -1
  2. package/extensions/commands.ts +9 -6
  3. package/extensions/context-imports.ts +3 -3
  4. package/extensions/env-settings.ts +5 -2
  5. package/extensions/git-checkpoint.ts +137 -21
  6. package/extensions/hooks/config.ts +16 -10
  7. package/extensions/hooks/decisions.ts +9 -9
  8. package/extensions/hooks/index.ts +22 -11
  9. package/extensions/hooks/matcher.ts +12 -7
  10. package/extensions/hooks/runners.ts +7 -28
  11. package/extensions/internal/agent-run.ts +1 -1
  12. package/extensions/internal/bash-rules.ts +1 -2
  13. package/extensions/internal/claude-tool-names.ts +4 -7
  14. package/extensions/internal/command-file.ts +2 -3
  15. package/extensions/internal/external-imports.ts +4 -5
  16. package/extensions/internal/goal-evaluator.ts +4 -3
  17. package/extensions/internal/instruction-events.ts +5 -5
  18. package/extensions/internal/managed-settings.ts +1 -1
  19. package/extensions/internal/mcp-oauth.ts +13 -3
  20. package/extensions/internal/model-complete.ts +7 -1
  21. package/extensions/internal/model-lookup.ts +10 -3
  22. package/extensions/internal/path-rules.ts +10 -13
  23. package/extensions/internal/plugins.ts +5 -5
  24. package/extensions/internal/process-tree.ts +39 -0
  25. package/extensions/internal/project-approval.ts +7 -7
  26. package/extensions/internal/project-root.ts +13 -13
  27. package/extensions/internal/settings-chain.ts +19 -0
  28. package/extensions/internal/settings-watch.ts +3 -1
  29. package/extensions/internal/shell-resolve.ts +3 -1
  30. package/extensions/internal/tool-target.ts +4 -5
  31. package/extensions/internal/values.ts +20 -4
  32. package/extensions/mcp/config.ts +11 -4
  33. package/extensions/mcp/index.ts +40 -4
  34. package/extensions/mcp/policy.ts +14 -12
  35. package/extensions/mcp/transport.ts +2 -8
  36. package/extensions/memory.ts +4 -4
  37. package/extensions/notify.ts +2 -7
  38. package/extensions/output-styles.ts +22 -17
  39. package/extensions/question.ts +2 -7
  40. package/extensions/skills.ts +5 -5
  41. package/extensions/status-line.ts +4 -0
  42. package/extensions/subagent/agents.ts +4 -4
  43. package/extensions/subagent/background.ts +35 -41
  44. package/extensions/subagent/child.ts +2 -2
  45. package/extensions/subagent/modes.ts +18 -13
  46. package/extensions/subagent/params.ts +1 -2
  47. package/extensions/subagent/run.ts +3 -14
  48. package/extensions/thinking.ts +3 -3
  49. package/extensions/web.ts +3 -4
  50. package/package.json +7 -7
@@ -152,18 +152,6 @@ export async function runBackgroundMode(params: SubagentParamsStatic, context: B
152
152
  return backgroundCapResult(makeDetails)
153
153
  }
154
154
  const runCwd = params.cwd ?? defaultCwd
155
- // The same isolation boundary as the foreground path: no worktree, no run.
156
- let worktree: AgentWorktree | undefined
157
- if (agent.isolation === 'worktree') {
158
- const created = await createAgentWorktree(runCwd, agent.name)
159
- if ('error' in created) {
160
- return {
161
- content: [{ type: 'text', text: `isolation: worktree could not be created for ${agent.name}: ${created.error}` }],
162
- details: makeDetails('single')([]),
163
- }
164
- }
165
- worktree = created
166
- }
167
155
  // Anchor project/local memory at the session project (defaultCwd), which is the one
168
156
  // projectApproved gated; see the foreground path for why runCwd must not be used.
169
157
  const memorySection = agentMemoryPromptSection(agent, defaultCwd, projectApproved)
@@ -176,6 +164,21 @@ export async function runBackgroundMode(params: SubagentParamsStatic, context: B
176
164
  // foreground path).
177
165
  args.push('--system-prompt', tmpPrompt.filePath)
178
166
  }
167
+ // The same isolation boundary as the foreground path: no worktree, no run. Created
168
+ // after the temp prompt, so a prompt write that throws has nothing to leak; the cap
169
+ // refusal below removes it again.
170
+ let worktree: AgentWorktree | undefined
171
+ if (agent.isolation === 'worktree') {
172
+ const created = await createAgentWorktree(runCwd, agent.name)
173
+ if ('error' in created) {
174
+ removeTmpPrompt(tmpPrompt)
175
+ return {
176
+ content: [{ type: 'text', text: `isolation: worktree could not be created for ${agent.name}: ${created.error}` }],
177
+ details: makeDetails('single')([]),
178
+ }
179
+ }
180
+ worktree = created
181
+ }
179
182
  // The id is preset so SubagentStart hooks run pre-spawn with the id the run
180
183
  // will actually carry, and their context lands before the child's first prompt.
181
184
  const presetId = `bg-${randomUUID().slice(0, 8)}`
@@ -216,8 +219,10 @@ export async function runBackgroundMode(params: SubagentParamsStatic, context: B
216
219
  presetId,
217
220
  )
218
221
  if (id === null) {
219
- // Lost the cap race to a parallel batch: the atomic check inside startBackgroundRun refused.
222
+ // Lost the cap race to a parallel batch: the atomic check inside startBackgroundRun
223
+ // refused. A pristine worktree is removed by the same cleanup a finished run uses.
220
224
  removeTmpPrompt(tmpPrompt)
225
+ if (worktree) await cleanupAgentWorktree(runCwd, worktree).catch(() => {})
221
226
  return backgroundCapResult(makeDetails)
222
227
  }
223
228
  pi.events.emit(SUBAGENT_CHANNEL, { phase: 'start', agentType: agent.name, agentId: id })
@@ -23,8 +23,7 @@ export const ChainItem = Type.Object({
23
23
  })
24
24
 
25
25
  const AgentScopeSchema = StringEnum(['user', 'project', 'both'] as const, {
26
- description: 'Which agent directories to use. Default: "user". Use "both" to include project-local agents.',
27
- default: 'user',
26
+ description: 'Which agent directories to use. Omitted, an approved project resolves to "both" and an unapproved one to "user"; "both" includes project-local agents.',
28
27
  })
29
28
 
30
29
  export const SubagentParams = Type.Object({
@@ -17,6 +17,7 @@ import type { AgentToolResult } from '@earendil-works/pi-agent-core'
17
17
  import type { Message } from '@earendil-works/pi-ai'
18
18
  import { withFileMutationQueue } from '@earendil-works/pi-coding-agent'
19
19
 
20
+ import { killProcessTree } from '../internal/process-tree.js'
20
21
  import { runSubagentStartHooks } from '../internal/subagent-hooks.js'
21
22
  import { type AgentConfig, resolveModelAlias } from './agents.js'
22
23
  import { agentHooksEnv, agentInvocationArgs, agentMemoryPromptSection, childPromptBody, taskWithStartContext, unresolvedToolsError, withMemoryTools } from './child.js'
@@ -130,7 +131,7 @@ function appendPartialNote(result: SingleResult): void {
130
131
  * the spawn() call site instead; catching it here, in one place with an explicit
131
132
  * return type, keeps the caller's non-null stdout/stderr narrowing that a bare
132
133
  * try/catch around an inline spawn() call loses. */
133
- function spawnChild(command: string, args: string[], options: { cwd: string; env: NodeJS.ProcessEnv }): { proc: ChildProcessByStdio<null, Readable, Readable> } | { error: Error } {
134
+ export function spawnChild(command: string, args: string[], options: { cwd: string; env: NodeJS.ProcessEnv }): { proc: ChildProcessByStdio<null, Readable, Readable> } | { error: Error } {
134
135
  try {
135
136
  return {
136
137
  proc: spawn(command, args, {
@@ -348,19 +349,7 @@ async function runSingleAgentInner(options: RunAgentOptions): Promise<SingleResu
348
349
  resolve(1)
349
350
  })
350
351
 
351
- const killGroup = (sig: NodeJS.Signals): void => {
352
- try {
353
- // A child that never spawned has no pid and no group; the direct kill is all there is.
354
- if (proc.pid) process.kill(-proc.pid, sig)
355
- else proc.kill(sig)
356
- } catch {
357
- try {
358
- proc.kill(sig)
359
- } catch {
360
- /* already gone */
361
- }
362
- }
363
- }
352
+ const killGroup = (sig: NodeJS.Signals): void => killProcessTree(proc, sig)
364
353
  if (signal) {
365
354
  onAbort = () => {
366
355
  wasAborted = true
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Thinking Keyword Escalation
3
3
  *
4
- * Claude Code raises reasoning effort for a single turn when the prompt carries a
5
- * think keyword: `ultrathink` asks for the maximum, `think hard`/`think harder` for
6
- * a high level, and a bare `think` for a medium one. The word stays in the prompt
4
+ * Claude Code raises reasoning effort for a single turn when the prompt carries
5
+ * `ultrathink`, the only keyword: `think`, `think hard` and similar phrases pass
6
+ * through as ordinary prompt text. The word stays in the prompt
7
7
  * (the input is observed, never consumed or transformed), the escalation only ever
8
8
  * raises the level, and the prior level is restored once the turn settles, mirroring
9
9
  * how commands.ts restores a per-command model override on agent_settled.
package/extensions/web.ts CHANGED
@@ -16,6 +16,7 @@ import { Type } from 'typebox'
16
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
+ import { parseNumericEnv } from './internal/values.js'
19
20
  import { httpFetch } from './internal/web-transport.js'
20
21
 
21
22
  const SEARCH_ENDPOINT = 'https://html.duckduckgo.com/html/?q='
@@ -265,10 +266,8 @@ async function fetchText(rawUrl: string, crossHost: CrossHost, transport = httpF
265
266
  const FETCH_CACHE_TTL_DEFAULT_MS = 15 * 60 * 1000
266
267
 
267
268
  function fetchCacheTtlMs(): number {
268
- const raw = process.env.CLAUDE_CODE_WEBFETCH_CACHE_TTL_MS
269
- if (raw === undefined || raw.trim() === '') return FETCH_CACHE_TTL_DEFAULT_MS
270
- const parsed = Number(raw)
271
- return Number.isFinite(parsed) && parsed >= 0 ? parsed : FETCH_CACHE_TTL_DEFAULT_MS
269
+ const parsed = parseNumericEnv(process.env.CLAUDE_CODE_WEBFETCH_CACHE_TTL_MS)
270
+ return parsed !== undefined && parsed >= 0 ? parsed : FETCH_CACHE_TTL_DEFAULT_MS
272
271
  }
273
272
  const FETCH_CACHE_MAX_ENTRIES = 50
274
273
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-code",
3
- "version": "1.0.63",
3
+ "version": "1.0.65",
4
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",
@@ -63,15 +63,15 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@biomejs/biome": "^2.5.4",
66
- "@earendil-works/pi-agent-core": "^0.84.2",
67
- "@earendil-works/pi-ai": "^0.84.2",
68
- "@earendil-works/pi-coding-agent": "^0.84.2",
69
- "@earendil-works/pi-tui": "^0.84.2",
66
+ "@earendil-works/pi-agent-core": "^0.85.1",
67
+ "@earendil-works/pi-ai": "^0.85.1",
68
+ "@earendil-works/pi-coding-agent": "^0.85.1",
69
+ "@earendil-works/pi-tui": "^0.85.1",
70
70
  "@types/node": "^26.1.1",
71
- "@vitest/coverage-v8": "^4.1.10",
71
+ "@vitest/coverage-v8": "^5.0.0",
72
72
  "fast-check": "^4.9.0",
73
73
  "knip": "^6.34.0",
74
74
  "typescript": "^7.0.2",
75
- "vitest": "^4.1.10"
75
+ "vitest": "^5.0.0"
76
76
  }
77
77
  }