pi-code 1.0.71 → 1.0.72
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/extensions/commands.ts +41 -9
- package/extensions/plan-mode/index.ts +14 -3
- package/package.json +1 -1
package/extensions/commands.ts
CHANGED
|
@@ -52,6 +52,7 @@ import { managedSettingsFile, readManagedSettings } from './internal/managed-set
|
|
|
52
52
|
import { findModel } from './internal/model-lookup.js'
|
|
53
53
|
import { capForContext } from './internal/output-guard.js'
|
|
54
54
|
import { matchesPathRules } from './internal/path-rules.js'
|
|
55
|
+
import { isPlanModeState, PLAN_MODE_CHANNEL } from './internal/plan-mode-state.js'
|
|
55
56
|
import { type InstalledPlugin, installedPlugins, pluginComponentPath } from './internal/plugins.js'
|
|
56
57
|
import { isProjectApproved } from './internal/project-approval.js'
|
|
57
58
|
import { ancestorDirs, repoRoot } from './internal/project-root.js'
|
|
@@ -319,6 +320,9 @@ export default function commandsExtension(pi: ExtensionAPI) {
|
|
|
319
320
|
let projectApproved = false
|
|
320
321
|
/** Tool set to put back once the run a restricted command drove has ended. */
|
|
321
322
|
let pendingRestore: string[] | undefined
|
|
323
|
+
/** The tools this run's restriction took out of the set. Put back as a delta on whatever is
|
|
324
|
+
* active by then: overwriting with the snapshot undid every change made meanwhile. */
|
|
325
|
+
let tookAway = new Set<string>()
|
|
322
326
|
/** `Bash(...)` scopes enforced while that run lasts; lifted with the restriction. */
|
|
323
327
|
let pendingBashRules: string[] | undefined
|
|
324
328
|
/** `WebFetch(domain:...)` scopes enforced the same way. */
|
|
@@ -362,17 +366,17 @@ export default function commandsExtension(pi: ExtensionAPI) {
|
|
|
362
366
|
// agent_settled fires exactly once, after the run has fully settled and no such
|
|
363
367
|
// continuation remains, which is the grant's true clearing point.
|
|
364
368
|
pi.on('agent_settled', async () => {
|
|
365
|
-
pendingBashRules = undefined
|
|
366
|
-
pendingDomainRules = undefined
|
|
367
|
-
pendingAgentRules = undefined
|
|
368
|
-
pendingSkillRules = undefined
|
|
369
|
-
pendingPathRules = undefined
|
|
370
369
|
void modelOverride.settle()
|
|
371
370
|
void effortOverride.settle()
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
371
|
+
liftToolScope()
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
// "Execute the plan" continues the very run a scoped command started, so the scope it set
|
|
375
|
+
// for planning would judge every call of the execution and the tools it narrowed away
|
|
376
|
+
// would stay gone. Leaving plan mode is the point the user approved more than the command
|
|
377
|
+
// asked for; the model and effort overrides still last until the run settles.
|
|
378
|
+
pi.events?.on(PLAN_MODE_CHANNEL, (data) => {
|
|
379
|
+
if (isPlanModeState(data) && !data.active) liftToolScope()
|
|
376
380
|
})
|
|
377
381
|
|
|
378
382
|
// agent_settled never fires when pi is quit mid-run (double Ctrl+C, Ctrl+D, a closed
|
|
@@ -427,6 +431,32 @@ export default function commandsExtension(pi: ExtensionAPI) {
|
|
|
427
431
|
return original
|
|
428
432
|
}
|
|
429
433
|
|
|
434
|
+
/** Record what the restriction just applied took out, against the set captured before it. */
|
|
435
|
+
function noteTakenAway(): void {
|
|
436
|
+
if (!pendingRestore) return
|
|
437
|
+
const active = new Set(pi.getActiveTools())
|
|
438
|
+
tookAway = new Set(pendingRestore.filter((tool) => !active.has(tool)))
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/** End the run's tool restriction: drop its scopes and put back what it took away, as a
|
|
442
|
+
* delta on the set in force. A snapshot overwrite undid whatever else changed the set
|
|
443
|
+
* meanwhile: plan mode's Execute (the session finished read-only, plan mode off, with edit
|
|
444
|
+
* and write gone) and a tool registered during the run (dropped until a /reload). */
|
|
445
|
+
function liftToolScope(): void {
|
|
446
|
+
pendingBashRules = undefined
|
|
447
|
+
pendingDomainRules = undefined
|
|
448
|
+
pendingAgentRules = undefined
|
|
449
|
+
pendingSkillRules = undefined
|
|
450
|
+
pendingPathRules = undefined
|
|
451
|
+
const original = pendingRestore
|
|
452
|
+
if (!original) return
|
|
453
|
+
const active = pi.getActiveTools()
|
|
454
|
+
const keep = new Set([...active, ...tookAway])
|
|
455
|
+
pi.setActiveTools([...original.filter((tool) => keep.has(tool)), ...active.filter((tool) => !original.includes(tool))])
|
|
456
|
+
pendingRestore = undefined
|
|
457
|
+
tookAway = new Set()
|
|
458
|
+
}
|
|
459
|
+
|
|
430
460
|
/** Apply a command's allowed-tools to the run it drives: intersect with the tools
|
|
431
461
|
* pi actually has, keep the bash and path scopes for the tool_call guard, and let
|
|
432
462
|
* agent_settled restore the previous set. */
|
|
@@ -442,6 +472,7 @@ export default function commandsExtension(pi: ExtensionAPI) {
|
|
|
442
472
|
// intersects to nothing named only tools pi has none of: that restriction cannot
|
|
443
473
|
// be expressed, and applying it as "no tools" is not what the command asked for.
|
|
444
474
|
if (granted.length > 0 || allowed.length === 0) pi.setActiveTools(granted)
|
|
475
|
+
noteTakenAway()
|
|
445
476
|
// The latest restricted command speaks for the turn: a later unscoped grant
|
|
446
477
|
// lifts an earlier command's scopes rather than stacking under them. Rules get
|
|
447
478
|
// the same ${CLAUDE_*} substitution as the body, so a rule can name a bundled
|
|
@@ -460,6 +491,7 @@ export default function commandsExtension(pi: ExtensionAPI) {
|
|
|
460
491
|
if (!disallowed || disallowed.length === 0) return
|
|
461
492
|
captureRestorePoint()
|
|
462
493
|
pi.setActiveTools(pi.getActiveTools().filter((tool) => !disallowed.includes(tool)))
|
|
494
|
+
noteTakenAway()
|
|
463
495
|
}
|
|
464
496
|
|
|
465
497
|
/** Claude's `model:` frontmatter overrides the model for this run only, then the
|
|
@@ -64,18 +64,28 @@ export default function planModeExtension(pi: ExtensionAPI): void {
|
|
|
64
64
|
let todoItems: TodoItem[] = []
|
|
65
65
|
let planFromTool = false
|
|
66
66
|
let savedTools: string[] = []
|
|
67
|
+
/** Every tool pi had registered when the set was saved, active or not: a tool outside it
|
|
68
|
+
* was registered later, and pi activated it on top of whatever set was in force. */
|
|
69
|
+
let toolsAtSave = new Set<string>()
|
|
67
70
|
let stalledRuns = 0
|
|
68
71
|
let runProgress = false
|
|
69
72
|
|
|
73
|
+
const registeredTools = (): Set<string> => new Set((pi.getAllTools?.() ?? []).map((tool) => tool.name))
|
|
74
|
+
|
|
70
75
|
function enterPlanTools(): void {
|
|
71
76
|
savedTools = pi.getActiveTools()
|
|
77
|
+
toolsAtSave = registeredTools()
|
|
72
78
|
pi.setActiveTools(PLAN_MODE_TOOLS.filter((t) => savedTools.includes(t)))
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
/** Put back the saved set, plus any tool registered since: pi activates a late one (an MCP
|
|
82
|
+
* server past its connect window) on top of the restricted set, and overwriting with the
|
|
83
|
+
* snapshot dropped it for the rest of the session. A tool that was registered but not
|
|
84
|
+
* active at entry is not late, and stays out. */
|
|
75
85
|
function restoreTools(): void {
|
|
76
|
-
if (savedTools.length
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
if (savedTools.length === 0) return
|
|
87
|
+
const late = pi.getActiveTools().filter((name) => !toolsAtSave.has(name) && !savedTools.includes(name))
|
|
88
|
+
pi.setActiveTools([...savedTools, ...late])
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
/** Hooks report Claude's permission_mode from this bus state. */
|
|
@@ -457,6 +467,7 @@ After completing a step, include a [DONE:n] tag in your response.`,
|
|
|
457
467
|
// snapshot when plan mode is off would instead push a stale set over whatever
|
|
458
468
|
// pi has registered since, so it stays scoped to this branch.
|
|
459
469
|
savedTools = restored.savedTools ?? pi.getActiveTools()
|
|
470
|
+
toolsAtSave = registeredTools()
|
|
460
471
|
pi.setActiveTools(PLAN_MODE_TOOLS.filter((t) => savedTools.includes(t)))
|
|
461
472
|
// --plan enters plan mode without ever toggling, so nothing has persisted yet
|
|
462
473
|
// and a /reload would find no snapshot to restore from. Record it now, while
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.72",
|
|
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",
|