shariq-pi-extensions 0.2.15 → 0.2.16
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/docs/EXTENSIONS.md
CHANGED
|
@@ -53,6 +53,8 @@ Replaces standard context compaction with a defensive, high-fidelity continuity
|
|
|
53
53
|
Key capabilities include:
|
|
54
54
|
- **Fail-Closed Validation**: Strictly enforces `stopReason === "stop"`, rejects tool calls and length-truncated output, and requires all 6 section headings.
|
|
55
55
|
- **Deterministic State Ledger (Schema v3)**: Machine-readable tracking of `touchedReadFiles`, `touchedModifiedFiles`, and asynchronous NUL-delimited Git worktree parsing capturing `activeDirtyFiles`, staged diffs, unstaged diffs, and untracked file previews in `CompactionEntry.details` and `<uncommitted-diff>` context.
|
|
56
|
+
- **Lockfile & Bundle Diff Filtering**: Automatically isolates `package-lock.json`, `Cargo.lock`, `yarn.lock`, and minified assets from raw diffs to preserve token budgets for source code logic.
|
|
57
|
+
- **Active Background Terminal Awareness**: Automatically identifies running background processes and records them under `<active-background-processes>` to prevent duplicate server launches.
|
|
56
58
|
- **Hierarchical Delta-Merging**: Carries forward immutable goals and user constraints across 10+ compaction cycles while condensing older completed items to prevent summary bloat.
|
|
57
59
|
- **Classified Retry Ladder**: Distinguishes non-retryable fatal auth/quota errors from transient reasoning/length limits (retrying with reasoning off) and falling back to the active session model.
|
|
58
60
|
- **Two-Ended Truncation & Credential Redaction**: Retains both head and tail of tool outputs (ensuring final error traces and test results survive) while redacting secrets and sensitive paths.
|
|
@@ -48,6 +48,10 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
48
48
|
const getManager = (): TerminalManager => {
|
|
49
49
|
if (manager) return manager;
|
|
50
50
|
manager = new TerminalManager();
|
|
51
|
+
(globalThis as any).__pi_get_active_terminals = () => {
|
|
52
|
+
if (!manager) return [];
|
|
53
|
+
return manager.list().filter((s) => s.status === "running").map((s) => `${s.id}: "${oneLine(s.title)}" (pid ${s.pid})`);
|
|
54
|
+
};
|
|
51
55
|
manager.setOnSettled((snapshot) => {
|
|
52
56
|
if (!modelOwned.delete(snapshot.id)) {
|
|
53
57
|
ui?.notify(
|
|
@@ -393,4 +397,8 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
393
397
|
description: "Open the background terminal control center",
|
|
394
398
|
handler: async (_args, ctx) => openCommand(ctx),
|
|
395
399
|
});
|
|
400
|
+
|
|
401
|
+
pi.on("session_shutdown", () => {
|
|
402
|
+
delete (globalThis as any).__pi_get_active_terminals;
|
|
403
|
+
});
|
|
396
404
|
}
|
|
@@ -18,11 +18,13 @@ When long-running agent sessions reach context thresholds, standard compaction f
|
|
|
18
18
|
4. **💥 Errors, Root Causes & Fixes** — Full error traces, root cause diagnostics, and verified solutions.
|
|
19
19
|
5. **🧠 Key Decisions & Hypotheses** — Architectural choices, trade-offs, and discarded hypotheses.
|
|
20
20
|
6. **📍 Resume Anchor & Immediate Next Action** — Verbatim quote or exact resume state with the single immediate next action.
|
|
21
|
-
7. **📂 Deterministic Engineering Ledger** — Programmatic `<read-files>`, `<touched-files>`, `<uncommitted-dirty-files>`, and bounded `<uncommitted-diff>` blocks.
|
|
21
|
+
7. **📂 Deterministic Engineering Ledger** — Programmatic `<read-files>`, `<touched-files>`, `<uncommitted-dirty-files>`, `<modified-lockfiles-and-assets>`, `<active-background-processes>`, and bounded `<uncommitted-diff>` blocks. Lockfiles and minified bundles are automatically excluded from raw diffing to preserve token budgets for real source code, while active background terminals/daemons are recorded to prevent port conflicts.
|
|
22
22
|
|
|
23
23
|
## Defensive Reliability & Multi-Stage Retry Ladder
|
|
24
24
|
|
|
25
25
|
- **Fail-Closed Validation**: Accepts only `stopReason === "stop"` and rejects tool calls, empty output, or summaries missing required section headers.
|
|
26
|
+
- **Lockfile & Bundle Diff Exclusion**: Automatically excludes `package-lock.json`, `Cargo.lock`, `yarn.lock`, `pnpm-lock.yaml`, and minified assets from raw diffs, recording their status under `<modified-lockfiles-and-assets>` to preserve 100% of diff token headroom for source code.
|
|
27
|
+
- **Background Daemon & Terminal Awareness**: Automatically detects running background terminals/processes and injects their status into `<active-background-processes>` so the successor agent never launches duplicate services.
|
|
26
28
|
- **Retry Ladder**: If an attempt encounters output limits or transient reasoning timeouts:
|
|
27
29
|
1. Primary configured model with requested reasoning.
|
|
28
30
|
2. Primary model with reasoning off (unblocks reasoning/token caps).
|
|
@@ -25,6 +25,7 @@ export interface GitEngineeringState {
|
|
|
25
25
|
files: DirtyFileState[];
|
|
26
26
|
patch: string;
|
|
27
27
|
sensitiveFilesOmitted: number;
|
|
28
|
+
lockfilesAndGeneratedAssets: string[];
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export interface SmartCompactionDetails {
|
|
@@ -41,6 +42,8 @@ export interface SmartCompactionDetails {
|
|
|
41
42
|
dirtyStateAvailable: boolean;
|
|
42
43
|
sensitiveDirtyFilesOmitted: number;
|
|
43
44
|
sensitiveTouchedFilesOmitted: number;
|
|
45
|
+
activeBackgroundProcesses?: string[];
|
|
46
|
+
lockfilesAndGeneratedAssets?: string[];
|
|
44
47
|
cycleCount: number;
|
|
45
48
|
timestamp: number;
|
|
46
49
|
}
|
|
@@ -124,6 +127,42 @@ export function extractPriorFileState(branchEntries?: any[]): {
|
|
|
124
127
|
return { touchedReadFiles, touchedModifiedFiles, cycleCount };
|
|
125
128
|
}
|
|
126
129
|
|
|
130
|
+
const GENERATED_OR_LOCKFILE_PATTERNS = [
|
|
131
|
+
/(?:^|\/)package-lock\.json$/i,
|
|
132
|
+
/(?:^|\/)pnpm-lock\.yaml$/i,
|
|
133
|
+
/(?:^|\/)yarn\.lock$/i,
|
|
134
|
+
/(?:^|\/)Cargo\.lock$/i,
|
|
135
|
+
/(?:^|\/)poetry\.lock$/i,
|
|
136
|
+
/(?:^|\/)bun\.lockb?$/i,
|
|
137
|
+
/(?:^|\/)composer\.lock$/i,
|
|
138
|
+
/(?:^|\/)flake\.lock$/i,
|
|
139
|
+
/(?:^|\/)mise\.lock$/i,
|
|
140
|
+
/\.min\.(?:js|css|mjs)$/i,
|
|
141
|
+
/\.map$/i,
|
|
142
|
+
/\.wasm$/i,
|
|
143
|
+
/(?:^|\/)(?:dist|build|out|\.next|\.nuxt|\.turbo|\.parcel-cache)\//i,
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
export function isGeneratedOrLockfile(filePath: string): boolean {
|
|
147
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
148
|
+
return GENERATED_OR_LOCKFILE_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function getActiveBackgroundProcesses(): string[] {
|
|
152
|
+
try {
|
|
153
|
+
const fn = (globalThis as any).__pi_get_active_terminals;
|
|
154
|
+
if (typeof fn === "function") {
|
|
155
|
+
const active = fn();
|
|
156
|
+
if (Array.isArray(active)) {
|
|
157
|
+
return active.filter((item): item is string => typeof item === "string" && Boolean(item.trim()));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
// Best-effort inspection.
|
|
162
|
+
}
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
|
|
127
166
|
const execFileAsync = promisify(execFile);
|
|
128
167
|
const GIT_TIMEOUT_MS = 5_000;
|
|
129
168
|
const GIT_OUTPUT_LIMIT = 2 * 1024 * 1024;
|
|
@@ -170,7 +209,7 @@ async function readUntrackedPreviews(
|
|
|
170
209
|
const sections: string[] = [];
|
|
171
210
|
let remaining = DIRTY_PATCH_CHARS;
|
|
172
211
|
for (const file of files) {
|
|
173
|
-
if (file.status !== "??" || isSensitivePath(file.path) || remaining <= 0) continue;
|
|
212
|
+
if (file.status !== "??" || isSensitivePath(file.path) || isGeneratedOrLockfile(file.path) || remaining <= 0) continue;
|
|
174
213
|
const absolute = path.resolve(root, file.path);
|
|
175
214
|
const relative = path.relative(root, absolute);
|
|
176
215
|
if (relative.startsWith("..") || path.isAbsolute(relative)) continue;
|
|
@@ -201,19 +240,23 @@ async function readUntrackedPreviews(
|
|
|
201
240
|
}
|
|
202
241
|
|
|
203
242
|
export async function getGitEngineeringState(cwd?: string, signal?: AbortSignal): Promise<GitEngineeringState> {
|
|
204
|
-
if (!cwd) return { available: false, files: [], patch: "", sensitiveFilesOmitted: 0 };
|
|
243
|
+
if (!cwd) return { available: false, files: [], patch: "", sensitiveFilesOmitted: 0, lockfilesAndGeneratedAssets: [] };
|
|
205
244
|
try {
|
|
206
245
|
const root = (await runGit(cwd, ["rev-parse", "--show-toplevel"], signal)).trim();
|
|
207
246
|
const status = await runGit(root, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], signal);
|
|
208
247
|
const allFiles = parseGitStatusPorcelainV1Z(status);
|
|
209
248
|
const sensitiveFilesOmitted = allFiles.filter((file) => isSensitivePath(file.path)).length;
|
|
210
249
|
const files = allFiles.filter((file) => !isSensitivePath(file.path));
|
|
211
|
-
|
|
250
|
+
|
|
251
|
+
const codeFiles = files.filter((file) => !isGeneratedOrLockfile(file.path));
|
|
252
|
+
const lockOrGeneratedFiles = files.filter((file) => isGeneratedOrLockfile(file.path)).map((file) => file.path);
|
|
253
|
+
|
|
254
|
+
const trackedCodePaths = codeFiles.filter((file) => file.status !== "??").map((file) => file.path).slice(0, 250);
|
|
212
255
|
const stagedArgs = ["diff", "--cached", "--no-ext-diff", "--no-color", "--unified=2"];
|
|
213
256
|
const unstagedArgs = ["diff", "--no-ext-diff", "--no-color", "--unified=2"];
|
|
214
|
-
if (
|
|
215
|
-
stagedArgs.push("--", ...
|
|
216
|
-
unstagedArgs.push("--", ...
|
|
257
|
+
if (trackedCodePaths.length > 0) {
|
|
258
|
+
stagedArgs.push("--", ...trackedCodePaths);
|
|
259
|
+
unstagedArgs.push("--", ...trackedCodePaths);
|
|
217
260
|
} else {
|
|
218
261
|
// An unmatched pathspec avoids reading unrelated or sensitive tracked diffs.
|
|
219
262
|
stagedArgs.push("--", ":(exclude,top)**");
|
|
@@ -222,7 +265,7 @@ export async function getGitEngineeringState(cwd?: string, signal?: AbortSignal)
|
|
|
222
265
|
const [staged, unstaged, untracked] = await Promise.all([
|
|
223
266
|
runGit(root, stagedArgs, signal),
|
|
224
267
|
runGit(root, unstagedArgs, signal),
|
|
225
|
-
readUntrackedPreviews(root,
|
|
268
|
+
readUntrackedPreviews(root, codeFiles),
|
|
226
269
|
]);
|
|
227
270
|
const sections = [
|
|
228
271
|
staged ? `## Staged changes\n${staged}` : "",
|
|
@@ -234,10 +277,11 @@ export async function getGitEngineeringState(cwd?: string, signal?: AbortSignal)
|
|
|
234
277
|
files,
|
|
235
278
|
patch: truncatePatch(redactLikelySecrets(sections.join("\n\n"))),
|
|
236
279
|
sensitiveFilesOmitted,
|
|
280
|
+
lockfilesAndGeneratedAssets: lockOrGeneratedFiles,
|
|
237
281
|
};
|
|
238
282
|
} catch (error) {
|
|
239
283
|
if (signal?.aborted) throw error;
|
|
240
|
-
return { available: false, files: [], patch: "", sensitiveFilesOmitted: 0 };
|
|
284
|
+
return { available: false, files: [], patch: "", sensitiveFilesOmitted: 0, lockfilesAndGeneratedAssets: [] };
|
|
241
285
|
}
|
|
242
286
|
}
|
|
243
287
|
|
|
@@ -519,6 +563,7 @@ export async function runSmartCompaction(
|
|
|
519
563
|
const touchedModifiedFilesList = allTouchedModifiedFiles.filter((file) => !isSensitivePath(file)).sort();
|
|
520
564
|
const gitState = await getGitEngineeringState(ctx.cwd, signal);
|
|
521
565
|
const activeDirtyFilesList = gitState.files.map((file) => file.path);
|
|
566
|
+
const activeBackgroundProcesses = getActiveBackgroundProcesses();
|
|
522
567
|
|
|
523
568
|
const fileOpsXml = formatFileOperationsXml({
|
|
524
569
|
readFiles: readFilesList,
|
|
@@ -527,6 +572,8 @@ export async function runSmartCompaction(
|
|
|
527
572
|
dirtyPatch: gitState.patch,
|
|
528
573
|
dirtyStateAvailable: gitState.available,
|
|
529
574
|
sensitiveFilesOmitted: gitState.sensitiveFilesOmitted + sensitiveTouchedFilesOmitted,
|
|
575
|
+
activeBackgroundProcesses,
|
|
576
|
+
lockfilesAndGeneratedAssets: gitState.lockfilesAndGeneratedAssets,
|
|
530
577
|
});
|
|
531
578
|
|
|
532
579
|
const finalSummary = `${finalSummaryText}${fileOpsXml}`;
|
|
@@ -546,6 +593,8 @@ export async function runSmartCompaction(
|
|
|
546
593
|
dirtyStateAvailable: gitState.available,
|
|
547
594
|
sensitiveDirtyFilesOmitted: gitState.sensitiveFilesOmitted,
|
|
548
595
|
sensitiveTouchedFilesOmitted,
|
|
596
|
+
activeBackgroundProcesses: activeBackgroundProcesses.length > 0 ? activeBackgroundProcesses : undefined,
|
|
597
|
+
lockfilesAndGeneratedAssets: gitState.lockfilesAndGeneratedAssets.length > 0 ? gitState.lockfilesAndGeneratedAssets : undefined,
|
|
549
598
|
cycleCount,
|
|
550
599
|
timestamp: Date.now(),
|
|
551
600
|
};
|
|
@@ -229,15 +229,21 @@ export function formatFileOperationsXml(options?: {
|
|
|
229
229
|
dirtyPatch?: string;
|
|
230
230
|
dirtyStateAvailable?: boolean;
|
|
231
231
|
sensitiveFilesOmitted?: number;
|
|
232
|
+
activeBackgroundProcesses?: Iterable<string>;
|
|
233
|
+
lockfilesAndGeneratedAssets?: Iterable<string>;
|
|
232
234
|
}): string {
|
|
233
235
|
if (!options) return "";
|
|
234
236
|
const readSet = new Set(options.readFiles ?? []);
|
|
235
237
|
const touchedSet = new Set(options.touchedModifiedFiles ?? []);
|
|
236
238
|
const dirtySet = new Set(options.activeDirtyFiles ?? []);
|
|
239
|
+
const backgroundSet = new Set(options.activeBackgroundProcesses ?? []);
|
|
240
|
+
const lockfilesSet = new Set(options.lockfilesAndGeneratedAssets ?? []);
|
|
237
241
|
|
|
238
242
|
const readOnly = [...readSet].filter((f) => !touchedSet.has(f)).sort();
|
|
239
243
|
const touched = [...touchedSet].sort();
|
|
240
244
|
const dirty = [...dirtySet].sort();
|
|
245
|
+
const background = [...backgroundSet].sort();
|
|
246
|
+
const lockfiles = [...lockfilesSet].sort();
|
|
241
247
|
|
|
242
248
|
const sections: string[] = [];
|
|
243
249
|
if (readOnly.length > 0) {
|
|
@@ -249,6 +255,12 @@ export function formatFileOperationsXml(options?: {
|
|
|
249
255
|
if (dirty.length > 0) {
|
|
250
256
|
sections.push(`<uncommitted-dirty-files>\n${dirty.map(escapeXml).join("\n")}\n</uncommitted-dirty-files>`);
|
|
251
257
|
}
|
|
258
|
+
if (lockfiles.length > 0) {
|
|
259
|
+
sections.push(`<modified-lockfiles-and-assets>\n${lockfiles.map(escapeXml).join("\n")}\n</modified-lockfiles-and-assets>`);
|
|
260
|
+
}
|
|
261
|
+
if (background.length > 0) {
|
|
262
|
+
sections.push(`<active-background-processes>\n${background.map(escapeXml).join("\n")}\n</active-background-processes>`);
|
|
263
|
+
}
|
|
252
264
|
if (options.dirtyPatch) {
|
|
253
265
|
sections.push(`<uncommitted-diff>\n${escapeXml(options.dirtyPatch)}\n</uncommitted-diff>`);
|
|
254
266
|
}
|