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.
Files changed (75) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/index.js +1371 -1128
  3. package/extensions/index.ts +9 -9
  4. package/package.json +3 -1
  5. package/src/{untracked.ts → attachments.ts} +2 -5
  6. package/src/commands/activation-preparation.ts +6 -6
  7. package/src/commands/activation.ts +7 -7
  8. package/src/commands/lifecycle.ts +5 -5
  9. package/src/commands/manual-command.ts +13 -9
  10. package/src/commands/manual-consultation.ts +8 -8
  11. package/src/commands/manual-progress.ts +3 -3
  12. package/src/commands/model-commands.ts +6 -6
  13. package/src/commands/model-options.ts +4 -4
  14. package/src/commands/model-picker.ts +2 -2
  15. package/src/commands/registration.ts +8 -8
  16. package/src/commands/renderers.ts +5 -5
  17. package/src/commands/runtime.ts +8 -8
  18. package/src/commands/settings-commands.ts +8 -8
  19. package/src/commands/settings-persistence.ts +4 -4
  20. package/src/commands/types.ts +5 -13
  21. package/src/commands.ts +2 -2
  22. package/src/config/args.ts +3 -3
  23. package/src/config/defaults.ts +3 -3
  24. package/src/config/schema.ts +322 -0
  25. package/src/config/state.ts +2 -2
  26. package/src/config/storage.ts +20 -117
  27. package/src/config/types.ts +1 -1
  28. package/src/config/validation.ts +61 -181
  29. package/src/config.ts +7 -7
  30. package/src/content-utils.ts +30 -0
  31. package/src/conversation.ts +13 -168
  32. package/src/git.ts +4 -18
  33. package/src/herdr.ts +6 -8
  34. package/src/model-stream.ts +5 -8
  35. package/src/outcomes.ts +2 -5
  36. package/src/pi-settings.ts +59 -0
  37. package/src/preferences.ts +2 -2
  38. package/src/redaction.ts +50 -0
  39. package/src/scout-context.ts +121 -485
  40. package/src/scout-curation.ts +84 -0
  41. package/src/scout-groups.ts +485 -0
  42. package/src/scout-protocol.ts +122 -0
  43. package/src/scout-reconstruct.ts +44 -0
  44. package/src/scout-types.ts +66 -0
  45. package/src/scout.ts +53 -63
  46. package/src/session-state.ts +119 -76
  47. package/src/tool-result-cap.ts +88 -0
  48. package/src/tools/consult-context.ts +155 -0
  49. package/src/tools/consultation.ts +71 -220
  50. package/src/tools/gate-policy.ts +5 -5
  51. package/src/tools/gate-protocol.ts +2 -2
  52. package/src/tools/loop-gate.ts +70 -61
  53. package/src/tools/prompts.ts +4 -7
  54. package/src/tools/register-ask-advisor.ts +9 -14
  55. package/src/tools/register-lifecycle.ts +7 -7
  56. package/src/tools/register-outcome.ts +5 -5
  57. package/src/tools/register-renderers.ts +5 -5
  58. package/src/tools/registration.ts +10 -10
  59. package/src/tools/render-advisor-result.ts +11 -8
  60. package/src/tools/render-common.ts +25 -13
  61. package/src/tools/scout-status.ts +5 -5
  62. package/src/tools/session.ts +1 -1
  63. package/src/tools/types.ts +7 -7
  64. package/src/tools.ts +9 -9
  65. package/src/ui/manual-dialog-render.ts +2 -2
  66. package/src/ui/manual-dialog.ts +4 -9
  67. package/src/ui/model-selector.ts +1 -1
  68. package/src/ui/settings-formatting.ts +2 -2
  69. package/src/ui/settings-items.ts +4 -4
  70. package/src/ui/settings-mutations.ts +2 -2
  71. package/src/ui/settings-selector.ts +5 -5
  72. package/src/ui/text-setting-submenu.ts +1 -1
  73. package/src/ui/types.ts +2 -2
  74. package/src/ui.ts +4 -4
  75. package/src/usage.ts +1 -6
@@ -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.js";
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
- export interface CoalescedUpdateResult {
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
- export type OutcomeTrigger =
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
- export const adviceDigest = (advice: string, key: Buffer) =>
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
+ };
@@ -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 "./conversation.js";
4
+ import { redactAndCapText } from "./redaction.ts";
5
5
 
6
- export const PREFERENCES_MAX_BYTES = 8 * 1024;
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 {
@@ -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
+ };