pi-advisor-flow 0.5.6 → 0.6.0
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 +13 -0
- package/dist/index.js +1371 -1128
- package/extensions/index.ts +9 -9
- package/package.json +3 -1
- package/src/{untracked.ts → attachments.ts} +2 -5
- package/src/commands/activation-preparation.ts +6 -6
- package/src/commands/activation.ts +7 -7
- package/src/commands/lifecycle.ts +5 -5
- package/src/commands/manual-command.ts +13 -9
- package/src/commands/manual-consultation.ts +8 -8
- package/src/commands/manual-progress.ts +3 -3
- package/src/commands/model-commands.ts +6 -6
- package/src/commands/model-options.ts +4 -4
- package/src/commands/model-picker.ts +2 -2
- package/src/commands/registration.ts +8 -8
- package/src/commands/renderers.ts +5 -5
- package/src/commands/runtime.ts +8 -8
- package/src/commands/settings-commands.ts +8 -8
- package/src/commands/settings-persistence.ts +4 -4
- package/src/commands/types.ts +5 -13
- package/src/commands.ts +2 -2
- package/src/config/args.ts +3 -3
- package/src/config/defaults.ts +3 -3
- package/src/config/schema.ts +322 -0
- package/src/config/state.ts +2 -2
- package/src/config/storage.ts +20 -117
- package/src/config/types.ts +1 -1
- package/src/config/validation.ts +61 -181
- package/src/config.ts +7 -7
- package/src/content-utils.ts +30 -0
- package/src/conversation.ts +13 -168
- package/src/git.ts +4 -18
- package/src/herdr.ts +6 -8
- package/src/model-stream.ts +5 -8
- package/src/outcomes.ts +2 -5
- package/src/pi-settings.ts +59 -0
- package/src/preferences.ts +2 -2
- package/src/redaction.ts +50 -0
- package/src/scout-context.ts +121 -485
- package/src/scout-curation.ts +84 -0
- package/src/scout-groups.ts +485 -0
- package/src/scout-protocol.ts +122 -0
- package/src/scout-reconstruct.ts +44 -0
- package/src/scout-types.ts +66 -0
- package/src/scout.ts +53 -63
- package/src/session-state.ts +119 -76
- package/src/tool-result-cap.ts +88 -0
- package/src/tools/consult-context.ts +155 -0
- package/src/tools/consultation.ts +71 -220
- package/src/tools/gate-policy.ts +5 -5
- package/src/tools/gate-protocol.ts +2 -2
- package/src/tools/loop-gate.ts +70 -61
- package/src/tools/prompts.ts +4 -7
- package/src/tools/register-ask-advisor.ts +9 -14
- package/src/tools/register-lifecycle.ts +7 -7
- package/src/tools/register-outcome.ts +5 -5
- package/src/tools/register-renderers.ts +5 -5
- package/src/tools/registration.ts +10 -10
- package/src/tools/render-advisor-result.ts +11 -8
- package/src/tools/render-common.ts +25 -13
- package/src/tools/scout-status.ts +5 -5
- package/src/tools/session.ts +1 -1
- package/src/tools/types.ts +7 -7
- package/src/tools.ts +9 -9
- package/src/ui/manual-dialog-render.ts +2 -2
- package/src/ui/manual-dialog.ts +4 -9
- package/src/ui/model-selector.ts +1 -1
- package/src/ui/settings-formatting.ts +2 -2
- package/src/ui/settings-items.ts +4 -4
- package/src/ui/settings-mutations.ts +2 -2
- package/src/ui/settings-selector.ts +5 -5
- package/src/ui/text-setting-submenu.ts +1 -1
- package/src/ui/types.ts +2 -2
- package/src/ui.ts +4 -4
- package/src/usage.ts +1 -6
package/src/model-stream.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
stream,
|
|
7
7
|
} from "@earendil-works/pi-ai/compat";
|
|
8
8
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import { splitRef } from "./config/state.
|
|
9
|
+
import { splitRef } from "./config/state.ts";
|
|
10
10
|
|
|
11
11
|
export interface ResolvedConfiguredModel {
|
|
12
12
|
apiKey: string;
|
|
@@ -61,7 +61,7 @@ export interface CollectedTextStream {
|
|
|
61
61
|
|
|
62
62
|
export const ADVISOR_STREAM_UPDATE_INTERVAL_MS = 90;
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
interface CoalescedUpdateResult {
|
|
65
65
|
error?: unknown;
|
|
66
66
|
failed: boolean;
|
|
67
67
|
}
|
|
@@ -81,12 +81,7 @@ export interface CoalescedUpdateScheduler {
|
|
|
81
81
|
) => ReturnType<typeof setTimeout>;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
/**
|
|
85
|
-
* Keep stream updates responsive without forwarding every provider delta to
|
|
86
|
-
* the UI. The first update in a burst is immediate; later updates are kept as
|
|
87
|
-
* the latest value and published at most once per interval. `flush()` closes
|
|
88
|
-
* the publisher and is intended for terminal success/error paths.
|
|
89
|
-
*/
|
|
84
|
+
/** Publishes at most one stream update per interval; the first in a burst is immediate and flush() closes for terminal paths. */
|
|
90
85
|
export const createCoalescedUpdate = <T>(
|
|
91
86
|
publish: (value: T) => void,
|
|
92
87
|
intervalMs = ADVISOR_STREAM_UPDATE_INTERVAL_MS,
|
|
@@ -141,6 +136,8 @@ export const createCoalescedUpdate = <T>(
|
|
|
141
136
|
: scheduler.now() - lastPublishedAt;
|
|
142
137
|
const delay = Math.max(0, intervalMs - elapsed);
|
|
143
138
|
if (delay === 0) {
|
|
139
|
+
// A zero delay publishes synchronously rather than through the timer,
|
|
140
|
+
// so the first update of a burst lands before the caller continues.
|
|
144
141
|
publishPending();
|
|
145
142
|
return;
|
|
146
143
|
}
|
package/src/outcomes.ts
CHANGED
|
@@ -15,10 +15,7 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
|
15
15
|
|
|
16
16
|
export type OutcomeAdoption = "followed" | "not-followed" | "unknown";
|
|
17
17
|
export type OutcomeValidation = "passed" | "failed" | "not-run" | "unknown";
|
|
18
|
-
|
|
19
|
-
| "manual"
|
|
20
|
-
| "executor-requested"
|
|
21
|
-
| "repeated-tool-call";
|
|
18
|
+
type OutcomeTrigger = "manual" | "executor-requested" | "repeated-tool-call";
|
|
22
19
|
export const ADOPTIONS: OutcomeAdoption[] = [
|
|
23
20
|
"followed",
|
|
24
21
|
"not-followed",
|
|
@@ -115,7 +112,7 @@ const withOutcomeLock = async <T>(run: () => Promise<T>): Promise<T> => {
|
|
|
115
112
|
throw new Error("Timed out waiting to append an Advisor outcome.");
|
|
116
113
|
};
|
|
117
114
|
|
|
118
|
-
|
|
115
|
+
const adviceDigest = (advice: string, key: Buffer) =>
|
|
119
116
|
createHmac("sha256", key).update(advice).digest("hex").slice(0, 16);
|
|
120
117
|
|
|
121
118
|
/** Best-effort, global-only, minimal persistent telemetry. */
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
|
|
5
|
+
interface PiGlobalSettings {
|
|
6
|
+
hideThinkingBlock?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface CachedSetting {
|
|
10
|
+
hidden: boolean;
|
|
11
|
+
identity: string;
|
|
12
|
+
readAt: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Cached per file and re-read on identity change; stat calls are rate-limited because renderers run per frame.
|
|
16
|
+
const SETTING_RECHECK_INTERVAL_MS = 2000;
|
|
17
|
+
const cache = new Map<string, CachedSetting>();
|
|
18
|
+
|
|
19
|
+
const settingsIdentity = (path: string): string => {
|
|
20
|
+
try {
|
|
21
|
+
const stats = statSync(path, { bigint: true });
|
|
22
|
+
return `${stats.mtimeNs}:${stats.ctimeNs}:${stats.size}:${stats.ino}`;
|
|
23
|
+
} catch {
|
|
24
|
+
return `unstattable:${process.hrtime.bigint()}`;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const readHideThinking = (path: string): boolean => {
|
|
29
|
+
try {
|
|
30
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
31
|
+
return (
|
|
32
|
+
typeof parsed === "object" &&
|
|
33
|
+
parsed !== null &&
|
|
34
|
+
(parsed as PiGlobalSettings).hideThinkingBlock === true
|
|
35
|
+
);
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Whether Pi's `hide_thinking` setting hides rendered thinking blocks. */
|
|
42
|
+
export const piHideThinkingEnabled = (): boolean => {
|
|
43
|
+
const path = join(getAgentDir(), "settings.json");
|
|
44
|
+
if (!existsSync(path)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const cached = cache.get(path);
|
|
49
|
+
if (cached && now - cached.readAt < SETTING_RECHECK_INTERVAL_MS) {
|
|
50
|
+
return cached.hidden;
|
|
51
|
+
}
|
|
52
|
+
const identity = settingsIdentity(path);
|
|
53
|
+
const hidden =
|
|
54
|
+
cached && cached.identity === identity
|
|
55
|
+
? cached.hidden
|
|
56
|
+
: readHideThinking(path);
|
|
57
|
+
cache.set(path, { hidden, identity, readAt: now });
|
|
58
|
+
return hidden;
|
|
59
|
+
};
|
package/src/preferences.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { lstat, open, realpath } from "node:fs/promises";
|
|
2
2
|
import { join, relative } from "node:path";
|
|
3
3
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { redactAndCapText } from "./
|
|
4
|
+
import { redactAndCapText } from "./redaction.ts";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const PREFERENCES_MAX_BYTES = 8 * 1024;
|
|
7
7
|
// Keep the local filename components separate from Socket's URL-string heuristic.
|
|
8
8
|
const PREFERENCES_FILENAME = ["advisor-preferences", "md"].join(".");
|
|
9
9
|
export interface TextAttachment {
|
package/src/redaction.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { capUtf8Bytes } from "./content-utils.ts";
|
|
2
|
+
|
|
3
|
+
const REDACTION_MARKER = "[REDACTED SECRET]";
|
|
4
|
+
const PEM_BEGIN_PATTERN = /-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----/gi;
|
|
5
|
+
const PEM_END_PATTERN = /-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/i;
|
|
6
|
+
const SECRET_PATTERNS = [
|
|
7
|
+
/-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----[\s\S]*?-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/gi,
|
|
8
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/gi,
|
|
9
|
+
/\b(?:api[_-]?key|token|secret|password|passwd)\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s"'&,;)}\]]+)/gi,
|
|
10
|
+
/([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi,
|
|
11
|
+
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,
|
|
12
|
+
/\b(?:aws_secret_access_key|aws_session_token)\s*[:=]\s*[^\s"'&,;)}\]]+/gi,
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
const redactUnterminatedPem = (value: string): string => {
|
|
16
|
+
const begins = [...value.matchAll(PEM_BEGIN_PATTERN)];
|
|
17
|
+
const lastBegin = begins.at(-1);
|
|
18
|
+
if (lastBegin?.index === undefined) {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
const hasEnd = PEM_END_PATTERN.test(
|
|
22
|
+
value.slice(lastBegin.index + lastBegin[0].length)
|
|
23
|
+
);
|
|
24
|
+
return hasEnd
|
|
25
|
+
? value
|
|
26
|
+
: `${value.slice(0, lastBegin.index)}${REDACTION_MARKER}`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Redacts common credential forms locally; it is not a data-classification system. */
|
|
30
|
+
export const redactSecrets = (value: string): string => {
|
|
31
|
+
let redacted = redactUnterminatedPem(value);
|
|
32
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
33
|
+
redacted = redacted.replace(pattern, (_match, scheme) =>
|
|
34
|
+
typeof scheme === "string"
|
|
35
|
+
? `${scheme}${REDACTION_MARKER}@`
|
|
36
|
+
: REDACTION_MARKER
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return redacted;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Redacts before a byte-safe cap so no secret fragment survives truncation. */
|
|
43
|
+
export const redactAndCapText = (
|
|
44
|
+
value: string,
|
|
45
|
+
maxBytes: number,
|
|
46
|
+
redact = true
|
|
47
|
+
): string => {
|
|
48
|
+
const source = redact ? redactSecrets(value) : value;
|
|
49
|
+
return capUtf8Bytes(source, maxBytes);
|
|
50
|
+
};
|