@youngjurry/pi-agents 0.8.0 → 0.8.1

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,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.1 - 2026-09-10
4
+
5
+ - Remove automatic legacy-file archival and its full main-session scan; the extension no longer archives or deletes user session data.
6
+ - Retain only the lightweight `codex-agents` → `pi-agents` compatibility migration required for users upgrading directly from 0.7.x; it is a no-op for fresh installations.
7
+
3
8
  ## 0.8.0 - 2026-09-09
4
9
 
5
10
  - Add `/agent-usage`, a read-only overlay that reports separate main/sub-agent token and cache totals plus combined tokens and cost without modifying root context.
package/README.md CHANGED
@@ -189,8 +189,8 @@ The settings file is optional, but spawning requires a model from either the tas
189
189
  - Each root storage group records its owning main-session file in `owner.json`
190
190
  - The former `~/.pi/agent/codex-agents/` directory and `agents-setting.json` filename migrate automatically without overwriting newer files
191
191
  - Resuming an existing main session removes groups whose owning main-session file has been deleted; new sessions and `/reload` do not trigger grouped cleanup
192
- - Legacy flat files are archived only after every ordinary Pi main session has been scanned and no persisted agent-state reference exists
193
192
  - Referenced legacy flat child files are migrated when their main session is resumed
193
+ - The extension never automatically archives or deletes legacy flat files
194
194
  - Parents receive a compact completion notice instead of the full answer; use `list_agents(view="results")` or read the result file on demand
195
195
  - Notices to a busy agent are queued safely: `wait_agent` returns them in its own result, and any leftovers are delivered right after a successful recipient turn
196
196
  - `wait_agent` sends only newly queued mailbox notices to the model; its child status tree excludes the active caller, is folded in the TUI by default, and can be toggled with `Ctrl+O`
package/index.ts CHANGED
@@ -5,7 +5,7 @@ import { Text, truncateToWidth } from "@earendil-works/pi-tui";
5
5
  import { AgentControl } from "./control.ts";
6
6
  import { getAgentSettingsPath, loadAgentSettings, resolveAgentLimits } from "./settings.ts";
7
7
  import { createCollaborationTools } from "./tools.ts";
8
- import { archiveUnownedLegacyFiles, migrateLegacyAgentStorage } from "./storage.ts";
8
+ import { migrateLegacyAgentStorage } from "./storage.ts";
9
9
  import { EXTENSION_ID, ROOT_PATH, type AgentLifecycleStatus, type AgentView } from "./types.ts";
10
10
  import { AgentPickerComponent, AgentTranscriptViewer, AgentUsageViewer, formatAgentUsage } from "./viewer.ts";
11
11
 
@@ -101,7 +101,6 @@ export default function codexAgentsExtension(pi: ExtensionAPI): void {
101
101
  let activeContext: ExtensionContext | undefined;
102
102
  let widgetTui: { requestRender(): void } | undefined;
103
103
  let storageMigrationReported = false;
104
- let legacyArchiveStarted = false;
105
104
 
106
105
  const updateUi = () => {
107
106
  const ctx = activeContext;
@@ -135,18 +134,6 @@ export default function codexAgentsExtension(pi: ExtensionAPI): void {
135
134
  }
136
135
  for (const warning of storageMigration.warnings) ctx.ui.notify(`Agent storage migration: ${warning}`, "warning");
137
136
  }
138
- if (!legacyArchiveStarted) {
139
- legacyArchiveStarted = true;
140
- void archiveUnownedLegacyFiles().then((report) => {
141
- const current = activeContext;
142
- if (!current) return;
143
- if (report.error) {
144
- current.ui.notify(`Legacy agent archive skipped: ${report.error}`, "warning");
145
- } else if (report.archivedFiles > 0) {
146
- current.ui.notify(`Archived ${report.archivedFiles} unowned legacy agent files to ${report.archiveDirectory}.`, "info");
147
- }
148
- });
149
- }
150
137
  const resumedExistingSession = event.reason === "resume"
151
138
  || (event.reason === "startup" && ctx.sessionManager.getEntries().some((entry) => entry.type === "message"));
152
139
  if (resumedExistingSession) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youngjurry/pi-agents",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Persistent in-process Codex-style multi-agent collaboration for Pi",
5
5
  "author": "youngjurry",
6
6
  "type": "module",
package/storage.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { createInterface } from "node:readline";
4
3
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
5
- import { STATE_ENTRY_TYPE } from "./types.ts";
6
4
 
7
5
  const STORAGE_DIRECTORY_NAME = "pi-agents";
8
6
  const LEGACY_STORAGE_DIRECTORY_NAME = "codex-agents";
@@ -14,14 +12,6 @@ export interface StorageMigrationReport {
14
12
  warnings: string[];
15
13
  }
16
14
 
17
- export interface LegacyArchiveReport {
18
- archivedFiles: number;
19
- retainedFiles: number;
20
- scannedMainSessions: number;
21
- archiveDirectory?: string;
22
- error?: string;
23
- }
24
-
25
15
  export function getAgentStorageDirectory(): string {
26
16
  return path.join(getAgentDir(), STORAGE_DIRECTORY_NAME);
27
17
  }
@@ -65,7 +55,10 @@ function mergeWithoutOverwrite(source: string, destination: string, report: Stor
65
55
  }
66
56
  }
67
57
 
68
- /** Move the old package-owned directory without overwriting newer data. */
58
+ /**
59
+ * Preserve the 0.7.x upgrade path. For a fresh installation this is only an
60
+ * existence check and performs no writes.
61
+ */
69
62
  export function migrateLegacyAgentStorage(): StorageMigrationReport {
70
63
  const report: StorageMigrationReport = { movedEntries: 0, warnings: [] };
71
64
  const source = getLegacyAgentStorageDirectory();
@@ -87,161 +80,8 @@ export function resolveMigratedStoragePath(file: string): string {
87
80
  const legacyRoot = path.resolve(getLegacyAgentStorageDirectory());
88
81
  if (source !== legacyRoot && !source.startsWith(`${legacyRoot}${path.sep}`)) return source;
89
82
  // A collision-safe merge leaves the legacy source in place. Prefer the exact
90
- // persisted path whenever it still exists rather than shadowing it with a
91
- // different destination file that happens to share its relative name.
83
+ // persisted path instead of shadowing it with a different destination file.
92
84
  if (fs.existsSync(source)) return source;
93
85
  const translated = path.join(getAgentStorageDirectory(), path.relative(legacyRoot, source));
94
86
  return fs.existsSync(translated) ? translated : source;
95
87
  }
96
-
97
- function listMainSessionFiles(): string[] {
98
- const sessionsRoot = path.join(getAgentDir(), "sessions");
99
- if (!fs.existsSync(sessionsRoot)) return [];
100
- const files: string[] = [];
101
- for (const project of fs.readdirSync(sessionsRoot, { withFileTypes: true })) {
102
- if (!project.isDirectory() && !project.isSymbolicLink()) continue;
103
- const projectDirectory = path.join(sessionsRoot, project.name);
104
- try {
105
- for (const entry of fs.readdirSync(projectDirectory, { withFileTypes: true })) {
106
- if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(path.join(projectDirectory, entry.name));
107
- }
108
- } catch {
109
- // A failed directory scan makes archival unsafe.
110
- throw new Error(`could not scan main session directory: ${projectDirectory}`);
111
- }
112
- }
113
- return files;
114
- }
115
-
116
- function sessionFileIds(file: string): string[] {
117
- const ids = new Set<string>();
118
- const basename = path.basename(file);
119
- const filenameMatch = basename.match(/_([0-9a-f-]{16,})\.jsonl$/i);
120
- if (filenameMatch?.[1]) ids.add(filenameMatch[1]);
121
- let descriptor: number | undefined;
122
- try {
123
- descriptor = fs.openSync(file, "r");
124
- const buffer = Buffer.allocUnsafe(4096);
125
- const bytesRead = fs.readSync(descriptor, buffer, 0, buffer.length, 0);
126
- const firstLine = buffer.subarray(0, bytesRead).toString("utf8").split("\n", 1)[0];
127
- if (firstLine) {
128
- const header = JSON.parse(firstLine) as { type?: unknown; id?: unknown };
129
- if (header.type === "session" && typeof header.id === "string") ids.add(header.id);
130
- }
131
- } catch {
132
- // The filename ID remains usable for conservative reference detection.
133
- } finally {
134
- if (descriptor !== undefined) fs.closeSync(descriptor);
135
- }
136
- return [...ids];
137
- }
138
-
139
- function legacyFlatFiles(): Array<{ file: string; ids: string[] }> {
140
- const roots = [getAgentStorageDirectory(), getLegacyAgentStorageDirectory()];
141
- const files: Array<{ file: string; ids: string[] }> = [];
142
- for (const root of roots) {
143
- for (const kind of ["sessions", "results"] as const) {
144
- const directory = path.join(root, kind);
145
- if (!fs.existsSync(directory)) continue;
146
- for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
147
- if (!entry.isFile()) continue;
148
- if (kind === "sessions" && !entry.name.endsWith(".jsonl")) continue;
149
- if (kind === "results" && !entry.name.endsWith(".md")) continue;
150
- const file = path.join(directory, entry.name);
151
- const ids = kind === "sessions"
152
- ? sessionFileIds(file)
153
- : [path.basename(file, path.extname(file))];
154
- files.push({ file, ids });
155
- }
156
- }
157
- }
158
- return files;
159
- }
160
-
161
- function uniqueArchiveTarget(directory: string, basename: string): string {
162
- let target = path.join(directory, basename);
163
- let suffix = 2;
164
- while (fs.existsSync(target)) {
165
- const extension = path.extname(basename);
166
- const stem = path.basename(basename, extension);
167
- target = path.join(directory, `${stem}-${suffix}${extension}`);
168
- suffix++;
169
- }
170
- return target;
171
- }
172
-
173
- /**
174
- * Archive legacy flat files only after scanning every ordinary Pi session for a
175
- * persisted agent-state reference. Any scan failure leaves all candidates in place.
176
- */
177
- export async function archiveUnownedLegacyFiles(): Promise<LegacyArchiveReport> {
178
- let candidates: Array<{ file: string; ids: string[] }>;
179
- let mainSessions: string[];
180
- try {
181
- candidates = legacyFlatFiles();
182
- if (candidates.length === 0) return { archivedFiles: 0, retainedFiles: 0, scannedMainSessions: 0 };
183
- mainSessions = listMainSessionFiles();
184
- } catch (error) {
185
- return {
186
- archivedFiles: 0,
187
- retainedFiles: 0,
188
- scannedMainSessions: 0,
189
- error: error instanceof Error ? error.message : String(error),
190
- };
191
- }
192
-
193
- const referencedIds = new Set<string>();
194
- const candidateIds = new Set(candidates.flatMap((candidate) => candidate.ids));
195
- let scannedMainSessions = 0;
196
- try {
197
- for (const sessionFile of mainSessions) {
198
- const lines = createInterface({ input: fs.createReadStream(sessionFile, { encoding: "utf8" }), crlfDelay: Infinity });
199
- for await (const line of lines) {
200
- if (!line.includes(STATE_ENTRY_TYPE)) continue;
201
- for (const id of candidateIds) {
202
- if (line.includes(id)) referencedIds.add(id);
203
- }
204
- }
205
- scannedMainSessions++;
206
- }
207
- } catch (error) {
208
- return {
209
- archivedFiles: 0,
210
- retainedFiles: candidates.length,
211
- scannedMainSessions,
212
- error: error instanceof Error ? error.message : String(error),
213
- };
214
- }
215
-
216
- // An unidentifiable file cannot be proven unowned and must remain untouched.
217
- const unowned = candidates.filter((candidate) => candidate.ids.length > 0 && !candidate.ids.some((id) => referencedIds.has(id)));
218
- if (unowned.length === 0) {
219
- return { archivedFiles: 0, retainedFiles: candidates.length, scannedMainSessions };
220
- }
221
- const stamp = new Date().toISOString().replace(/[:.]/g, "-");
222
- const archiveDirectory = path.join(getAgentStorageDirectory(), "archive", "legacy-unowned", stamp);
223
- let archivedFiles = 0;
224
- try {
225
- for (const candidate of unowned) {
226
- const kind = candidate.file.endsWith(".jsonl") ? "sessions" : "results";
227
- const destinationDirectory = path.join(archiveDirectory, kind);
228
- fs.mkdirSync(destinationDirectory, { recursive: true });
229
- fs.renameSync(candidate.file, uniqueArchiveTarget(destinationDirectory, path.basename(candidate.file)));
230
- archivedFiles++;
231
- }
232
- } catch (error) {
233
- return {
234
- archivedFiles,
235
- retainedFiles: candidates.length - archivedFiles,
236
- scannedMainSessions,
237
- archiveDirectory,
238
- error: error instanceof Error ? error.message : String(error),
239
- };
240
- }
241
- return {
242
- archivedFiles,
243
- retainedFiles: candidates.length - archivedFiles,
244
- scannedMainSessions,
245
- archiveDirectory,
246
- };
247
- }