@maxiaochao/pi-toolkit 0.5.1 → 0.5.3

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 2026-09-15 - v0.5.3
2
+
3
+ - fix pi-worktree remove: worktrees with initialized submodules now remove via forced retry; dirty worktrees confirm the discard explicitly before removal; worktrees whose directory is already gone now prune their stale registration and clean up the branch
4
+
5
+ ## 2026-09-11 - v0.5.2
6
+
7
+ - fix btw: side-session and summarize passed a nonexistent `modelRegistry` option to `createAgentSession`, so a fresh ModelRuntime was created that didn't know extension-registered providers (e.g. volcengine-plan) and prompting failed with "No API key found"; now the main session's ModelRuntime is reused via `modelRuntime`
8
+
1
9
  ## 2026-09-11 - v0.5.1
2
10
 
3
11
  - fix pi-worktree: remove now cleans up the branch automatically — merged branches are deleted without prompting, unmerged ones confirm once (one prompt covers both worktree removal and branch deletion), so recreating the same name no longer silently reuses a stale branch
package/bin/pi-worktree CHANGED
@@ -141,17 +141,68 @@ cmd_remove() {
141
141
  require_repo
142
142
  [[ $# -eq 1 ]] || die "usage: pi-worktree -r <name>"
143
143
  local dir; dir="$(wt_dir "$1")"
144
- [[ -d "$dir" ]] || die "worktree not found: $dir"
145
- # refuse to remove if the branch is not merged into main
146
- local branch; branch=$(git -C "$dir" branch --show-current)
147
- local rm_anyway=0
148
- if [[ -n "$branch" ]] && ! git merge-base --is-ancestor "$branch" main 2>/dev/null; then
149
- warn "branch '$branch' does not appear merged into 'main' (or no main branch exists)."
150
- read -rp "Remove anyway? [y/N] " ans
151
- [[ "$ans" =~ ^[Yy]$ ]] || die "aborted"
152
- rm_anyway=1
144
+ local branch registered rm_anyway=0 force=0
145
+ if [[ ! -d "$dir" ]]; then
146
+ # Directory already gone but possibly still registered (prunable), or a
147
+ # stale branch left behind by an earlier prune: clean the registration,
148
+ # then fall through to the branch cleanup.
149
+ local registered
150
+ registered=$(git worktree list --porcelain | awk -v p="$(pwd -P)/$dir" '
151
+ $0=="worktree "p {f=1; next}
152
+ f && /^branch / {print substr($0,8); exit}
153
+ f && /^detached/ {print "-"; exit}
154
+ f && /^$/ {exit}')
155
+ if [[ "$registered" == "-" ]]; then
156
+ git worktree prune
157
+ ok "pruned stale registration: $dir (directory already gone)"
158
+ return 0 # detached HEAD, nothing else to clean up
159
+ fi
160
+ if [[ -n "$registered" ]]; then
161
+ branch=${registered#refs/heads/}
162
+ elif git show-ref --verify --quiet "refs/heads/$1"; then
163
+ branch=$1 # registration already pruned; only the stale branch remains
164
+ else
165
+ die "worktree not found: $dir"
166
+ fi
167
+ if [[ -n "$registered" ]]; then
168
+ git worktree prune
169
+ ok "pruned stale registration: $dir (directory already gone)"
170
+ fi
171
+ else
172
+ # dirty worktree: removing discards modified/untracked files, which git
173
+ # refuses to do without --force — so confirm the loss explicitly
174
+ if ! git -C "$dir" diff --quiet 2>/dev/null \
175
+ || ! git -C "$dir" diff --cached --quiet 2>/dev/null \
176
+ || [[ -n "$(git -C "$dir" ls-files --others --exclude-standard)" ]]; then
177
+ warn "worktree contains modified/untracked files that will be discarded:"
178
+ git -C "$dir" status --short | head -10
179
+ read -rp "Discard and remove anyway? [y/N] " ans
180
+ [[ "$ans" =~ ^[Yy]$ ]] || die "aborted"
181
+ force=1
182
+ fi
183
+ branch=$(git -C "$dir" branch --show-current)
184
+ # refuse to remove if the branch is not merged into main
185
+ if [[ -n "$branch" ]] && ! git merge-base --is-ancestor "$branch" main 2>/dev/null; then
186
+ warn "branch '$branch' does not appear merged into 'main' (or no main branch exists)."
187
+ read -rp "Remove anyway? [y/N] " ans
188
+ [[ "$ans" =~ ^[Yy]$ ]] || die "aborted"
189
+ rm_anyway=1
190
+ fi
191
+ local out
192
+ if [[ "$force" == 1 ]]; then
193
+ git worktree remove --force "$dir" && ok "removed: $dir (discarded changes as confirmed)"
194
+ elif out=$(git worktree remove "$dir" 2>&1); then
195
+ ok "removed: $dir"
196
+ elif [[ "$out" == *"submodules cannot be moved or removed"* ]]; then
197
+ # git refuses to remove worktrees with initialized submodules unless
198
+ # --force is used (--force only overrides the submodule check here; the
199
+ # branch deletion below still asks before dropping unmerged commits)
200
+ git worktree remove --force "$dir" && ok "removed: $dir (contained submodules)"
201
+ else
202
+ printf '%s\n' "$out" >&2
203
+ die "failed to remove worktree: $dir"
204
+ fi
153
205
  fi
154
- git worktree remove "$dir" && ok "removed: $dir"
155
206
  # The branch survives `git worktree remove`; recreating the same name would
156
207
  # silently reuse this stale branch. Delete merged branches automatically.
157
208
  if [[ -n "$branch" ]] && git show-ref --verify --quiet "refs/heads/$branch"; then
package/extensions/btw.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  type ExtensionAPI,
10
10
  type ExtensionCommandContext,
11
11
  type ExtensionContext,
12
+ type ModelRuntime,
12
13
  type ResourceLoader,
13
14
  } from "@earendil-works/pi-coding-agent";
14
15
  import { type AssistantMessage, type Message, type ThinkingLevel as AiThinkingLevel } from "@earendil-works/pi-ai";
@@ -633,7 +634,7 @@ export default function (pi: ExtensionAPI) {
633
634
  const { session } = await createAgentSession({
634
635
  sessionManager: SessionManager.inMemory(),
635
636
  model: ctx.model,
636
- modelRegistry: ctx.modelRegistry as AgentSession["modelRegistry"],
637
+ modelRuntime: getParentModelRuntime(ctx),
637
638
  thinkingLevel: pi.getThinkingLevel() as SessionThinkingLevel,
638
639
  tools: ["read", "bash", "edit", "write"],
639
640
  resourceLoader: createBtwResourceLoader(ctx),
@@ -809,6 +810,14 @@ export default function (pi: ExtensionAPI) {
809
810
  });
810
811
  }
811
812
 
813
+ function getParentModelRuntime(ctx: ExtensionCommandContext | ExtensionContext): ModelRuntime {
814
+ // The ModelRegistry facade keeps its runtime in a JS property that is
815
+ // private at the type level. Reusing it is required: a fresh ModelRuntime
816
+ // would not know about extension-registered providers (e.g. volcengine-plan),
817
+ // causing "No API key found" even when auth.json has the credential.
818
+ return (ctx.modelRegistry as unknown as { runtime: ModelRuntime }).runtime;
819
+ }
820
+
812
821
  async function summarizeThread(ctx: ExtensionContext, items: BtwDetails[]): Promise<string> {
813
822
  const model = ctx.model;
814
823
  if (!model) {
@@ -823,7 +832,7 @@ export default function (pi: ExtensionAPI) {
823
832
  const { session } = await createAgentSession({
824
833
  sessionManager: SessionManager.inMemory(),
825
834
  model,
826
- modelRegistry: ctx.modelRegistry as AgentSession["modelRegistry"],
835
+ modelRuntime: getParentModelRuntime(ctx),
827
836
  thinkingLevel: "off",
828
837
  tools: [],
829
838
  resourceLoader: createBtwResourceLoader(ctx, [BTW_SUMMARY_PROMPT]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxiaochao/pi-toolkit",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Personal pi coding agent toolkit: /todos, /btw, /cache_export extensions, browser automation, and bundled theme/skills.",
5
5
  "type": "module",
6
6
  "keywords": [