@herbertgao/pi-subagents 0.15.3 → 0.15.5

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.
@@ -19,7 +19,7 @@ import {
19
19
  truncateToWidth,
20
20
  visibleWidth,
21
21
  } from "@earendil-works/pi-tui"
22
- import { renderAgentName } from "../agent-color.js"
22
+ import { hasAgentBadge, renderAgentName } from "../agent-color.js"
23
23
  import type { AgentManager } from "../agent-manager.js"
24
24
  import type { AgentRecord } from "../types.js"
25
25
  import { getLifetimeTotal } from "../usage.js"
@@ -476,15 +476,24 @@ export class FleetList {
476
476
  width: number,
477
477
  theme: Theme,
478
478
  ): string {
479
- const left = ` ${this.bullet(rosterIndex, sel, theme)} ${renderAgentName(record.type, theme, { fallbackColor: "muted" })} ${record.description}`
479
+ const selected = rosterIndex === sel
480
+ const name = renderAgentName(
481
+ record.type,
482
+ theme,
483
+ selected
484
+ ? { fallbackColor: "text", bold: hasAgentBadge(record.type) }
485
+ : { fallbackColor: "muted" },
486
+ )
487
+ const description = selected
488
+ ? theme.fg("text", record.description)
489
+ : record.description
490
+ const left = ` ${this.bullet(rosterIndex, sel, theme)} ${name} ${description}`
480
491
  const tokens = getLifetimeTotal(
481
492
  this.agentActivity.get(record.id)?.lifetimeUsage ?? record.lifetimeUsage,
482
493
  )
483
494
  const elapsedMs = (record.completedAt ?? Date.now()) - record.startedAt // freezes once finished
484
- const right = theme.fg(
485
- "dim",
486
- `${formatFleetElapsed(elapsedMs)} · ${formatFleetTokens(tokens)}`,
487
- )
495
+ const stats = `${formatFleetElapsed(elapsedMs)} · ${formatFleetTokens(tokens)}`
496
+ const right = selected ? theme.fg("text", stats) : theme.fg("dim", stats)
488
497
  return rightAlign(left, right, width)
489
498
  }
490
499
  }
@@ -11,6 +11,7 @@
11
11
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent"
12
12
  import type { SubagentScheduler } from "../schedule.js"
13
13
  import type { ScheduledSubagent } from "../types.js"
14
+ import { selectItem } from "./select-item.js"
14
15
 
15
16
  /** Format an ISO timestamp as relative time ("in 4h", "2d ago", "—"). */
16
17
  function relTime(iso: string | undefined, now = Date.now()): string {
@@ -88,16 +89,16 @@ export async function showSchedulesMenu(
88
89
  return
89
90
  }
90
91
 
91
- const labels = jobs.map((j) => formatJob(j, scheduler))
92
- const choice = await ctx.ui.select(
92
+ // Numbered + item-paired: two jobs whose names agree in the first 18
93
+ // characters format identically, and matching the returned string back
94
+ // against a parallel label array cancelled whichever came first.
95
+ const job = await selectItem(
96
+ ctx.ui,
93
97
  `Scheduled jobs (${jobs.length}) — select to cancel`,
94
- labels,
98
+ jobs,
99
+ (j) => formatJob(j, scheduler),
95
100
  )
96
- if (!choice) return
97
-
98
- const idx = labels.indexOf(choice)
99
- if (idx < 0) return
100
- const job = jobs[idx]
101
+ if (!job) return
101
102
 
102
103
  const ok = await ctx.ui.confirm(
103
104
  `Cancel "${job.name}"?`,
@@ -0,0 +1,48 @@
1
+ /**
2
+ * select-item.ts — pick an item from a list via `ctx.ui.select`, safely.
3
+ *
4
+ * Pi's dialog API is `select(title, options: string[]) => Promise<string | undefined>`:
5
+ * strings in, string out, with no index or value form. Callers therefore have to
6
+ * map the returned string back to the item it came from, and the obvious way —
7
+ * `labels.indexOf(choice)` over a parallel array — silently resolves to the
8
+ * FIRST match whenever two rows format identically. Row formatters here truncate
9
+ * (job names to 18 chars, agent descriptions to whatever fits), and the text they
10
+ * truncate is LLM-authored, so collisions are ordinary rather than exotic.
11
+ *
12
+ * This numbers every row, which makes the labels unique by construction — no
13
+ * data-dependent branch that only executes in the case nobody exercises — and
14
+ * keeps each label paired with its item so a later edit that sorts or filters
15
+ * between building and resolving cannot desync them.
16
+ */
17
+
18
+ /** Minimal shape of the `ctx.ui` surface this needs. */
19
+ export interface SelectUI {
20
+ select(title: string, options: string[]): Promise<string | undefined>
21
+ }
22
+
23
+ /**
24
+ * Show a numbered picker and return the chosen item (not its label).
25
+ *
26
+ * Returns undefined when the user escapes, or when the returned string is not
27
+ * one we offered.
28
+ */
29
+ export async function selectItem<T>(
30
+ ui: SelectUI,
31
+ title: string,
32
+ items: readonly T[],
33
+ format: (item: T, index: number) => string,
34
+ ): Promise<T | undefined> {
35
+ // Pad the number so a 10+ item list keeps its columns aligned.
36
+ const width = String(items.length).length
37
+ const rows = items.map((item, i) => ({
38
+ item,
39
+ label: `${String(i + 1).padStart(width)}. ${format(item, i)}`,
40
+ }))
41
+
42
+ const choice = await ui.select(
43
+ title,
44
+ rows.map((r) => r.label),
45
+ )
46
+ if (!choice) return undefined
47
+ return rows.find((r) => r.label === choice)?.item
48
+ }
package/src/worktree.ts CHANGED
@@ -134,11 +134,15 @@ export function cleanupWorktree(
134
134
  // Truncate description for commit message (no shell sanitization needed — execFileSync uses argv)
135
135
  const safeDesc = agentDescription.slice(0, 200)
136
136
  const commitMsg = `pi-agent: ${safeDesc}`
137
- execFileSync("git", ["commit", "--no-verify", "-m", commitMsg], {
138
- cwd: worktree.path,
139
- stdio: "pipe",
140
- timeout: 10000,
141
- })
137
+ execFileSync(
138
+ "git",
139
+ ["commit", "--no-verify", "--no-gpg-sign", "-m", commitMsg],
140
+ {
141
+ cwd: worktree.path,
142
+ stdio: "pipe",
143
+ timeout: 10000,
144
+ },
145
+ )
142
146
  } else {
143
147
  const currentSha = execFileSync("git", ["rev-parse", "HEAD"], {
144
148
  cwd: worktree.path,