@esso0428/pi-subagents 0.15.3 → 0.15.4

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
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.15.4] - 2026-09-11
11
+
12
+ ### Fixed
13
+ - **Unavailable history records no longer load the subagent panels.** Reload-restored metadata is shown in AgentWidget, FleetView, and `/agents` only when its project-local transcript still exists; stale records remain available through the normal session result/history data without creating a dead-end Enter target.
14
+
10
15
  ## [0.15.3] - 2026-09-11
11
16
 
12
17
  ### Added
@@ -14,6 +14,8 @@ export declare function createAgentHistoryPath(cwd: string, agentId: string, age
14
14
  export declare function agentHistoryLocator(cwd: string, historyPath: string): string;
15
15
  /** Resolve only paths in this package's project-local transcript namespace. */
16
16
  export declare function resolveAgentHistoryPath(cwd: string, locator: string): string | undefined;
17
+ /** Return true only when a valid project-local transcript file exists. */
18
+ export declare function hasAgentHistory(cwd: string, locator: string | undefined): boolean;
17
19
  /**
18
20
  * Read persisted transcript entries into the message shape used by the live
19
21
  * conversation viewer. Malformed lines and unknown records are skipped so one
@@ -5,6 +5,7 @@ exports.ensureSubagentsGitignore = ensureSubagentsGitignore;
5
5
  exports.createAgentHistoryPath = createAgentHistoryPath;
6
6
  exports.agentHistoryLocator = agentHistoryLocator;
7
7
  exports.resolveAgentHistoryPath = resolveAgentHistoryPath;
8
+ exports.hasAgentHistory = hasAgentHistory;
8
9
  exports.readAgentHistory = readAgentHistory;
9
10
  const node_fs_1 = require("node:fs");
10
11
  const node_path_1 = require("node:path");
@@ -58,6 +59,13 @@ function resolveAgentHistoryPath(cwd, locator) {
58
59
  return undefined;
59
60
  return candidate;
60
61
  }
62
+ /** Return true only when a valid project-local transcript file exists. */
63
+ function hasAgentHistory(cwd, locator) {
64
+ if (!locator)
65
+ return false;
66
+ const path = resolveAgentHistoryPath(cwd, locator);
67
+ return path !== undefined && (0, node_fs_1.existsSync)(path);
68
+ }
61
69
  /**
62
70
  * Read persisted transcript entries into the message shape used by the live
63
71
  * conversation viewer. Malformed lines and unknown records are skipped so one
package/dist/index.js CHANGED
@@ -581,7 +581,7 @@ function default_1(pi) {
581
581
  // everything else; "off" = hide the widget entirely. Read live at render time.
582
582
  let widgetMode = "background";
583
583
  function getWidgetMode() { return widgetMode; }
584
- const widget = new agent_widget_js_1.AgentWidget(manager, agentActivity, getWidgetMode);
584
+ const widget = new agent_widget_js_1.AgentWidget(manager, agentActivity, getWidgetMode, () => currentCtx?.cwd);
585
585
  function setWidgetMode(m) { widgetMode = m; widget.update(); }
586
586
  // Claude Code-style FleetView: navigable list of main + subagents below the editor.
587
587
  const fleet = new fleet_list_js_1.FleetList(manager, agentActivity, () => currentCtx?.cwd);
@@ -1617,7 +1617,7 @@ Terse command-style prompts produce shallow, generic work.
1617
1617
  }
1618
1618
  }
1619
1619
  async function showRunningAgents(ctx) {
1620
- const agents = manager.listAgents();
1620
+ const agents = manager.listAgents().filter((record) => record.session !== undefined || (0, agent_history_js_1.hasAgentHistory)(ctx.cwd, record.transcriptPath));
1621
1621
  if (agents.length === 0) {
1622
1622
  ctx.ui.notify("No agents.", "info");
1623
1623
  return;
@@ -102,6 +102,8 @@ export declare class AgentWidget {
102
102
  * extension supplies one defaulting to `"background"`.
103
103
  */
104
104
  private mode;
105
+ /** Current project cwd used to validate reload-restored transcripts. */
106
+ private getCwd;
105
107
  private uiCtx;
106
108
  private widgetFrame;
107
109
  private widgetInterval;
@@ -121,7 +123,9 @@ export declare class AgentWidget {
121
123
  * `WidgetMode`. Defaults to `"all"` when a caller supplies no policy; the
122
124
  * extension supplies one defaulting to `"background"`.
123
125
  */
124
- mode?: () => WidgetMode);
126
+ mode?: () => WidgetMode,
127
+ /** Current project cwd used to validate reload-restored transcripts. */
128
+ getCwd?: () => string | undefined);
125
129
  /**
126
130
  * Agents eligible for the widget, per the current `WidgetMode`:
127
131
  * - `off`: none (the widget's existing empty-state path hides it entirely).
@@ -18,6 +18,7 @@ exports.getPromptModeLabel = getPromptModeLabel;
18
18
  exports.buildInvocationTags = buildInvocationTags;
19
19
  exports.describeActivity = describeActivity;
20
20
  const pi_tui_1 = require("@earendil-works/pi-tui");
21
+ const agent_history_js_1 = require("../agent-history.js");
21
22
  const agent_types_js_1 = require("../agent-types.js");
22
23
  const usage_js_1 = require("../usage.js");
23
24
  // ---- Constants ----
@@ -155,6 +156,7 @@ class AgentWidget {
155
156
  manager;
156
157
  agentActivity;
157
158
  mode;
159
+ getCwd;
158
160
  uiCtx;
159
161
  widgetFrame = 0;
160
162
  widgetInterval;
@@ -174,10 +176,13 @@ class AgentWidget {
174
176
  * `WidgetMode`. Defaults to `"all"` when a caller supplies no policy; the
175
177
  * extension supplies one defaulting to `"background"`.
176
178
  */
177
- mode = () => "all") {
179
+ mode = () => "all",
180
+ /** Current project cwd used to validate reload-restored transcripts. */
181
+ getCwd = () => undefined) {
178
182
  this.manager = manager;
179
183
  this.agentActivity = agentActivity;
180
184
  this.mode = mode;
185
+ this.getCwd = getCwd;
181
186
  }
182
187
  /**
183
188
  * Agents eligible for the widget, per the current `WidgetMode`:
@@ -191,7 +196,12 @@ class AgentWidget {
191
196
  * - `all`: every agent.
192
197
  */
193
198
  widgetAgents() {
194
- const all = this.manager.listAgents();
199
+ const all = this.manager.listAgents().filter((record) => {
200
+ if (record.status === "running" || record.status === "queued")
201
+ return true;
202
+ const cwd = this.getCwd();
203
+ return cwd !== undefined && (0, agent_history_js_1.hasAgentHistory)(cwd, record.transcriptPath);
204
+ });
195
205
  switch (this.mode()) {
196
206
  case "off": return [];
197
207
  case "background": return all.filter(a => a.isBackground !== false);
@@ -74,9 +74,11 @@ export declare class FleetList {
74
74
  update(): void;
75
75
  /**
76
76
  * Agents shown in the list, ordered earliest-launched first so the ones you
77
- * started sooner sit at the top. Every row is openable (has a session), so Enter
78
- * never dead-ends. Included: running/queued, plus the agent currently being
79
- * viewed, plus recently-finished ones (they linger briefly before dropping out).
77
+ * started sooner sit at the top. Live rows are openable through their session;
78
+ * terminal rows are included only when their durable transcript exists. This
79
+ * prevents Enter from dead-ending on stale rehydrated metadata.
80
+ * Included: running/queued, plus the agent currently being viewed, plus
81
+ * recently-finished ones (they linger briefly before dropping out).
80
82
  * Pending agents with no session yet are hidden until they start.
81
83
  * (`listAgents()` is newest-first, so we re-sort.)
82
84
  */
@@ -15,7 +15,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.FleetList = void 0;
16
16
  exports.formatFleetElapsed = formatFleetElapsed;
17
17
  exports.formatFleetTokens = formatFleetTokens;
18
- const node_fs_1 = require("node:fs");
19
18
  const pi_tui_1 = require("@earendil-works/pi-tui");
20
19
  const agent_history_js_1 = require("../agent-history.js");
21
20
  const usage_js_1 = require("../usage.js");
@@ -167,9 +166,11 @@ class FleetList {
167
166
  // ---- Roster ----
168
167
  /**
169
168
  * Agents shown in the list, ordered earliest-launched first so the ones you
170
- * started sooner sit at the top. Every row is openable (has a session), so Enter
171
- * never dead-ends. Included: running/queued, plus the agent currently being
172
- * viewed, plus recently-finished ones (they linger briefly before dropping out).
169
+ * started sooner sit at the top. Live rows are openable through their session;
170
+ * terminal rows are included only when their durable transcript exists. This
171
+ * prevents Enter from dead-ending on stale rehydrated metadata.
172
+ * Included: running/queued, plus the agent currently being viewed, plus
173
+ * recently-finished ones (they linger briefly before dropping out).
173
174
  * Pending agents with no session yet are hidden until they start.
174
175
  * (`listAgents()` is newest-first, so we re-sort.)
175
176
  */
@@ -181,10 +182,7 @@ class FleetList {
181
182
  || a.id === this.viewingAgentId
182
183
  || (a.completedAt != null && now - a.completedAt < FINISHED_LINGER_MS));
183
184
  const cwd = this.getCwd();
184
- const historyPath = cwd && a.transcriptPath
185
- ? (0, agent_history_js_1.resolveAgentHistoryPath)(cwd, a.transcriptPath)
186
- : undefined;
187
- const history = !!historyPath && (0, node_fs_1.existsSync)(historyPath);
185
+ const history = cwd !== undefined && (0, agent_history_js_1.hasAgentHistory)(cwd, a.transcriptPath);
188
186
  return !!live || history;
189
187
  })
190
188
  .sort((a, b) => a.startedAt - b.startedAt);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esso0428/pi-subagents",
3
- "version": "0.15.3",
3
+ "version": "0.15.4",
4
4
  "description": "A pi extension that brings smart Claude Code-style autonomous sub-agents to pi, with npm:pi-subagents-style JSON agent overrides.",
5
5
  "author": "ESSO0428",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  /** Durable, project-local transcript storage for subagents. */
2
2
 
3
- import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
3
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
4
4
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import type { AgentSession } from "@earendil-works/pi-coding-agent";
6
6
 
@@ -62,6 +62,13 @@ export function resolveAgentHistoryPath(cwd: string, locator: string): string |
62
62
  return candidate;
63
63
  }
64
64
 
65
+ /** Return true only when a valid project-local transcript file exists. */
66
+ export function hasAgentHistory(cwd: string, locator: string | undefined): boolean {
67
+ if (!locator) return false;
68
+ const path = resolveAgentHistoryPath(cwd, locator);
69
+ return path !== undefined && existsSync(path);
70
+ }
71
+
65
72
  /**
66
73
  * Read persisted transcript entries into the message shape used by the live
67
74
  * conversation viewer. Malformed lines and unknown records are skipped so one
package/src/index.ts CHANGED
@@ -24,7 +24,7 @@ import { isModelInScope, readEnabledModels, resolveEnabledModels } from "./enabl
24
24
  import { GroupJoinManager } from "./group-join.js";
25
25
  import { resolveAgentInvocationConfig, resolveJoinMode } from "./invocation-config.js";
26
26
  import { type ModelRegistry, resolveModel } from "./model-resolver.js";
27
- import { agentHistoryLocator, createAgentHistoryPath, readAgentHistory, ensureSubagentsGitignore } from "./agent-history.js";
27
+ import { agentHistoryLocator, createAgentHistoryPath, ensureSubagentsGitignore, hasAgentHistory, readAgentHistory } from "./agent-history.js";
28
28
  import { createOutputFilePath, streamToOutputFile, writeInitialEntry } from "./output-file.js";
29
29
  import { SubagentScheduler } from "./schedule.js";
30
30
  import { resolveStorePath, ScheduleStore } from "./schedule-store.js";
@@ -640,7 +640,7 @@ export default function (pi: ExtensionAPI) {
640
640
  // everything else; "off" = hide the widget entirely. Read live at render time.
641
641
  let widgetMode: WidgetMode = "background";
642
642
  function getWidgetMode(): WidgetMode { return widgetMode; }
643
- const widget = new AgentWidget(manager, agentActivity, getWidgetMode);
643
+ const widget = new AgentWidget(manager, agentActivity, getWidgetMode, () => currentCtx?.cwd);
644
644
  function setWidgetMode(m: WidgetMode): void { widgetMode = m; widget.update(); }
645
645
 
646
646
  // Claude Code-style FleetView: navigable list of main + subagents below the editor.
@@ -1789,7 +1789,9 @@ Terse command-style prompts produce shallow, generic work.
1789
1789
  }
1790
1790
 
1791
1791
  async function showRunningAgents(ctx: ExtensionCommandContext) {
1792
- const agents = manager.listAgents();
1792
+ const agents = manager.listAgents().filter((record) =>
1793
+ record.session !== undefined || hasAgentHistory(ctx.cwd, record.transcriptPath),
1794
+ );
1793
1795
  if (agents.length === 0) {
1794
1796
  ctx.ui.notify("No agents.", "info");
1795
1797
  return;
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { truncateToWidth } from "@earendil-works/pi-tui";
9
+ import { hasAgentHistory } from "../agent-history.js";
9
10
  import type { AgentManager } from "../agent-manager.js";
10
11
  import { getConfig } from "../agent-types.js";
11
12
  import type { AgentInvocation, SubagentType, WidgetMode } from "../types.js";
@@ -237,6 +238,8 @@ export class AgentWidget {
237
238
  * extension supplies one defaulting to `"background"`.
238
239
  */
239
240
  private mode: () => WidgetMode = () => "all",
241
+ /** Current project cwd used to validate reload-restored transcripts. */
242
+ private getCwd: () => string | undefined = () => undefined,
240
243
  ) {}
241
244
 
242
245
  /**
@@ -251,7 +254,11 @@ export class AgentWidget {
251
254
  * - `all`: every agent.
252
255
  */
253
256
  private widgetAgents() {
254
- const all = this.manager.listAgents();
257
+ const all = this.manager.listAgents().filter((record) => {
258
+ if (record.status === "running" || record.status === "queued") return true;
259
+ const cwd = this.getCwd();
260
+ return cwd !== undefined && hasAgentHistory(cwd, record.transcriptPath);
261
+ });
255
262
  switch (this.mode()) {
256
263
  case "off": return [];
257
264
  case "background": return all.filter(a => a.isBackground !== false);
@@ -11,10 +11,9 @@
11
11
  * can `consume` keys — gated on `getEditorText() === ""` so normal typing is untouched.
12
12
  */
13
13
 
14
- import { existsSync } from "node:fs";
15
14
  import { Editor, isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
16
15
  import type { AgentManager } from "../agent-manager.js";
17
- import { readAgentHistory, resolveAgentHistoryPath } from "../agent-history.js";
16
+ import { hasAgentHistory, readAgentHistory } from "../agent-history.js";
18
17
  import type { AgentRecord } from "../types.js";
19
18
  import { getLifetimeTotal } from "../usage.js";
20
19
  import { type AgentActivity, getDisplayName, type Theme } from "./agent-widget.js";
@@ -182,9 +181,11 @@ export class FleetList {
182
181
 
183
182
  /**
184
183
  * Agents shown in the list, ordered earliest-launched first so the ones you
185
- * started sooner sit at the top. Every row is openable (has a session), so Enter
186
- * never dead-ends. Included: running/queued, plus the agent currently being
187
- * viewed, plus recently-finished ones (they linger briefly before dropping out).
184
+ * started sooner sit at the top. Live rows are openable through their session;
185
+ * terminal rows are included only when their durable transcript exists. This
186
+ * prevents Enter from dead-ending on stale rehydrated metadata.
187
+ * Included: running/queued, plus the agent currently being viewed, plus
188
+ * recently-finished ones (they linger briefly before dropping out).
188
189
  * Pending agents with no session yet are hidden until they start.
189
190
  * (`listAgents()` is newest-first, so we re-sort.)
190
191
  */
@@ -198,10 +199,7 @@ export class FleetList {
198
199
  || (a.completedAt != null && now - a.completedAt < FINISHED_LINGER_MS)
199
200
  );
200
201
  const cwd = this.getCwd();
201
- const historyPath = cwd && a.transcriptPath
202
- ? resolveAgentHistoryPath(cwd, a.transcriptPath)
203
- : undefined;
204
- const history = !!historyPath && existsSync(historyPath);
202
+ const history = cwd !== undefined && hasAgentHistory(cwd, a.transcriptPath);
205
203
  return !!live || history;
206
204
  })
207
205
  .sort((a, b) => a.startedAt - b.startedAt);
@@ -6,6 +6,7 @@ import {
6
6
  agentHistoryLocator,
7
7
  createAgentHistoryPath,
8
8
  ensureSubagentsGitignore,
9
+ hasAgentHistory,
9
10
  readAgentHistory,
10
11
  resolveAgentHistoryPath,
11
12
  } from "../src/agent-history.js";
@@ -48,10 +49,13 @@ describe("project-local subagent history", () => {
48
49
 
49
50
  expect(locator).toBe(".pi-subagents/agent-transcripts/agent-1_Explore_transcript.jsonl");
50
51
  expect(resolveAgentHistoryPath(cwd, locator)).toBe(historyPath);
52
+ expect(hasAgentHistory(cwd, locator)).toBe(false);
53
+ expect(hasAgentHistory(cwd, ".pi-subagents/other.jsonl")).toBe(false);
51
54
  expect(resolveAgentHistoryPath(cwd, ".pi-subagents/other.jsonl")).toBeUndefined();
52
55
  expect(resolveAgentHistoryPath(cwd, ".pi-subagents/agent-transcripts/../other.jsonl")).toBeUndefined();
53
56
 
54
57
  writeInitialEntry(historyPath, "agent-1", "hello", cwd);
58
+ expect(hasAgentHistory(cwd, locator)).toBe(true);
55
59
  const messages = readAgentHistory(cwd, locator);
56
60
  expect(messages).toHaveLength(1);
57
61
  expect(messages?.[0]).toMatchObject({ role: "user", content: "hello" });