pi-advisor-flow 0.6.0 → 0.8.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 (93) hide show
  1. package/CHANGELOG.md +34 -17
  2. package/README.md +10 -8
  3. package/dist/index.js +2852 -868
  4. package/extensions/index.ts +2 -1
  5. package/package.json +55 -40
  6. package/src/attachments.ts +11 -10
  7. package/src/commands/activation-preparation.ts +1 -0
  8. package/src/commands/activation.ts +2 -0
  9. package/src/commands/lifecycle.ts +1 -0
  10. package/src/commands/manual-command.ts +7 -1
  11. package/src/commands/manual-consultation.ts +103 -79
  12. package/src/commands/manual-progress.ts +7 -5
  13. package/src/commands/model-commands.ts +1 -1
  14. package/src/commands/model-options.ts +14 -3
  15. package/src/commands/model-picker.ts +1 -0
  16. package/src/commands/registration.ts +1 -0
  17. package/src/commands/renderers.ts +67 -55
  18. package/src/commands/runtime.ts +16 -14
  19. package/src/commands/settings-commands.ts +10 -4
  20. package/src/commands/settings-persistence.ts +64 -2
  21. package/src/commands/types.ts +2 -0
  22. package/src/commands.ts +0 -1
  23. package/src/config/args.ts +1 -1
  24. package/src/config/defaults.ts +83 -1
  25. package/src/config/schema.ts +152 -15
  26. package/src/config/state.ts +80 -2
  27. package/src/config/storage.ts +38 -22
  28. package/src/config/types.ts +45 -3
  29. package/src/config/validation.ts +33 -21
  30. package/src/config.ts +3 -0
  31. package/src/content-utils.ts +24 -4
  32. package/src/conversation.ts +16 -15
  33. package/src/git.ts +10 -3
  34. package/src/herdr-block.ts +83 -0
  35. package/src/herdr-shared.ts +108 -0
  36. package/src/herdr.ts +30 -192
  37. package/src/jev/client.ts +338 -0
  38. package/src/jev/failure.ts +19 -0
  39. package/src/jev/key-store.ts +267 -0
  40. package/src/jev/ledger.ts +228 -0
  41. package/src/jev/questions.ts +122 -0
  42. package/src/jev/state.ts +50 -0
  43. package/src/jev/transport.ts +63 -0
  44. package/src/model-stream.ts +35 -37
  45. package/src/outcomes.ts +20 -12
  46. package/src/pi-settings.ts +4 -9
  47. package/src/preferences.ts +4 -2
  48. package/src/redaction.ts +10 -12
  49. package/src/scout-context.ts +59 -60
  50. package/src/scout-curation.ts +4 -13
  51. package/src/scout-groups.ts +178 -175
  52. package/src/scout-protocol.ts +74 -70
  53. package/src/scout-reconstruct.ts +1 -1
  54. package/src/scout.ts +115 -73
  55. package/src/session-state.ts +148 -39
  56. package/src/tool-result-cap.ts +18 -17
  57. package/src/tools/consult-context.ts +7 -15
  58. package/src/tools/consultation.ts +14 -4
  59. package/src/tools/gate-policy.ts +1 -0
  60. package/src/tools/gate-protocol.ts +36 -23
  61. package/src/tools/jev-filter.ts +204 -0
  62. package/src/tools/jev-turn-gate.ts +203 -0
  63. package/src/tools/loop-gate.ts +32 -20
  64. package/src/tools/model-access.ts +46 -0
  65. package/src/tools/outage-notifier.ts +30 -0
  66. package/src/tools/prompts.ts +20 -25
  67. package/src/tools/register-ask-advisor.ts +73 -17
  68. package/src/tools/register-lifecycle.ts +22 -0
  69. package/src/tools/register-outcome.ts +2 -0
  70. package/src/tools/register-renderers.ts +149 -73
  71. package/src/tools/registration.ts +15 -0
  72. package/src/tools/render-advisor-result.ts +77 -35
  73. package/src/tools/render-common.ts +16 -19
  74. package/src/tools/scout-status.ts +42 -27
  75. package/src/tools/types.ts +11 -0
  76. package/src/tools.ts +0 -1
  77. package/src/ui/jev-setup-submenu.ts +350 -0
  78. package/src/ui/manual-dialog-render.ts +1 -0
  79. package/src/ui/manual-dialog.ts +39 -43
  80. package/src/ui/masked-input.ts +62 -0
  81. package/src/ui/model-multi-selector.ts +9 -0
  82. package/src/ui/model-selector-adapter.ts +31 -0
  83. package/src/ui/model-selector.ts +14 -161
  84. package/src/ui/searchable-model-list.ts +230 -0
  85. package/src/ui/settings-formatting.ts +24 -14
  86. package/src/ui/settings-items.ts +200 -2
  87. package/src/ui/settings-list-adapter.ts +15 -12
  88. package/src/ui/settings-mutations.ts +169 -47
  89. package/src/ui/settings-selector.ts +18 -9
  90. package/src/ui/text-setting-submenu.ts +3 -6
  91. package/src/ui/types.ts +25 -0
  92. package/src/ui.ts +0 -1
  93. package/src/usage.ts +29 -20
@@ -1,10 +1,11 @@
1
- import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
2
- import {
3
- getMarkdownTheme,
4
- type Theme,
5
- type ToolRenderResultOptions,
1
+ import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
2
+ import type {
3
+ AgentToolResult,
4
+ Theme,
5
+ ToolRenderResultOptions,
6
6
  } from "@earendil-works/pi-coding-agent";
7
7
  import { Box, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
8
+
8
9
  import { getAdvisorSettings } from "../config/state.ts";
9
10
  import { textFrom } from "../conversation.ts";
10
11
  import { formatAdvisorUsage } from "../usage.ts";
@@ -29,6 +30,44 @@ const syncRenderPhase = (context: AdvisorToolContext, phase: string) => {
29
30
  context.state.phase = phase;
30
31
  };
31
32
 
33
+ const attachmentLabels = (details: AdvisorToolDetails | undefined) =>
34
+ [
35
+ details?.draftBytes
36
+ ? `Draft attached · ${details.draftBytes} B`
37
+ : undefined,
38
+ details?.preferenceBytes
39
+ ? `Project preferences attached · ${details.preferenceBytes} B`
40
+ : undefined,
41
+ details?.trackedBytes
42
+ ? `Tracked files attached · ${details.trackedBytes} B`
43
+ : undefined,
44
+ details?.untrackedBytes
45
+ ? `Untracked files attached · ${details.untrackedBytes} B`
46
+ : undefined,
47
+ ].filter((label): label is string => label !== undefined);
48
+
49
+ const renderJevSkipBox = (
50
+ box: Box,
51
+ result: AgentToolResult<AdvisorToolDetails>,
52
+ expanded: boolean,
53
+ theme: Theme
54
+ ) => {
55
+ const details = advisorResultDetails(result);
56
+ const lines = [
57
+ theme.fg("dim", theme.bold("◆ ADVISOR · SKIPPED")),
58
+ theme.fg("dim", ` ${details?.jev?.reason ?? ""}`),
59
+ ];
60
+ box.addChild(new Text(lines.join("\n"), 0, 0));
61
+ box.addChild(
62
+ new Markdown(
63
+ adviceForDisplay(textFrom(result.content), expanded),
64
+ 0,
65
+ 0,
66
+ getMarkdownTheme()
67
+ )
68
+ );
69
+ };
70
+
32
71
  const renderPartialAdvisorResult = (
33
72
  box: Box,
34
73
  result: AgentToolResult<AdvisorToolDetails>,
@@ -78,6 +117,33 @@ const renderPartialAdvisorResult = (
78
117
  }
79
118
  };
80
119
 
120
+ const thinkingPreview = (details: AdvisorToolDetails | undefined) =>
121
+ details?.thinking?.trim()
122
+ ? `${details.thinking.slice(0, 300)}${details.thinking.length > 300 ? "…" : ""}`
123
+ : "";
124
+
125
+ const finalResultLines = (
126
+ details: AdvisorToolDetails | undefined,
127
+ advice: string,
128
+ theme: Theme
129
+ ): string[] => {
130
+ const lines = [renderAdvisorResponseHeader(hasSoundVerdict(advice), theme)];
131
+ if (details?.advisor) {
132
+ lines.push(theme.fg("dim", ` ${details.advisor}`));
133
+ }
134
+ if (getAdvisorSettings().showUsageDetails) {
135
+ const usage = formatAdvisorUsage(details?.usage);
136
+ if (usage) {
137
+ lines.push(theme.fg("dim", ` Usage: ${usage}`));
138
+ }
139
+ }
140
+ const attachments = attachmentLabels(details);
141
+ if (attachments.length) {
142
+ lines.push(theme.fg("dim", ` ${attachments.join(" · ")}`));
143
+ }
144
+ return lines;
145
+ };
146
+
81
147
  const renderFinalAdvisorResult = (
82
148
  box: Box,
83
149
  result: AgentToolResult<AdvisorToolDetails>,
@@ -91,6 +157,10 @@ const renderFinalAdvisorResult = (
91
157
  context.state.timerId = undefined;
92
158
  }
93
159
  const details = advisorResultDetails(result);
160
+ if (details?.jev?.skipped) {
161
+ renderJevSkipBox(box, result, expanded, theme);
162
+ return;
163
+ }
94
164
  if (details?.scout) {
95
165
  context.state.scout = details.scout;
96
166
  }
@@ -102,36 +172,8 @@ const renderFinalAdvisorResult = (
102
172
  return;
103
173
  }
104
174
  const advice = details?.text || textFrom(result.content);
105
- const lines = [renderAdvisorResponseHeader(hasSoundVerdict(advice), theme)];
106
- if (details?.advisor) {
107
- lines.push(theme.fg("dim", ` ${details.advisor}`));
108
- }
109
- if (getAdvisorSettings().showUsageDetails) {
110
- const usage = formatAdvisorUsage(details?.usage);
111
- if (usage) {
112
- lines.push(theme.fg("dim", ` Usage: ${usage}`));
113
- }
114
- }
115
- const attachments = [
116
- details?.draftBytes
117
- ? `Draft attached · ${details.draftBytes} B`
118
- : undefined,
119
- details?.preferenceBytes
120
- ? `Project preferences attached · ${details.preferenceBytes} B`
121
- : undefined,
122
- details?.trackedBytes
123
- ? `Tracked files attached · ${details.trackedBytes} B`
124
- : undefined,
125
- details?.untrackedBytes
126
- ? `Untracked files attached · ${details.untrackedBytes} B`
127
- : undefined,
128
- ].filter(Boolean);
129
- if (attachments.length) {
130
- lines.push(theme.fg("dim", ` ${attachments.join(" · ")}`));
131
- }
132
- const thinking = details?.thinking?.trim()
133
- ? `${details.thinking.slice(0, 300)}${details.thinking.length > 300 ? "…" : ""}`
134
- : "";
175
+ const thinking = thinkingPreview(details);
176
+ const lines = finalResultLines(details, advice, theme);
135
177
  const displayAdvice = advice || "(Advisor returned no advice.)";
136
178
  box.addChild(new Text(lines.join("\n"), 0, 0));
137
179
  if (thinking) {
@@ -1,12 +1,14 @@
1
- import { getMarkdownTheme, type Theme } from "@earendil-works/pi-coding-agent";
1
+ import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
3
  import {
3
4
  Box,
4
- type Component,
5
5
  Markdown,
6
6
  Text,
7
7
  truncateToWidth,
8
8
  visibleWidth,
9
9
  } from "@earendil-works/pi-tui";
10
+ import type { Component } from "@earendil-works/pi-tui";
11
+
10
12
  import { advisorCollapseResponsesRef } from "../config/state.ts";
11
13
  import { piHideThinkingEnabled } from "../pi-settings.ts";
12
14
 
@@ -27,6 +29,9 @@ const THINKING_PREFIX = " ";
27
29
  const THINKING_PREFIX_WIDTH = visibleWidth(THINKING_PREFIX);
28
30
  export type ThinkingTheme = Pick<Theme, "fg">;
29
31
 
32
+ /** Shared no-op for interface-required methods that manage no state. */
33
+ export const noop = (): void => undefined;
34
+
30
35
  /** Renders nested-model thinking as Markdown with the compact speech-bubble cue; the prefix is added after parsing so it cannot change block syntax. */
31
36
  class ThinkingMarkdown implements Component {
32
37
  private readonly markdown: Markdown;
@@ -59,28 +64,20 @@ class ThinkingMarkdown implements Component {
59
64
  }
60
65
 
61
66
  /** Collapsed placeholder shown when Pi's hide_thinking setting is on. */
62
- class HiddenThinkingLabel implements Component {
63
- private readonly label: string;
64
-
65
- constructor(theme: ThinkingTheme) {
66
- this.label = theme.fg("thinkingText", `${THINKING_PREFIX}Thinking…`);
67
- }
68
-
69
- render(): string[] {
70
- return [this.label];
71
- }
72
-
73
- invalidate(): void {
74
- // The collapsed label is static.
75
- }
76
- }
67
+ const hiddenThinkingLabel = (theme: ThinkingTheme): Component => {
68
+ const label = theme.fg("thinkingText", `${THINKING_PREFIX}Thinking…`);
69
+ return {
70
+ invalidate: noop,
71
+ render: () => [label],
72
+ };
73
+ };
77
74
 
78
75
  export const renderThinkingMarkdown = (
79
76
  thinking: string,
80
77
  theme: ThinkingTheme
81
78
  ): Component =>
82
79
  piHideThinkingEnabled()
83
- ? new HiddenThinkingLabel(theme)
80
+ ? hiddenThinkingLabel(theme)
84
81
  : new ThinkingMarkdown(thinking, theme);
85
82
 
86
83
  export const resolveAdvisorRequest = (question?: string) =>
@@ -107,7 +104,7 @@ export const renderAdvisorCallBox = (
107
104
 
108
105
  const COLLAPSED_ADVICE_LINES = 12;
109
106
  // The system prompt requires this exact first line, so the match is exact too.
110
- const SOUND_VERDICT = /^Verdict:\s*sound$/;
107
+ const SOUND_VERDICT = /^Verdict:\s*sound$/u;
111
108
 
112
109
  export const hasSoundVerdict = (advice: string) =>
113
110
  SOUND_VERDICT.test(
@@ -3,7 +3,9 @@ import type {
3
3
  ExtensionContext,
4
4
  Theme,
5
5
  } from "@earendil-works/pi-coding-agent";
6
- import { type Box, Text } from "@earendil-works/pi-tui";
6
+ import { Text } from "@earendil-works/pi-tui";
7
+ import type { Box } from "@earendil-works/pi-tui";
8
+
7
9
  import { executorRef, getAdvisorSettings } from "../config/state.ts";
8
10
  import type { ScoutLifecycleEvent } from "../scout.ts";
9
11
  import { formatAdvisorUsage, snapshotAdvisorUsage } from "../usage.ts";
@@ -142,6 +144,43 @@ const scoutTitle = (scout: ScoutToolDetails, frame: string) => {
142
144
  return "◆ SCOUT · FALLBACK";
143
145
  };
144
146
 
147
+ const scoutSummaryLine = (scout: ScoutToolDetails) =>
148
+ ` ${scout.model}${
149
+ scout.selectedCount === undefined
150
+ ? ""
151
+ : ` · ${scout.selectedCount} kept / ${Math.max(0, (scout.availableCount ?? 0) - scout.selectedCount)} omitted`
152
+ }${scout.latencyMs === undefined ? "" : ` · ${(scout.latencyMs / 1000).toFixed(1)}s`}`;
153
+
154
+ const scoutExpandedLines = (
155
+ scout: ScoutToolDetails,
156
+ expanded: boolean,
157
+ theme: Theme
158
+ ): string[] => {
159
+ const lines: string[] = [];
160
+ if (expanded && scout.selectedLabels?.length) {
161
+ lines.push(
162
+ theme.fg("dim", ` Selected: ${scout.selectedLabels.join("; ")}`)
163
+ );
164
+ }
165
+ if (expanded && scout.synthesis) {
166
+ lines.push(
167
+ theme.fg(
168
+ "dim",
169
+ ` Scout synthesis (untrusted inference): ${scout.synthesis}`
170
+ )
171
+ );
172
+ }
173
+ if (expanded && scout.omittedBeforeScout) {
174
+ lines.push(
175
+ theme.fg(
176
+ "dim",
177
+ ` ${scout.omittedBeforeScout} group(s) omitted before Scout`
178
+ )
179
+ );
180
+ }
181
+ return lines;
182
+ };
183
+
145
184
  export const renderScoutDetails = (
146
185
  box: Box,
147
186
  scout: ScoutToolDetails,
@@ -159,10 +198,7 @@ export const renderScoutDetails = (
159
198
  : "accent",
160
199
  theme.bold(title)
161
200
  ),
162
- theme.fg(
163
- "dim",
164
- ` ${scout.model}${scout.selectedCount === undefined ? "" : ` · ${scout.selectedCount} kept / ${Math.max(0, (scout.availableCount ?? 0) - scout.selectedCount)} omitted`}${scout.latencyMs === undefined ? "" : ` · ${(scout.latencyMs / 1000).toFixed(1)}s`}`
165
- ),
201
+ theme.fg("dim", scoutSummaryLine(scout)),
166
202
  ];
167
203
  if (getAdvisorSettings().showUsageDetails) {
168
204
  const usage = formatAdvisorUsage(scout.usage);
@@ -178,28 +214,7 @@ export const renderScoutDetails = (
178
214
  if (thinking.trim()) {
179
215
  box.addChild(renderThinkingMarkdown(thinking, theme));
180
216
  }
181
- const expandedLines: string[] = [];
182
- if (expanded && scout.selectedLabels?.length) {
183
- expandedLines.push(
184
- theme.fg("dim", ` Selected: ${scout.selectedLabels.join("; ")}`)
185
- );
186
- }
187
- if (expanded && scout.synthesis) {
188
- expandedLines.push(
189
- theme.fg(
190
- "dim",
191
- ` Scout synthesis (untrusted inference): ${scout.synthesis}`
192
- )
193
- );
194
- }
195
- if (expanded && scout.omittedBeforeScout) {
196
- expandedLines.push(
197
- theme.fg(
198
- "dim",
199
- ` ${scout.omittedBeforeScout} group(s) omitted before Scout`
200
- )
201
- );
202
- }
217
+ const expandedLines = scoutExpandedLines(scout, expanded, theme);
203
218
  if (expandedLines.length > 0) {
204
219
  box.addChild(new Text(expandedLines.join("\n"), 0, 0));
205
220
  }
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+
2
3
  import type { appendOutcome } from "../outcomes.ts";
3
4
  import type { ScoutOutcome } from "../scout.ts";
4
5
  import type {
@@ -8,6 +9,8 @@ import type {
8
9
  GateTrigger,
9
10
  } from "../session-state.ts";
10
11
  import type { consultAdvisor, runAdvisorGate } from "./consultation.ts";
12
+ import type { JevSkipKind, screenConsultation } from "./jev-filter.ts";
13
+ import type { JevTurnGateDeps } from "./jev-turn-gate.ts";
11
14
  import type { ScoutStatusManager } from "./scout-status.ts";
12
15
 
13
16
  export type {
@@ -79,6 +82,11 @@ export interface AdvisorToolDetails {
79
82
  adviceId?: string;
80
83
  advisor?: string;
81
84
  draftBytes?: number;
85
+ jev?: {
86
+ kind: JevSkipKind;
87
+ reason: string;
88
+ skipped: boolean;
89
+ };
82
90
  preferenceBytes?: number;
83
91
  question?: string;
84
92
  scout?: ScoutToolDetails;
@@ -108,6 +116,7 @@ export interface ToolRegistrationContext {
108
116
  reservedCalls: Set<string>;
109
117
  runGate: typeof runAdvisorGate;
110
118
  scoutStatus: ScoutStatusManager;
119
+ screen: typeof screenConsultation;
111
120
  session: AdvisorSessionState;
112
121
  }
113
122
 
@@ -115,5 +124,7 @@ export interface ToolRegistrationDependencies {
115
124
  appendOutcome?: typeof appendOutcome;
116
125
  consult?: typeof consultAdvisor;
117
126
  runGate?: typeof runAdvisorGate;
127
+ screen?: typeof screenConsultation;
118
128
  statusManager?: ScoutStatusManager;
129
+ turnGateDeps?: JevTurnGateDeps;
119
130
  }
package/src/tools.ts CHANGED
@@ -1,4 +1,3 @@
1
- // biome-ignore lint/performance/noBarrelFile: this facade intentionally preserves the public tools module contract.
2
1
  export {
3
2
  consultAdvisor,
4
3
  curateAdvisorConversation,