@dyyz1993/pi-coding-agent 0.69.12 → 0.69.14
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/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +13 -11
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -4
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +73 -25
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/server-channel.d.ts +27 -0
- package/dist/core/extensions/server-channel.d.ts.map +1 -0
- package/dist/core/extensions/server-channel.js +40 -0
- package/dist/core/extensions/server-channel.js.map +1 -0
- package/dist/core/extensions/types.d.ts +19 -29
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/file-store/internal-git.d.ts +31 -0
- package/dist/core/file-store/internal-git.d.ts.map +1 -0
- package/dist/core/file-store/internal-git.js +176 -0
- package/dist/core/file-store/internal-git.js.map +1 -0
- package/dist/core/session-manager.d.ts +1 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +7 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +25 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +26 -3
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +33 -2
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +22 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/rules-engine/cache.d.ts +4 -0
- package/dist/rules-engine/cache.d.ts.map +1 -0
- package/dist/rules-engine/cache.js +32 -0
- package/dist/rules-engine/cache.js.map +1 -0
- package/dist/rules-engine/config.d.ts +8 -0
- package/dist/rules-engine/config.d.ts.map +1 -0
- package/dist/rules-engine/config.js +56 -0
- package/dist/rules-engine/config.js.map +1 -0
- package/dist/rules-engine/index.d.ts +10 -0
- package/dist/rules-engine/index.d.ts.map +1 -0
- package/dist/rules-engine/index.js +404 -0
- package/dist/rules-engine/index.js.map +1 -0
- package/dist/rules-engine/injector.d.ts +5 -0
- package/dist/rules-engine/injector.d.ts.map +1 -0
- package/dist/rules-engine/injector.js +57 -0
- package/dist/rules-engine/injector.js.map +1 -0
- package/dist/rules-engine/loader.d.ts +8 -0
- package/dist/rules-engine/loader.d.ts.map +1 -0
- package/dist/rules-engine/loader.js +190 -0
- package/dist/rules-engine/loader.js.map +1 -0
- package/dist/rules-engine/matcher.d.ts +3 -0
- package/dist/rules-engine/matcher.d.ts.map +1 -0
- package/dist/rules-engine/matcher.js +48 -0
- package/dist/rules-engine/matcher.js.map +1 -0
- package/dist/rules-engine/types.d.ts +150 -0
- package/dist/rules-engine/types.d.ts.map +1 -0
- package/dist/rules-engine/types.js +2 -0
- package/dist/rules-engine/types.js.map +1 -0
- package/docs/extensions.md +56 -56
- package/docs/file-rollback-design.md +287 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/file-snapshot.ts +407 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
realpathSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { dirname, join, relative } from "node:path";
|
|
13
|
+
import type { ExtensionAPI, ExtensionContext, SessionTreeEvent, TurnEndEvent } from "@dyyz1993/pi-coding-agent";
|
|
14
|
+
|
|
15
|
+
function fnv1a(data: string): string {
|
|
16
|
+
let hash = 0x811c9dc5;
|
|
17
|
+
for (let i = 0; i < data.length; i++) {
|
|
18
|
+
hash ^= data.charCodeAt(i);
|
|
19
|
+
hash = Math.imul(hash, 0x01000193);
|
|
20
|
+
}
|
|
21
|
+
return hash.toString(16).padStart(8, "0");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DEFAULT_IGNORE_PATTERNS = [
|
|
25
|
+
"node_modules",
|
|
26
|
+
".git",
|
|
27
|
+
".pi",
|
|
28
|
+
"dist",
|
|
29
|
+
"build",
|
|
30
|
+
".DS_Store",
|
|
31
|
+
"__pycache__",
|
|
32
|
+
".next",
|
|
33
|
+
".nuxt",
|
|
34
|
+
"target",
|
|
35
|
+
".gradle",
|
|
36
|
+
".idea",
|
|
37
|
+
".vscode",
|
|
38
|
+
"*.swp",
|
|
39
|
+
"*.swo",
|
|
40
|
+
"*.pyc",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function matchGlob(name: string, pattern: string): boolean {
|
|
44
|
+
if (pattern.startsWith("*.")) {
|
|
45
|
+
return name.endsWith(pattern.slice(1));
|
|
46
|
+
}
|
|
47
|
+
if (pattern.endsWith("/")) {
|
|
48
|
+
return name === pattern.slice(0, -1);
|
|
49
|
+
}
|
|
50
|
+
return name === pattern;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function shouldIgnore(relPath: string, extraPatterns: string[]): boolean {
|
|
54
|
+
const parts = relPath.split("/");
|
|
55
|
+
for (const part of parts) {
|
|
56
|
+
for (const pattern of DEFAULT_IGNORE_PATTERNS) {
|
|
57
|
+
if (matchGlob(part, pattern)) return true;
|
|
58
|
+
}
|
|
59
|
+
for (const pattern of extraPatterns) {
|
|
60
|
+
const trimmed = pattern.trim();
|
|
61
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
62
|
+
if (trimmed.startsWith("!")) continue;
|
|
63
|
+
if (matchGlob(part, trimmed.replace(/^\/+/, ""))) return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findCanonicalGitRoot(cwd: string): string | null {
|
|
70
|
+
let dir = realpathSync(cwd);
|
|
71
|
+
for (;;) {
|
|
72
|
+
const gitPath = join(dir, ".git");
|
|
73
|
+
if (!existsSync(gitPath)) {
|
|
74
|
+
const parent = dirname(dir);
|
|
75
|
+
if (parent === dir) return null;
|
|
76
|
+
dir = parent;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const stat = lstatSync(gitPath);
|
|
80
|
+
if (stat.isDirectory()) return dir;
|
|
81
|
+
if (stat.isFile()) {
|
|
82
|
+
const content = readFileSync(gitPath, "utf-8").trim();
|
|
83
|
+
const match = content.match(/^gitdir:\s*(.+)/);
|
|
84
|
+
if (!match) return null;
|
|
85
|
+
const gitdir = match[1]!.trim();
|
|
86
|
+
if (gitdir.includes("/worktrees/")) {
|
|
87
|
+
const commonPrefix = gitdir.replace(/\/worktrees\/[^/]+\/?$/, "");
|
|
88
|
+
let rootDir = commonPrefix;
|
|
89
|
+
if (rootDir.endsWith("/.git")) rootDir = rootDir.slice(0, -4);
|
|
90
|
+
if (!existsSync(join(rootDir, ".git"))) return null;
|
|
91
|
+
return realpathSync(rootDir);
|
|
92
|
+
}
|
|
93
|
+
const parent = dirname(gitdir);
|
|
94
|
+
if (!existsSync(parent)) return null;
|
|
95
|
+
return parent;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class ObjectStore {
|
|
102
|
+
private readonly objectsDir: string;
|
|
103
|
+
|
|
104
|
+
constructor(storeDir: string) {
|
|
105
|
+
this.objectsDir = join(storeDir, "objects");
|
|
106
|
+
mkdirSync(this.objectsDir, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
writeObject(content: string): string {
|
|
110
|
+
const hash = fnv1a(content);
|
|
111
|
+
const prefix = hash.slice(0, 2);
|
|
112
|
+
const suffix = hash.slice(2);
|
|
113
|
+
const dir = join(this.objectsDir, prefix);
|
|
114
|
+
const file = join(dir, suffix);
|
|
115
|
+
if (!existsSync(file)) {
|
|
116
|
+
mkdirSync(dir, { recursive: true });
|
|
117
|
+
writeFileSync(file, content, "utf-8");
|
|
118
|
+
}
|
|
119
|
+
return hash;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
readObject(hash: string): string {
|
|
123
|
+
return readFileSync(join(this.objectsDir, hash.slice(0, 2), hash.slice(2)), "utf-8");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
hasObject(hash: string): boolean {
|
|
127
|
+
return existsSync(join(this.objectsDir, hash.slice(0, 2), hash.slice(2)));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
scanWorkingDir(cwd: string): Map<string, string> {
|
|
131
|
+
const gitignorePatterns: string[] = [];
|
|
132
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
133
|
+
if (existsSync(gitignorePath)) {
|
|
134
|
+
try {
|
|
135
|
+
gitignorePatterns.push(...readFileSync(gitignorePath, "utf-8").split(/\r?\n/));
|
|
136
|
+
} catch {}
|
|
137
|
+
}
|
|
138
|
+
const result = new Map<string, string>();
|
|
139
|
+
this.scanDir(cwd, cwd, gitignorePatterns, result);
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private scanDir(dir: string, root: string, extraPatterns: string[], result: Map<string, string>): void {
|
|
144
|
+
let entries: ReturnType<typeof readdirSync>;
|
|
145
|
+
try {
|
|
146
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
147
|
+
} catch {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
const fullPath = join(dir, entry.name);
|
|
152
|
+
const relPath = relative(root, fullPath);
|
|
153
|
+
if (entry.isDirectory()) {
|
|
154
|
+
if (shouldIgnore(`${relPath}/`, extraPatterns)) continue;
|
|
155
|
+
this.scanDir(fullPath, root, extraPatterns, result);
|
|
156
|
+
} else if (entry.isFile()) {
|
|
157
|
+
if (shouldIgnore(relPath, extraPatterns)) continue;
|
|
158
|
+
try {
|
|
159
|
+
const stat = lstatSync(fullPath);
|
|
160
|
+
if (stat.size > 1024 * 1024) continue;
|
|
161
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
162
|
+
result.set(relPath, content);
|
|
163
|
+
} catch {}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
writeTree(files: Map<string, string>): string {
|
|
169
|
+
const entries: Array<{ path: string; hash: string }> = [];
|
|
170
|
+
for (const [path, content] of files) {
|
|
171
|
+
const hash = this.writeObject(content);
|
|
172
|
+
entries.push({ path, hash });
|
|
173
|
+
}
|
|
174
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
175
|
+
const treeData = entries.map((e) => `${e.path}\0${e.hash}`).join("\n");
|
|
176
|
+
return this.writeObject(treeData);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
readTree(treeHash: string): Map<string, string> {
|
|
180
|
+
const treeData = this.readObject(treeHash);
|
|
181
|
+
const files = new Map<string, string>();
|
|
182
|
+
for (const line of treeData.split("\n")) {
|
|
183
|
+
if (!line) continue;
|
|
184
|
+
const sep = line.indexOf("\0");
|
|
185
|
+
if (sep === -1) continue;
|
|
186
|
+
const path = line.slice(0, sep);
|
|
187
|
+
const hash = line.slice(sep + 1);
|
|
188
|
+
if (this.hasObject(hash)) {
|
|
189
|
+
files.set(path, this.readObject(hash));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return files;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
parseTreeEntries(treeHash: string): Map<string, string> {
|
|
196
|
+
const treeData = this.readObject(treeHash);
|
|
197
|
+
const entries = new Map<string, string>();
|
|
198
|
+
for (const line of treeData.split("\n")) {
|
|
199
|
+
if (!line) continue;
|
|
200
|
+
const sep = line.indexOf("\0");
|
|
201
|
+
if (sep === -1) continue;
|
|
202
|
+
entries.set(line.slice(0, sep), line.slice(sep + 1));
|
|
203
|
+
}
|
|
204
|
+
return entries;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
computeTreeDiff(
|
|
208
|
+
oldTreeHash: string | null,
|
|
209
|
+
newTreeHash: string,
|
|
210
|
+
): { added: string[]; modified: string[]; deleted: string[] } {
|
|
211
|
+
const oldEntries = oldTreeHash ? this.parseTreeEntries(oldTreeHash) : new Map<string, string>();
|
|
212
|
+
const newEntries = this.parseTreeEntries(newTreeHash);
|
|
213
|
+
|
|
214
|
+
const added: string[] = [];
|
|
215
|
+
const modified: string[] = [];
|
|
216
|
+
const deleted: string[] = [];
|
|
217
|
+
|
|
218
|
+
for (const [path, hash] of newEntries) {
|
|
219
|
+
const old = oldEntries.get(path);
|
|
220
|
+
if (!old) added.push(path);
|
|
221
|
+
else if (old !== hash) modified.push(path);
|
|
222
|
+
}
|
|
223
|
+
for (const path of oldEntries.keys()) {
|
|
224
|
+
if (!newEntries.has(path)) deleted.push(path);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return { added: added.sort(), modified: modified.sort(), deleted: deleted.sort() };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
interface StepSnapshot {
|
|
232
|
+
baselineTreeHash: string | null;
|
|
233
|
+
snapshotTreeHash: string;
|
|
234
|
+
diff: { added: string[]; modified: string[]; deleted: string[] } | null;
|
|
235
|
+
turnIndex: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
interface UnrevertPoint {
|
|
239
|
+
preRollbackTreeHash: string;
|
|
240
|
+
rolledBackToLeaf: string;
|
|
241
|
+
restoredFiles: string[];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getStoreRoot(): string {
|
|
245
|
+
return join(homedir(), ".pi", "agent", "file-store");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export default function fileSnapshot(pi: ExtensionAPI) {
|
|
249
|
+
let store: ObjectStore | null = null;
|
|
250
|
+
let sessionStartTreeHash: string | null = null;
|
|
251
|
+
let lastCommittedTreeHash: string | null = null;
|
|
252
|
+
let turnIndex = 0;
|
|
253
|
+
let _sessionBaselinePaths: Set<string> = new Set();
|
|
254
|
+
|
|
255
|
+
function getStore(ctx: ExtensionContext): ObjectStore {
|
|
256
|
+
if (!store) {
|
|
257
|
+
const projectRoot = findCanonicalGitRoot(ctx.cwd) ?? ctx.cwd;
|
|
258
|
+
const projectHash = fnv1a(projectRoot);
|
|
259
|
+
store = new ObjectStore(join(getStoreRoot(), projectHash));
|
|
260
|
+
}
|
|
261
|
+
return store;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => {
|
|
265
|
+
store = null;
|
|
266
|
+
sessionStartTreeHash = null;
|
|
267
|
+
lastCommittedTreeHash = null;
|
|
268
|
+
turnIndex = 0;
|
|
269
|
+
const s = getStore(ctx);
|
|
270
|
+
const files = s.scanWorkingDir(ctx.cwd);
|
|
271
|
+
_sessionBaselinePaths = new Set(files.keys());
|
|
272
|
+
if (files.size > 0) {
|
|
273
|
+
sessionStartTreeHash = s.writeTree(files);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
pi.on("turn_end", async (_event: TurnEndEvent, ctx: ExtensionContext) => {
|
|
278
|
+
const s = getStore(ctx);
|
|
279
|
+
const files = s.scanWorkingDir(ctx.cwd);
|
|
280
|
+
const snapshotTreeHash = s.writeTree(files);
|
|
281
|
+
|
|
282
|
+
const compareTo = lastCommittedTreeHash ?? sessionStartTreeHash;
|
|
283
|
+
const isFirstSnapshot = !lastCommittedTreeHash && files.size > 0;
|
|
284
|
+
const diff = compareTo ? s.computeTreeDiff(compareTo, snapshotTreeHash || "") : null;
|
|
285
|
+
const hasChanges = diff && (diff.added.length > 0 || diff.modified.length > 0 || diff.deleted.length > 0);
|
|
286
|
+
|
|
287
|
+
if (hasChanges || isFirstSnapshot) {
|
|
288
|
+
pi.appendEntry("step-snapshot", {
|
|
289
|
+
baselineTreeHash: compareTo,
|
|
290
|
+
snapshotTreeHash,
|
|
291
|
+
diff: hasChanges ? diff : null,
|
|
292
|
+
turnIndex,
|
|
293
|
+
} satisfies StepSnapshot);
|
|
294
|
+
lastCommittedTreeHash = snapshotTreeHash || null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
turnIndex++;
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
pi.on("session_tree", async (event: SessionTreeEvent, ctx: ExtensionContext) => {
|
|
301
|
+
const targetId = event.newLeafId;
|
|
302
|
+
const s = getStore(ctx);
|
|
303
|
+
const entries = ctx.sessionManager.getEntries();
|
|
304
|
+
|
|
305
|
+
let targetTreeHash: string | null;
|
|
306
|
+
let currentTreeHash2: string | null;
|
|
307
|
+
|
|
308
|
+
if (!targetId) {
|
|
309
|
+
targetTreeHash = sessionStartTreeHash ?? null;
|
|
310
|
+
const currentSnapshot = findLatestSnapshotOnPath(entries, event.oldLeafId);
|
|
311
|
+
currentTreeHash2 = currentSnapshot?.snapshotTreeHash ?? null;
|
|
312
|
+
} else {
|
|
313
|
+
const targetEntry = entries.find((e) => e.id === targetId);
|
|
314
|
+
const isRootTarget = targetEntry && !targetEntry.parentId;
|
|
315
|
+
|
|
316
|
+
const targetSnapshot = isRootTarget ? null : findLatestSnapshotOnPath(entries, targetId);
|
|
317
|
+
const currentSnapshot = findLatestSnapshotOnPath(entries, event.oldLeafId);
|
|
318
|
+
|
|
319
|
+
targetTreeHash = targetSnapshot?.snapshotTreeHash ?? sessionStartTreeHash ?? null;
|
|
320
|
+
currentTreeHash2 = currentSnapshot?.snapshotTreeHash ?? null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (targetTreeHash === currentTreeHash2) return;
|
|
324
|
+
|
|
325
|
+
const targetFiles = targetTreeHash ? s.readTree(targetTreeHash) : new Map<string, string>();
|
|
326
|
+
const currentFiles = currentTreeHash2 ? s.readTree(currentTreeHash2) : new Map<string, string>();
|
|
327
|
+
|
|
328
|
+
const toRestore = new Map<string, string>();
|
|
329
|
+
for (const [path, content] of targetFiles) {
|
|
330
|
+
const current = currentFiles.get(path);
|
|
331
|
+
if (current !== content) {
|
|
332
|
+
toRestore.set(path, content);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const toDelete: string[] = [];
|
|
337
|
+
for (const path of currentFiles.keys()) {
|
|
338
|
+
if (!targetFiles.has(path)) {
|
|
339
|
+
toDelete.push(path);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (toRestore.size === 0 && toDelete.length === 0) return;
|
|
344
|
+
|
|
345
|
+
const preRollbackFiles = s.scanWorkingDir(ctx.cwd);
|
|
346
|
+
const preRollbackTreeHash = preRollbackFiles.size > 0 ? s.writeTree(preRollbackFiles) : "";
|
|
347
|
+
|
|
348
|
+
pi.appendEntry("unrevert-point", {
|
|
349
|
+
preRollbackTreeHash,
|
|
350
|
+
rolledBackToLeaf: targetId ?? "",
|
|
351
|
+
restoredFiles: [...toRestore.keys()],
|
|
352
|
+
} satisfies UnrevertPoint);
|
|
353
|
+
|
|
354
|
+
restoreFiles(ctx.cwd, toRestore);
|
|
355
|
+
deleteFiles(ctx.cwd, toDelete);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function restoreFiles(cwd: string, toRestore: Map<string, string>): void {
|
|
360
|
+
for (const [path, content] of toRestore) {
|
|
361
|
+
const absPath = join(cwd, path);
|
|
362
|
+
mkdirSync(join(absPath, ".."), { recursive: true });
|
|
363
|
+
writeFileSync(absPath, content, "utf-8");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function deleteFiles(cwd: string, paths: string[]): void {
|
|
368
|
+
for (const path of paths) {
|
|
369
|
+
const absPath = join(cwd, path);
|
|
370
|
+
if (existsSync(absPath)) {
|
|
371
|
+
rmSync(absPath, { force: true });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function findLatestSnapshotOnPath(
|
|
377
|
+
entries: Array<{ id: string; parentId: string | null; type: string; customType?: string; data?: unknown }>,
|
|
378
|
+
leafId: string | null,
|
|
379
|
+
): StepSnapshot | null {
|
|
380
|
+
if (!leafId) return null;
|
|
381
|
+
|
|
382
|
+
const byId = new Map(entries.map((e) => [e.id, e]));
|
|
383
|
+
const snapshots: StepSnapshot[] = [];
|
|
384
|
+
|
|
385
|
+
for (const entry of entries) {
|
|
386
|
+
if (entry.type !== "custom" || entry.customType !== "step-snapshot") continue;
|
|
387
|
+
if (!isOnPathTo(byId, leafId, entry.id)) continue;
|
|
388
|
+
snapshots.push(entry.data as StepSnapshot);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return snapshots.length > 0 ? snapshots[snapshots.length - 1] : null;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function isOnPathTo(
|
|
395
|
+
byId: Map<string, { id: string; parentId: string | null }>,
|
|
396
|
+
startId: string,
|
|
397
|
+
targetId: string,
|
|
398
|
+
): boolean {
|
|
399
|
+
let current: string | null = startId;
|
|
400
|
+
while (current !== null) {
|
|
401
|
+
if (current === targetId) return true;
|
|
402
|
+
const entry = byId.get(current);
|
|
403
|
+
if (!entry) break;
|
|
404
|
+
current = entry.parentId;
|
|
405
|
+
}
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-with-deps",
|
|
3
|
-
"version": "0.69.
|
|
3
|
+
"version": "0.69.11",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-with-deps",
|
|
9
|
-
"version": "0.69.
|
|
9
|
+
"version": "0.69.11",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dyyz1993/pi-coding-agent",
|
|
3
|
-
"version": "0.69.
|
|
3
|
+
"version": "0.69.14",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@mariozechner/jiti": "^2.6.2",
|
|
43
|
-
"@dyyz1993/pi-agent-core": "^0.69.
|
|
44
|
-
"@dyyz1993/pi-ai": "^0.69.
|
|
45
|
-
"@dyyz1993/pi-tui": "^0.69.
|
|
43
|
+
"@dyyz1993/pi-agent-core": "^0.69.14",
|
|
44
|
+
"@dyyz1993/pi-ai": "^0.69.14",
|
|
45
|
+
"@dyyz1993/pi-tui": "^0.69.14",
|
|
46
46
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
|
47
47
|
"typebox": "^1.1.24",
|
|
48
48
|
"chalk": "^5.5.0",
|