pi-advisor-flow 0.2.3 → 0.2.6
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/README.md +45 -11
- package/package.json +33 -30
- package/src/commands.ts +141 -34
- package/src/config.ts +167 -8
- package/src/conversation.ts +28 -3
- package/src/git.ts +190 -0
- package/src/outcomes.ts +85 -0
- package/src/preferences.ts +50 -0
- package/src/session-state.ts +36 -1
- package/src/tools.ts +340 -30
- package/src/ui.ts +410 -114
- package/src/untracked.ts +98 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { lstat, open, realpath } from "node:fs/promises";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { redactAndCapText } from "./conversation.js";
|
|
5
|
+
|
|
6
|
+
export const PREFERENCES_MAX_BYTES = 8 * 1024;
|
|
7
|
+
export interface TextAttachment {
|
|
8
|
+
bytes: number;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const inside = (root: string, candidate: string) => {
|
|
13
|
+
const path = relative(root, candidate);
|
|
14
|
+
return path === "" || !(path.startsWith("..") || path.includes("../"));
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Reads trusted project preferences without following a file or root escape. */
|
|
18
|
+
export const readProjectPreferences = async (
|
|
19
|
+
ctx: ExtensionContext,
|
|
20
|
+
maxBytes = PREFERENCES_MAX_BYTES,
|
|
21
|
+
redact = true
|
|
22
|
+
): Promise<TextAttachment | undefined> => {
|
|
23
|
+
if (!ctx.isProjectTrusted()) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const root = await realpath(ctx.cwd);
|
|
28
|
+
const candidate = join(ctx.cwd, ".pi", "advisor-preferences.md");
|
|
29
|
+
const stats = await lstat(candidate);
|
|
30
|
+
if (stats.isSymbolicLink() || !stats.isFile()) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const resolved = await realpath(candidate);
|
|
34
|
+
if (!inside(root, resolved)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const file = await open(resolved, "r");
|
|
38
|
+
try {
|
|
39
|
+
const buffer = Buffer.alloc(maxBytes + 1);
|
|
40
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
41
|
+
const source = buffer.subarray(0, bytesRead).toString("utf8");
|
|
42
|
+
const capped = redactAndCapText(source, maxBytes, redact);
|
|
43
|
+
return { bytes: Buffer.byteLength(capped, "utf8"), text: capped };
|
|
44
|
+
} finally {
|
|
45
|
+
await file.close();
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
// Missing, unreadable, or unsafe preferences are intentionally withheld.
|
|
49
|
+
}
|
|
50
|
+
};
|
package/src/session-state.ts
CHANGED
|
@@ -112,6 +112,13 @@ export class AdvisorSessionState {
|
|
|
112
112
|
#invocations: AdvisorInvocationRecord[] = [];
|
|
113
113
|
#loopInterventions = 0;
|
|
114
114
|
#consumedCalls = 0;
|
|
115
|
+
readonly #issuedAdvice = new Map<
|
|
116
|
+
string,
|
|
117
|
+
{ advice: string; trigger: ConsultationTrigger }
|
|
118
|
+
>();
|
|
119
|
+
readonly #reportedAdvice = new Set<string>();
|
|
120
|
+
#draftConsultations = 0;
|
|
121
|
+
#outcomes = 0;
|
|
115
122
|
|
|
116
123
|
resetTask() {
|
|
117
124
|
this.#previousSignature = undefined;
|
|
@@ -120,6 +127,10 @@ export class AdvisorSessionState {
|
|
|
120
127
|
this.#invocations = [];
|
|
121
128
|
this.#loopInterventions = 0;
|
|
122
129
|
this.#consumedCalls = 0;
|
|
130
|
+
this.#issuedAdvice.clear();
|
|
131
|
+
this.#reportedAdvice.clear();
|
|
132
|
+
this.#draftConsultations = 0;
|
|
133
|
+
this.#outcomes = 0;
|
|
123
134
|
}
|
|
124
135
|
|
|
125
136
|
clearBlocked() {
|
|
@@ -172,6 +183,29 @@ export class AdvisorSessionState {
|
|
|
172
183
|
recordInvocation(record: AdvisorInvocationRecord) {
|
|
173
184
|
this.#invocations.push(record);
|
|
174
185
|
}
|
|
186
|
+
issueAdvice(
|
|
187
|
+
id: string,
|
|
188
|
+
advice: string,
|
|
189
|
+
trigger: ConsultationTrigger,
|
|
190
|
+
draft = false
|
|
191
|
+
) {
|
|
192
|
+
this.#issuedAdvice.set(id, { advice, trigger });
|
|
193
|
+
if (draft) {
|
|
194
|
+
this.#draftConsultations += 1;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
claimAdvice(id: string) {
|
|
198
|
+
if (this.#reportedAdvice.has(id)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const advice = this.#issuedAdvice.get(id);
|
|
202
|
+
if (!advice) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.#reportedAdvice.add(id);
|
|
206
|
+
this.#outcomes += 1;
|
|
207
|
+
return advice;
|
|
208
|
+
}
|
|
175
209
|
|
|
176
210
|
summary(limit: number | undefined) {
|
|
177
211
|
if (this.#invocations.length === 0 && this.#loopInterventions === 0) {
|
|
@@ -215,7 +249,8 @@ export class AdvisorSessionState {
|
|
|
215
249
|
`Triggers: ${["manual", "executor-requested", "repeated-tool-call", "completion-review", "custom-rule"].filter((trigger) => countTrigger(trigger as AdvisorTrigger) > 0).join(", ") || "none"}`,
|
|
216
250
|
`Models: ${models}`,
|
|
217
251
|
`Budget: ${budget}`,
|
|
218
|
-
`Markdown advice: ${markdown.length} responses`,
|
|
252
|
+
`Markdown advice: ${markdown.length} responses (${this.#draftConsultations} with drafts)`,
|
|
253
|
+
`Outcome reports: ${this.#outcomes}`,
|
|
219
254
|
`Gate decisions: ${decisions}`,
|
|
220
255
|
`Loop matching: normalized tool signatures; ${this.#loopInterventions} gate intervention${this.#loopInterventions === 1 ? "" : "s"}`,
|
|
221
256
|
`Execution effects: ${effects("tool-blocked")} tool blocked, ${effects("session-blocked")} sessions blocked, ${effects("continued")} continued`,
|