pi-quiver 6.2.0 → 6.3.0

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
@@ -8,6 +8,10 @@ Published to npm as `pi-quiver` (`pi install npm:pi-quiver`). Pushing a
8
8
  via OIDC trusted publishing. The release helper at
9
9
  `.agents/skills/release/scripts/release.sh` cuts the tag; CI publishes.
10
10
 
11
+ ## v6.3.0 - 2026-09-16
12
+
13
+ - `session-name` Herdr sink claims any numeric tab label (optionally behind one `* `) at any position instead of only the label equal to the tab's live position, so tabs reordered before the first auto-name - or after a `/new` restore - get named; the claim resets at every `session_start` (`/new`, resume, fork behave like a fresh process) and a tab that reverts to a bare number is re-claimed on the next turn. The extension never writes a digits-only label (`1234` -> `#1234`) and the naming prompt asks for `PR 1234` / `issue 123` / `ticket ABC-123` over a bare ID.
14
+
11
15
  ## v6.2.0 - 2026-09-15
12
16
 
13
17
  - `slack_thread` flattens Block Kit blocks in its default rendering (blocks joined by ` / `, `[<type>]` for unknown types) and gains an opt-in `raw: true` mode returning thread messages as a pure JSON array for `slack_update` round-trips; both modes go through the existing size gate. (#21)
package/README.md CHANGED
@@ -200,7 +200,7 @@ survive untouched.
200
200
 
201
201
  `sessionAutoName.enabled` makes one extra short LLM call per session (once, after the first turn) to title it; `false` (default) makes no model calls. `rules` appends house conventions to the naming prompt (later rules win when they conflict with the built-ins). Literal, case-insensitive `deny` phrases are stripped from every name; whitespace inside a phrase is loose, so `"acme corp"` also catches `AcmeCorp`. `revisitFirstTurn` re-evaluates the name once that many model round trips have completed, while `revisitEveryTurns` does so at every multiple; both default to `0` (off) because each revisit costs another short LLM call. For example, `10` and `100` mark round trips 10, 100, 200, 300. Revisits only run when the agent has fully settled (idle, nothing queued) - an automated multi-turn run such as a subagent chain is never renamed or delayed mid-flight; cadence points it crossed fire once, at the settle. A machine-generated name is replaced when stale. A name set by a human is never overwritten: the extension strongly prefers it, and announces a suggestion only when the work has clearly moved on. Counts come from the persisted transcript, so they survive resume.
202
202
 
203
- `herdrTab` (default `true`) mirrors the same curated label to the Herdr tab bar over Herdr's unix socket, independent of `ghosttyTab` - either sink can be toggled off without affecting the other. It's claim-once: the extension adopts a tab only while it still shows its default numeric label (its live 1-based position among the workspace's tabs); a manually renamed tab, or one renamed mid-session by hand, is never touched again for that session - the human's label always wins. One exception: exactly one leading `* ` (herdr-ntfy-notify's armed marker) is not a human rename - it is preserved across renames and the shutdown restore, and removing it keeps the claim. On every shutdown (quit, reload, `/new`, resume, fork) a claimed tab's label is restored to its then-current default position, so a successor session in the same pane can claim cleanly. It only ever runs in TUI mode, on an attached TTY, under Herdr (`HERDR_ENV`/`HERDR_TAB_ID`/`HERDR_SOCKET_PATH` all set) - `pi -p`/json/rpc runs and background subagents never touch the tab. Caveats: a hard crash (`kill -9`) skips the restore and leaves the stale label - rename the tab by hand to recover; and the restored label is internally a custom name (Herdr has no clear-to-auto API), so that tab keeps a number-looking label but stops renumbering on later tab closes/reorders.
203
+ `herdrTab` (default `true`) mirrors the same curated label to the Herdr tab bar over Herdr's unix socket, independent of `ghosttyTab` - either sink can be toggled off without affecting the other. A tab is claimable while its label is a bare number (`1`, `4`, ...) at any position - Herdr's vocabulary for "unnamed" - so reordering tabs before or after naming never matters; a tab you deliberately name `3` is taken over too. A non-numeric label is treated as a human's and is never overwritten; the check repeats every turn, so renumbering the tab by hand hands it back. Exactly one leading `* ` (herdr-ntfy-notify's armed marker) is not a human rename - it is preserved across renames and the shutdown restore, and removing it keeps the claim. Every session claims afresh: on each shutdown (quit, reload, `/new`, resume, fork) a claimed tab is restored to its then-current position number, and the successor session in the pane claims it again on its first name. The label the extension writes is never digits-only - a bare `1234` becomes `#1234`, and the naming prompt asks the model for `PR 1234` / `issue 123` / `ticket ABC-123` instead of a bare ID. It only ever runs in TUI mode, on an attached TTY, under Herdr (`HERDR_ENV`/`HERDR_TAB_ID`/`HERDR_SOCKET_PATH` all set) - `pi -p`/json/rpc runs and background subagents never touch the tab. Caveat: the restored number is internally a custom name (Herdr has no clear-to-auto API), so that tab keeps a number-looking label but stops renumbering on later tab closes/reorders.
204
204
 
205
205
  `fastMode` only affects `claude-opus-4-8` and `claude-opus-5` requests on Anthropic's `anthropic-messages` API; enabling it opts into premium fast-mode pricing. `--fast` forces it on for one launch; `/fast on|off` toggles live. Proxy providers (opencode, cloudflare-ai-gateway) are excluded. `fastMode`'s header injection needs the `before_provider_headers` hook (pi bundling `@earendil-works/pi-coding-agent` >= 0.80.5); on older pi the beta header is silently not sent. The `anthropic-beta` header is discovered at request time by probing pi's own request assembly (no network), so conditional betas pi adds - e.g. `server-side-fallback-2026-07-01` for models with server-side fallback - are preserved alongside `fast-mode-2026-02-01`. If the probe fails, the header falls back to the static OAuth-identity + fast-mode list. See [doc/fetch.md](doc/fetch.md) and [doc/doc-to-md.md](doc/doc-to-md.md) for the ingestion tools' full reference; session-name/sword-header behavior above is complete.
206
206
 
@@ -173,12 +173,15 @@ export function isGhosttyActive(
173
173
  }
174
174
 
175
175
  export function toTabLabel(name: string, maxWords = 4): string {
176
- return name
176
+ const label = name
177
177
  .replace(/[\u0000-\u001f\u007f]/g, " ")
178
178
  .split(/\s+/)
179
179
  .filter(Boolean)
180
180
  .slice(0, maxWords)
181
181
  .join(" ");
182
+ // A bare number is Herdr's "unnamed tab" vocabulary; our own label must
183
+ // never look like one or a successor session would treat it as claimable.
184
+ return /^\d+$/.test(label) ? `#${label}` : label;
182
185
  }
183
186
 
184
187
  /**
@@ -338,6 +341,7 @@ export function buildNamingPrompt(conversation: string, opts: PromptOptions = {}
338
341
  "- Describe the actual task, NOT the tool/skill/command used to start it.",
339
342
  "- Lead with an action verb (e.g. refine, fix, add, rework).",
340
343
  "- Preserve ticket/issue IDs (e.g. ABC-123, PROJ-42, #99) verbatim.",
344
+ "- If the whole TAB would be just an ID, say what it is: PR 1234, issue 123, ticket ABC-123. Never reply with digits only.",
341
345
  "- No quotes, no trailing punctuation, plain ASCII.",
342
346
  ...rules.map((r) => `- ${r}`),
343
347
  "Example -> SESSION: Refine Linear Ticket ABC-123 / TAB: Refine ABC-123",
@@ -496,12 +500,15 @@ export function installSessionName(pi: ExtensionAPI, generate: NameGenerator = g
496
500
  if (currentTabLabel) renameGhosttyTab(currentTabLabel, cfg.ghosttyTab);
497
501
  };
498
502
 
499
- // Herdr sink. Claim-once: adopt the tab only while it shows its default
500
- // (position) label; a human rename - before or during the session - wins
501
- // permanently. The one exception is exactly one leading "* ": that is
502
- // herdr-ntfy-notify's armed marker, ignored for ownership and carried
503
- // through on every write. All syncs serialize on one chain so overlapping
504
- // hooks never interleave a read with a rename.
503
+ // Herdr sink. A tab is claimable while its label is a bare number - Herdr's
504
+ // vocabulary for "unnamed", and the only label this extension or
505
+ // herdr-ntfy-notify ever write back - regardless of position: Herdr stores a
506
+ // renamed number as a permanent custom label that reorders never update. A
507
+ // non-numeric label is a human's and is never overwritten; it is re-checked
508
+ // each turn so a tab renumbered by hand becomes claimable again. Exactly one
509
+ // leading "* " is herdr-ntfy-notify's armed marker, ignored for ownership and
510
+ // carried through on every write. All syncs serialize on one chain so
511
+ // overlapping hooks never interleave a read with a rename.
505
512
  // Used both for turn_start syncs (bounds a wedged-but-accepting Herdr so it
506
513
  // can't stall the turn) and for the session_end restore.
507
514
  const HERDR_TIMEOUT_MS = 500;
@@ -514,44 +521,42 @@ export function installSessionName(pi: ExtensionAPI, generate: NameGenerator = g
514
521
  };
515
522
 
516
523
  const ARMED_PREFIX = "* ";
524
+ const NUMERIC = /^\d+$/;
517
525
  const matchOwned = (live: string, expected: string): { owned: boolean; armed: boolean } => {
518
526
  if (live === expected) return { owned: true, armed: false };
519
527
  if (live === ARMED_PREFIX + expected) return { owned: true, armed: true };
520
528
  return { owned: false, armed: false };
521
529
  };
530
+ const matchClaimable = (live: string): { claimable: boolean; armed: boolean } => {
531
+ if (NUMERIC.test(live)) return { claimable: true, armed: false };
532
+ if (live.startsWith(ARMED_PREFIX) && NUMERIC.test(live.slice(ARMED_PREFIX.length))) return { claimable: true, armed: true };
533
+ return { claimable: false, armed: false };
534
+ };
522
535
 
523
536
  const syncHerdrTab = (cfg: Config, label: string | null, mode: Mode | undefined): Promise<void> => {
524
537
  const run = async (): Promise<void> => {
525
538
  if (!cfg.herdrTab || mode !== "tui" || !label) return;
526
- if (herdrClaim === "backed-off") return;
527
539
  if (!isHerdrActive()) return;
528
540
  const sock = process.env.HERDR_SOCKET_PATH as string;
529
541
  const tabId = process.env.HERDR_TAB_ID as string;
530
- if (herdrClaim === null) {
531
- const tabs = await listTabs(sock, HERDR_TIMEOUT_MS);
532
- if (!tabs) return; // transient; retry next sync
533
- const position = positionOf(tabs, tabId);
534
- if (position === -1) return; // stale tab id (pane moved); retry harmlessly
535
- const own = tabs.find((t) => t.tab_id === tabId)!;
536
- const owned = matchOwned(own.label, String(position));
537
- if (!owned.owned) {
538
- herdrClaim = "backed-off"; // human (or crashed predecessor) owns it
542
+ const live = await getTab(sock, tabId, HERDR_TIMEOUT_MS);
543
+ if (!live) return; // transient or stale tab id; a failed read is never a human rename
544
+ if (typeof herdrClaim === "object" && herdrClaim !== null) {
545
+ const owned = matchOwned(live.label, herdrClaim.lastWritten);
546
+ if (owned.owned) {
547
+ if (label === herdrClaim.lastWritten) return;
548
+ if (await renameTab(sock, tabId, (owned.armed ? ARMED_PREFIX : "") + label, HERDR_TIMEOUT_MS)) herdrClaim.lastWritten = label;
539
549
  return;
540
550
  }
541
- if (await renameTab(sock, tabId, (owned.armed ? ARMED_PREFIX : "") + label, HERDR_TIMEOUT_MS)) {
542
- herdrClaim = { lastWritten: label };
543
- }
544
- return;
545
551
  }
546
- const live = await getTab(sock, tabId, HERDR_TIMEOUT_MS);
547
- if (!live) return; // failed read is not a human rename; stay claimed
548
- const owned = matchOwned(live.label, herdrClaim.lastWritten);
549
- if (!owned.owned) {
550
- herdrClaim = "backed-off";
552
+ const claim = matchClaimable(live.label);
553
+ if (!claim.claimable) {
554
+ herdrClaim = "backed-off"; // human owns it until the label is numeric again
551
555
  return;
552
556
  }
553
- if (label === herdrClaim.lastWritten) return;
554
- if (await renameTab(sock, tabId, (owned.armed ? ARMED_PREFIX : "") + label, HERDR_TIMEOUT_MS)) herdrClaim.lastWritten = label;
557
+ if (await renameTab(sock, tabId, (claim.armed ? ARMED_PREFIX : "") + label, HERDR_TIMEOUT_MS)) {
558
+ herdrClaim = { lastWritten: label };
559
+ }
555
560
  };
556
561
  herdrChain = herdrChain.then(run, run);
557
562
  return herdrChain;
@@ -595,6 +600,10 @@ export function installSessionName(pi: ExtensionAPI, generate: NameGenerator = g
595
600
  });
596
601
 
597
602
  pi.on("session_start", async (_event, ctx) => {
603
+ // Every session claims afresh, exactly like a fresh process: /new, resume,
604
+ // and fork each tear the previous session down (restore included) first.
605
+ // Before loadConfig so `enabled: false, herdrTab: true` resets too.
606
+ herdrClaim = null;
598
607
  const cfg = loadConfig(ctx);
599
608
  if (!cfg.enabled) return; // off by default; opt in via settings.json
600
609
  const current = pi.getSessionName();
@@ -646,9 +655,9 @@ export function installSessionName(pi: ExtensionAPI, generate: NameGenerator = g
646
655
  });
647
656
 
648
657
  // Restore the numeric label on every teardown reason (quit/reload/new/
649
- // resume/fork): a successor session in this pane must find the default so
650
- // claim-once stays sound. The restored label is internally a custom name -
651
- // Herdr has no clear-to-auto API - so it looks right but won't renumber.
658
+ // resume/fork) so the pane reads as unnamed again. The restored label is
659
+ // internally a custom name - Herdr has no clear-to-auto API - so it won't
660
+ // renumber on reorders; the successor claims any numeric label regardless.
652
661
  pi.on("session_shutdown", async () => {
653
662
  await restoreHerdrTab();
654
663
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-quiver",
3
- "version": "6.2.0",
3
+ "version": "6.3.0",
4
4
  "description": "Personal pack of Pi coding-agent extensions: context-safe fetch, doc_to_md PDF/DOCX/PPTX-to-Markdown conversion, session naming, a themed ASCII startup header, Opus 4.8 fast mode, and a provider-stall watchdog.",
5
5
  "author": "Jacek Juraszek",
6
6
  "license": "MIT",