pi-advisor-flow 0.5.5 → 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 (76) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/README.md +1 -1
  3. package/dist/index.js +5786 -0
  4. package/extensions/index.ts +9 -9
  5. package/package.json +16 -10
  6. package/src/{untracked.ts → attachments.ts} +2 -5
  7. package/src/commands/activation-preparation.ts +6 -6
  8. package/src/commands/activation.ts +7 -7
  9. package/src/commands/lifecycle.ts +5 -5
  10. package/src/commands/manual-command.ts +13 -9
  11. package/src/commands/manual-consultation.ts +8 -8
  12. package/src/commands/manual-progress.ts +3 -3
  13. package/src/commands/model-commands.ts +6 -6
  14. package/src/commands/model-options.ts +4 -4
  15. package/src/commands/model-picker.ts +2 -2
  16. package/src/commands/registration.ts +8 -8
  17. package/src/commands/renderers.ts +5 -5
  18. package/src/commands/runtime.ts +8 -8
  19. package/src/commands/settings-commands.ts +8 -8
  20. package/src/commands/settings-persistence.ts +4 -4
  21. package/src/commands/types.ts +5 -13
  22. package/src/commands.ts +2 -2
  23. package/src/config/args.ts +3 -3
  24. package/src/config/defaults.ts +3 -3
  25. package/src/config/schema.ts +322 -0
  26. package/src/config/state.ts +2 -2
  27. package/src/config/storage.ts +113 -103
  28. package/src/config/types.ts +1 -1
  29. package/src/config/validation.ts +61 -181
  30. package/src/config.ts +7 -7
  31. package/src/content-utils.ts +30 -0
  32. package/src/conversation.ts +13 -168
  33. package/src/git.ts +4 -18
  34. package/src/herdr.ts +6 -8
  35. package/src/model-stream.ts +5 -8
  36. package/src/outcomes.ts +2 -5
  37. package/src/pi-settings.ts +59 -0
  38. package/src/preferences.ts +2 -2
  39. package/src/redaction.ts +50 -0
  40. package/src/scout-context.ts +121 -485
  41. package/src/scout-curation.ts +84 -0
  42. package/src/scout-groups.ts +485 -0
  43. package/src/scout-protocol.ts +122 -0
  44. package/src/scout-reconstruct.ts +44 -0
  45. package/src/scout-types.ts +66 -0
  46. package/src/scout.ts +53 -63
  47. package/src/session-state.ts +119 -76
  48. package/src/tool-result-cap.ts +88 -0
  49. package/src/tools/consult-context.ts +155 -0
  50. package/src/tools/consultation.ts +71 -220
  51. package/src/tools/gate-policy.ts +5 -5
  52. package/src/tools/gate-protocol.ts +2 -2
  53. package/src/tools/loop-gate.ts +70 -61
  54. package/src/tools/prompts.ts +4 -7
  55. package/src/tools/register-ask-advisor.ts +9 -14
  56. package/src/tools/register-lifecycle.ts +7 -7
  57. package/src/tools/register-outcome.ts +5 -5
  58. package/src/tools/register-renderers.ts +5 -5
  59. package/src/tools/registration.ts +10 -10
  60. package/src/tools/render-advisor-result.ts +11 -8
  61. package/src/tools/render-common.ts +25 -13
  62. package/src/tools/scout-status.ts +5 -5
  63. package/src/tools/session.ts +1 -1
  64. package/src/tools/types.ts +7 -7
  65. package/src/tools.ts +9 -9
  66. package/src/ui/manual-dialog-render.ts +10 -3
  67. package/src/ui/manual-dialog.ts +4 -9
  68. package/src/ui/model-selector.ts +7 -6
  69. package/src/ui/settings-formatting.ts +2 -2
  70. package/src/ui/settings-items.ts +4 -4
  71. package/src/ui/settings-mutations.ts +2 -2
  72. package/src/ui/settings-selector.ts +5 -5
  73. package/src/ui/text-setting-submenu.ts +6 -1
  74. package/src/ui/types.ts +2 -2
  75. package/src/ui.ts +4 -4
  76. package/src/usage.ts +1 -6
@@ -0,0 +1,88 @@
1
+ import {
2
+ DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES,
3
+ DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
4
+ } from "./config/types.ts";
5
+ import { byteLength, capUtf8Bytes } from "./content-utils.ts";
6
+
7
+ export interface ToolResultTruncation {
8
+ content: string;
9
+ omittedLines: number;
10
+ totalBytes: number;
11
+ totalLines: number;
12
+ truncated: boolean;
13
+ }
14
+
15
+ const OMITTED_MARKER = "[... omitted tool-result section ...]";
16
+
17
+ export const capToolResult = (
18
+ value: string,
19
+ maxLines = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
20
+ maxBytes = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES
21
+ ): ToolResultTruncation => {
22
+ const lines = value.split("\n");
23
+ const totalLines = lines.length;
24
+ const totalBytes = byteLength(value);
25
+ if ((maxLines === 0 || maxBytes === 0) && value.length > 0) {
26
+ return {
27
+ content: "[Tool result omitted: configured limit is zero]",
28
+ omittedLines: totalLines,
29
+ totalBytes,
30
+ totalLines,
31
+ truncated: true,
32
+ };
33
+ }
34
+ if (totalLines <= maxLines && totalBytes <= maxBytes) {
35
+ return {
36
+ content: value,
37
+ omittedLines: 0,
38
+ totalBytes,
39
+ totalLines,
40
+ truncated: false,
41
+ };
42
+ }
43
+
44
+ const markerBytes = byteLength(OMITTED_MARKER);
45
+ if (maxBytes < markerBytes || maxLines === 1) {
46
+ return {
47
+ content: capUtf8Bytes(OMITTED_MARKER, maxBytes),
48
+ omittedLines: totalLines,
49
+ totalBytes,
50
+ totalLines,
51
+ truncated: true,
52
+ };
53
+ }
54
+ const headCount = Math.floor((maxLines - 1) / 2);
55
+ const tailCount = maxLines - 1 - headCount;
56
+ const collect = (
57
+ candidates: string[],
58
+ maxEntries: number,
59
+ maxContentBytes: number
60
+ ) => {
61
+ const selected: string[] = [];
62
+ let used = 0;
63
+ for (const line of candidates.slice(0, maxEntries)) {
64
+ const next = used + byteLength(line) + (selected.length ? 1 : 0);
65
+ if (next > maxContentBytes) {
66
+ break;
67
+ }
68
+ selected.push(line);
69
+ used = next;
70
+ }
71
+ return selected;
72
+ };
73
+ const availableBytes = maxBytes - markerBytes - 2;
74
+ const head = collect(lines, headCount, Math.floor(availableBytes / 2));
75
+ const tail = collect(
76
+ lines.slice(Math.max(head.length, lines.length - tailCount)),
77
+ tailCount,
78
+ availableBytes - byteLength(head.join("\n"))
79
+ );
80
+ const content = [...head, OMITTED_MARKER, ...tail].join("\n");
81
+ return {
82
+ content,
83
+ omittedLines: Math.max(0, totalLines - head.length - tail.length),
84
+ totalBytes,
85
+ totalLines,
86
+ truncated: true,
87
+ };
88
+ };
@@ -0,0 +1,155 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ readTrackedFiles,
4
+ readUntrackedFiles,
5
+ type UntrackedAttachment,
6
+ } from "../attachments.ts";
7
+ import {
8
+ advisorGitContextMaxCharsRef,
9
+ advisorGitContextRef,
10
+ advisorRedactSecretsRef,
11
+ advisorScoutEnabledRef,
12
+ advisorTrackedFileContentRef,
13
+ advisorUntrackedContentRef,
14
+ contextMaxCharsRef,
15
+ } from "../config/state.ts";
16
+ import {
17
+ clampGitContextLevel,
18
+ collectGitContext,
19
+ type GitContextLevel,
20
+ } from "../git.ts";
21
+ import { readProjectPreferences } from "../preferences.ts";
22
+ import { redactAndCapText, redactSecrets } from "../redaction.ts";
23
+ import {
24
+ runAdvisorScout,
25
+ type ScoutLifecycleEvent,
26
+ type ScoutOutcome,
27
+ } from "../scout.ts";
28
+ import { curateAdvisorConversation } from "../scout-curation.ts";
29
+ import {
30
+ advisorGitContextBudget,
31
+ advisorRepositoryContext,
32
+ advisorRequestConversation,
33
+ } from "./prompts.ts";
34
+
35
+ /** Maximum bytes for project preferences and the redacted draft. */
36
+ const ATTACHMENT_TEXT_MAX_BYTES = 8 * 1024;
37
+ /** Combined budget for untracked plus tracked file attachments. */
38
+ const ATTACHMENTS_TOTAL_MAX_BYTES = 24 * 1024;
39
+
40
+ export interface ConsultationContextOptions {
41
+ ctx: ExtensionContext;
42
+ currentInvocationId?: string;
43
+ draft?: string;
44
+ gitContext?: GitContextLevel;
45
+ includeTracked?: string[];
46
+ includeUntracked?: string[];
47
+ onScout?: (event: ScoutLifecycleEvent) => void;
48
+ signal?: AbortSignal;
49
+ }
50
+
51
+ export interface ConsultationContext {
52
+ /** Disclosed repository changes header. */
53
+ changeText: string;
54
+ /** Curated (or legacy) conversation body. */
55
+ conversation: string;
56
+ /** Redacted draft text, if a draft was supplied. */
57
+ draftText?: string;
58
+ /** Redacted project preferences, if present. */
59
+ preferences?: { bytes: number; text: string };
60
+ /** Non-cancelling Scout outcome, when Scout ran. */
61
+ scout?: Exclude<ScoutOutcome, { cancelled: true }>;
62
+ /** Redacted tracked-file attachments. */
63
+ tracked: UntrackedAttachment[];
64
+ /** Redacted untracked-file attachments. */
65
+ untracked: UntrackedAttachment[];
66
+ }
67
+
68
+ /** Assembles every context region the Advisor request embeds: repository
69
+ * changes (spending the shared budget first), the curated conversation,
70
+ * project preferences, and consented file attachments. */
71
+ export const assembleConsultationContext = async (
72
+ options: ConsultationContextOptions
73
+ ): Promise<ConsultationContext> => {
74
+ const { ctx } = options;
75
+
76
+ // The user setting is the ceiling; the Executor may only narrow it.
77
+ const allowed = advisorGitContextRef;
78
+ const level = clampGitContextLevel(options.gitContext ?? allowed, allowed);
79
+ const gitBudget = advisorGitContextBudget(
80
+ contextMaxCharsRef,
81
+ advisorGitContextMaxCharsRef
82
+ );
83
+ const changes = collectGitContext(
84
+ ctx.cwd,
85
+ level,
86
+ gitBudget,
87
+ advisorRedactSecretsRef ? redactSecrets : undefined
88
+ );
89
+ // The disclosure warning is control metadata, not repository payload. Keep it
90
+ // outside the zero-byte Git budget so disabling disclosure cannot erase it.
91
+ const changeText = advisorRepositoryContext(
92
+ changes,
93
+ options.gitContext ?? allowed,
94
+ level,
95
+ gitBudget
96
+ );
97
+ // Repository context spends part of the shared budget, so a large patch
98
+ // cannot silently push the conversation past the model's context window.
99
+ const conversationBudget = Math.max(
100
+ 0,
101
+ contextMaxCharsRef - changeText.length
102
+ );
103
+ const legacyConversation = advisorRequestConversation(
104
+ ctx,
105
+ conversationBudget
106
+ );
107
+ const curated = await curateAdvisorConversation(
108
+ ctx,
109
+ legacyConversation,
110
+ options.signal,
111
+ options.onScout,
112
+ advisorScoutEnabledRef,
113
+ runAdvisorScout,
114
+ options.currentInvocationId,
115
+ conversationBudget
116
+ );
117
+ const preferences = await readProjectPreferences(
118
+ ctx,
119
+ ATTACHMENT_TEXT_MAX_BYTES,
120
+ advisorRedactSecretsRef
121
+ );
122
+ const draftText = options.draft
123
+ ? redactAndCapText(
124
+ options.draft,
125
+ ATTACHMENT_TEXT_MAX_BYTES,
126
+ advisorRedactSecretsRef
127
+ )
128
+ : undefined;
129
+ const untracked = await readUntrackedFiles(
130
+ ctx.cwd,
131
+ options.includeUntracked ?? [],
132
+ advisorUntrackedContentRef,
133
+ advisorRedactSecretsRef
134
+ );
135
+ const tracked = await readTrackedFiles(
136
+ ctx.cwd,
137
+ options.includeTracked ?? [],
138
+ advisorTrackedFileContentRef,
139
+ advisorRedactSecretsRef,
140
+ Math.max(
141
+ 0,
142
+ ATTACHMENTS_TOTAL_MAX_BYTES -
143
+ untracked.reduce((sum, item) => sum + item.bytes, 0)
144
+ )
145
+ );
146
+ return {
147
+ changeText,
148
+ conversation: curated.conversation,
149
+ draftText,
150
+ preferences,
151
+ scout: curated.scout,
152
+ tracked,
153
+ untracked,
154
+ };
155
+ };
@@ -3,201 +3,60 @@ import type { Message } from "@earendil-works/pi-ai/compat";
3
3
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
4
  import {
5
5
  advisorEffortRef,
6
- advisorGitContextMaxCharsRef,
7
- advisorGitContextRef,
8
6
  advisorRedactSecretsRef,
9
7
  advisorRef,
10
- advisorScoutEnabledRef,
11
- advisorTrackedFileContentRef,
12
- advisorUntrackedContentRef,
13
- contextMaxCharsRef,
14
- executorRef,
15
- } from "../config/state.js";
16
- import { loadConfig } from "../config/storage.js";
17
- import { redactAndCapText, redactSecrets } from "../conversation.js";
18
- import {
19
- clampGitContextLevel,
20
- collectGitContext,
21
- type GitContextLevel,
22
- } from "../git.js";
23
- import { collectTextStream, resolveConfiguredModel } from "../model-stream.js";
24
- import { readProjectPreferences } from "../preferences.js";
25
- import {
26
- runAdvisorScout,
27
- type ScoutLifecycleEvent,
28
- type ScoutOutcome,
29
- } from "../scout.js";
30
- import {
31
- buildScoutManifest,
32
- reconstructScoutConversation,
33
- SCOUT_MANIFEST_MAX_BYTES,
34
- } from "../scout-context.js";
35
- import type { ConsultationTrigger, GateTrigger } from "../session-state.js";
36
- import { readTrackedFiles, readUntrackedFiles } from "../untracked.js";
37
- import { parseAutomaticDecision } from "./gate-protocol.js";
8
+ } from "../config/state.ts";
9
+ import { loadConfig } from "../config/storage.ts";
10
+ import type { GitContextLevel } from "../git.ts";
11
+ import { collectTextStream, resolveConfiguredModel } from "../model-stream.ts";
12
+ import { redactSecrets } from "../redaction.ts";
13
+ import type { ScoutLifecycleEvent } from "../scout.ts";
14
+ import type { ConsultationTrigger, GateTrigger } from "../session-state.ts";
15
+ import { assembleConsultationContext } from "./consult-context.ts";
16
+ import { parseAutomaticDecision } from "./gate-protocol.ts";
38
17
  import {
39
18
  ADVISOR_DECISION_SYSTEM,
40
19
  ADVISOR_SYSTEM,
41
- advisorGitContextBudget,
42
20
  advisorMessageText,
43
- advisorRepositoryContext,
44
- advisorRequestConversation,
45
- } from "./prompts.js";
46
- import type { AdvisorConsultationResult, AdvisorGateOutcome } from "./types.js";
21
+ } from "./prompts.ts";
22
+ import type { AdvisorConsultationResult, AdvisorGateOutcome } from "./types.ts";
47
23
 
48
- export const curateAdvisorConversation = async (
49
- ctx: ExtensionContext,
50
- legacyConversation: string,
51
- signal?: AbortSignal,
52
- onScout?: (event: ScoutLifecycleEvent) => void,
53
- enabled = advisorScoutEnabledRef,
54
- runScout: typeof runAdvisorScout = runAdvisorScout,
55
- currentInvocationId?: string,
56
- maxChars?: number
57
- ): Promise<{
58
- conversation: string;
59
- scout?: Exclude<ScoutOutcome, { cancelled: true }>;
60
- }> => {
61
- if (!enabled) {
62
- return { conversation: legacyConversation };
63
- }
64
- if (maxChars !== undefined && maxChars <= 0) {
65
- return { conversation: "" };
66
- }
67
- const built = buildScoutManifest(ctx, {
68
- currentInvocationId,
69
- maxConversationChars: maxChars,
70
- maxManifestBytes: SCOUT_MANIFEST_MAX_BYTES,
71
- });
72
- if (!built.ok) {
73
- const scout: Exclude<ScoutOutcome, { cancelled: true }> = {
74
- category: built.reason,
75
- message: built.message,
76
- metrics: {
77
- availableCount: 0,
78
- inputBytes: 0,
79
- latencyMs: 0,
80
- omittedBeforeScout: 0,
81
- selectedCount: 0,
82
- },
83
- model: executorRef,
84
- ok: false,
85
- };
86
- onScout?.({ outcome: scout, type: "fallback" });
87
- return { conversation: legacyConversation, scout };
88
- }
89
- const outcome = await runScout(
90
- ctx,
91
- built.manifest,
92
- signal,
93
- onScout,
94
- undefined,
95
- undefined
96
- );
97
- if (!outcome.ok && outcome.cancelled) {
98
- throw signal?.reason instanceof Error
99
- ? signal.reason
100
- : new Error("Advisor operation cancelled during Scout.");
101
- }
102
- let conversation = legacyConversation;
103
- if (outcome.ok) {
104
- conversation =
105
- maxChars === undefined
106
- ? outcome.conversation
107
- : reconstructScoutConversation(
108
- built.manifest,
109
- outcome.selection.selectedIds,
110
- outcome.selection.synthesis,
111
- maxChars
112
- );
24
+ // biome-ignore lint/performance/noBarrelFile: preserves the tools facade's historical re-export of the moved curation entry point.
25
+ export { curateAdvisorConversation } from "../scout-curation.ts";
26
+
27
+ /** Thrown when the Advisor produced an empty response body. */
28
+ class AdvisorNoAdviceError extends Error {
29
+ constructor() {
30
+ super("Advisor returned no advice.");
31
+ this.name = "AdvisorNoAdviceError";
113
32
  }
114
- return { conversation, scout: outcome };
115
- };
33
+ }
34
+
35
+ interface CollectAdvisorResponseOptions {
36
+ ctx: ExtensionContext;
37
+ currentInvocationId?: string;
38
+ draft?: string;
39
+ gitContext?: GitContextLevel;
40
+ includeTracked?: string[];
41
+ includeUntracked?: string[];
42
+ onChunk?: (thinking: string, text: string) => void;
43
+ onScout?: (event: ScoutLifecycleEvent) => void;
44
+ question?: string;
45
+ signal?: AbortSignal;
46
+ systemPrompt: string;
47
+ }
48
+
49
+ const fileTag = (item: { path: string; text: string }) =>
50
+ `<file path=${JSON.stringify(item.path)}>\n${item.text}\n</file>`;
116
51
 
117
52
  const collectAdvisorResponse = async (
118
- ctx: ExtensionContext,
119
- systemPrompt: string,
120
- question: string | undefined,
121
- signal: AbortSignal | undefined,
122
- onChunk: ((thinking: string, text: string) => void) | undefined,
123
- gitContext?: GitContextLevel,
124
- draft?: string,
125
- includeUntracked?: string[],
126
- includeTracked?: string[],
127
- onScout?: (event: ScoutLifecycleEvent) => void,
128
- currentInvocationId?: string
53
+ options: CollectAdvisorResponseOptions
129
54
  ) => {
55
+ const { ctx, question, signal, systemPrompt } = options;
130
56
  loadConfig(ctx);
131
57
  const resolved = await resolveConfiguredModel(ctx, advisorRef, "Advisor");
58
+ const context = await assembleConsultationContext(options);
132
59
 
133
- // The user setting is the ceiling; the Executor may only narrow it.
134
- const allowed = advisorGitContextRef;
135
- const level = clampGitContextLevel(gitContext ?? allowed, allowed);
136
- const gitBudget = advisorGitContextBudget(
137
- contextMaxCharsRef,
138
- advisorGitContextMaxCharsRef
139
- );
140
- const changes = collectGitContext(
141
- ctx.cwd,
142
- level,
143
- gitBudget,
144
- advisorRedactSecretsRef ? redactSecrets : undefined
145
- );
146
- // The note is placed first so a cap can never drop the statement that the
147
- // Advisor's view of the repository is limited.
148
- // The disclosure warning is control metadata, not repository payload. Keep it
149
- // outside the zero-byte Git budget so disabling disclosure cannot erase it.
150
- const changeText = advisorRepositoryContext(
151
- changes,
152
- gitContext ?? allowed,
153
- level,
154
- gitBudget
155
- );
156
- // Repository context spends part of the shared budget, so a large patch
157
- // cannot silently push the conversation past the model's context window.
158
- const conversationBudget = Math.max(
159
- 0,
160
- contextMaxCharsRef - changeText.length
161
- );
162
- const legacyConversation = advisorRequestConversation(
163
- ctx,
164
- conversationBudget
165
- );
166
- const curated = await curateAdvisorConversation(
167
- ctx,
168
- legacyConversation,
169
- signal,
170
- onScout,
171
- advisorScoutEnabledRef,
172
- runAdvisorScout,
173
- currentInvocationId,
174
- conversationBudget
175
- );
176
- const { conversation, scout } = curated;
177
- const preferences = await readProjectPreferences(
178
- ctx,
179
- 8 * 1024,
180
- advisorRedactSecretsRef
181
- );
182
- const draftText = draft
183
- ? redactAndCapText(draft, 8 * 1024, advisorRedactSecretsRef)
184
- : undefined;
185
- const untracked = await readUntrackedFiles(
186
- ctx.cwd,
187
- includeUntracked ?? [],
188
- advisorUntrackedContentRef,
189
- advisorRedactSecretsRef
190
- );
191
- const tracked = await readTrackedFiles(
192
- ctx.cwd,
193
- includeTracked ?? [],
194
- advisorTrackedFileContentRef,
195
- advisorRedactSecretsRef,
196
- Math.max(
197
- 0,
198
- 24 * 1024 - untracked.reduce((sum, item) => sum + item.bytes, 0)
199
- )
200
- );
201
60
  // Keep the original question available to local UI callers, but never send
202
61
  // its credential-shaped values to the provider when redaction is enabled.
203
62
  const outboundQuestion =
@@ -209,19 +68,13 @@ const collectAdvisorResponse = async (
209
68
  content: [
210
69
  {
211
70
  text: advisorMessageText(
212
- conversation,
71
+ context.conversation,
213
72
  outboundQuestion,
214
- changeText,
215
- draftText,
216
- preferences?.text,
217
- untracked.map(
218
- (item) =>
219
- `<file path=${JSON.stringify(item.path)}>\n${item.text}\n</file>`
220
- ),
221
- tracked.map(
222
- (item) =>
223
- `<file path=${JSON.stringify(item.path)}>\n${item.text}\n</file>`
224
- )
73
+ context.changeText,
74
+ context.draftText,
75
+ context.preferences?.text,
76
+ context.untracked.map(fileTag),
77
+ context.tracked.map(fileTag)
225
78
  ),
226
79
  type: "text",
227
80
  },
@@ -233,27 +86,29 @@ const collectAdvisorResponse = async (
233
86
 
234
87
  const streamed = await collectTextStream(resolved, {
235
88
  messages,
236
- onChunk,
89
+ onChunk: options.onChunk,
237
90
  reasoning: advisorEffortRef,
238
91
  signal,
239
92
  systemPrompt,
240
93
  });
241
94
  const markdown = streamed.text;
242
95
  if (!markdown.trim()) {
243
- throw new Error("Advisor returned no advice.");
96
+ throw new AdvisorNoAdviceError();
244
97
  }
245
98
  return {
246
- draftBytes: draftText ? Buffer.byteLength(draftText, "utf8") : undefined,
99
+ draftBytes: context.draftText
100
+ ? Buffer.byteLength(context.draftText, "utf8")
101
+ : undefined,
247
102
  markdown,
248
103
  model: advisorRef,
249
- preferenceBytes: preferences?.bytes,
104
+ preferenceBytes: context.preferences?.bytes,
250
105
  thinkingText: streamed.thinking,
251
106
  trackedBytes:
252
- tracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
107
+ context.tracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
253
108
  untrackedBytes:
254
- untracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
109
+ context.untracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
255
110
  usage: streamed.usage,
256
- ...(scout ? { scout } : {}),
111
+ ...(context.scout ? { scout: context.scout } : {}),
257
112
  };
258
113
  };
259
114
 
@@ -270,19 +125,19 @@ export const consultAdvisor = async (
270
125
  onScout?: (event: ScoutLifecycleEvent) => void,
271
126
  currentInvocationId?: string
272
127
  ): Promise<AdvisorConsultationResult> => {
273
- const result = await collectAdvisorResponse(
128
+ const result = await collectAdvisorResponse({
274
129
  ctx,
275
- ADVISOR_SYSTEM,
276
- question,
277
- signal,
278
- onChunk,
279
- gitContext,
130
+ currentInvocationId,
280
131
  draft,
281
- includeUntracked,
132
+ gitContext,
282
133
  includeTracked,
134
+ includeUntracked,
135
+ onChunk,
283
136
  onScout,
284
- currentInvocationId
285
- );
137
+ question,
138
+ signal,
139
+ systemPrompt: ADVISOR_SYSTEM,
140
+ });
286
141
  return { ...result, adviceId: randomUUID(), trigger };
287
142
  };
288
143
 
@@ -296,19 +151,15 @@ export const runAdvisorGate = async (
296
151
  currentInvocationId?: string
297
152
  ): Promise<AdvisorGateOutcome> => {
298
153
  try {
299
- const result = await collectAdvisorResponse(
154
+ const result = await collectAdvisorResponse({
300
155
  ctx,
301
- ADVISOR_DECISION_SYSTEM,
302
- question,
303
- signal,
156
+ currentInvocationId,
304
157
  onChunk,
305
- undefined,
306
- undefined,
307
- undefined,
308
- undefined,
309
158
  onScout,
310
- currentInvocationId
311
- );
159
+ question,
160
+ signal,
161
+ systemPrompt: ADVISOR_DECISION_SYSTEM,
162
+ });
312
163
  const parsed = parseAutomaticDecision(result.markdown);
313
164
  if (!parsed.ok) {
314
165
  return { ...parsed, usage: result.usage };
@@ -327,7 +178,7 @@ export const runAdvisorGate = async (
327
178
  const message = error instanceof Error ? error.message : String(error);
328
179
  return {
329
180
  category:
330
- message === "Advisor returned no advice."
181
+ error instanceof AdvisorNoAdviceError
331
182
  ? "empty-response"
332
183
  : "provider-error",
333
184
  message,
@@ -8,11 +8,11 @@ import {
8
8
  getAdvisorMaxCallsPerSession,
9
9
  getAdvisorSettings,
10
10
  isSimpleMode,
11
- } from "../config/state.js";
12
- import type { GateFailureMode } from "../config/types.js";
13
- import { herdrAdvisorBlock, notifyHerdrAdvisorFailure } from "../herdr.js";
14
- import type { AdvisorSessionState } from "../session-state.js";
15
- import type { AdvisorGateResult, GateFailureCategory } from "./types.js";
11
+ } from "../config/state.ts";
12
+ import type { GateFailureMode } from "../config/types.ts";
13
+ import { herdrAdvisorBlock, notifyHerdrAdvisorFailure } from "../herdr.ts";
14
+ import type { AdvisorSessionState } from "../session-state.ts";
15
+ import type { AdvisorGateResult, GateFailureCategory } from "./types.ts";
16
16
 
17
17
  export const updateAdvisorUsageStatus = (
18
18
  ctx: ExtensionContext,
@@ -1,5 +1,5 @@
1
- import type { GateDecision } from "../session-state.js";
2
- import type { AdvisorGateFailure, AdvisorGateResult } from "./types.js";
1
+ import type { GateDecision } from "../session-state.ts";
2
+ import type { AdvisorGateFailure, AdvisorGateResult } from "./types.ts";
3
3
 
4
4
  const DECISION_LINE = /^Decision\s*:\s*(proceed|revise|blocked)\s*$/i;
5
5
  const CODE_FENCE = /^ {0,3}(`{3,}|~{3,})(.*)$/;