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
@@ -12,24 +12,25 @@ import {
12
12
  advisorScoutEnabledRef,
13
13
  getAdvisorMaxCallsPerSession,
14
14
  isSimpleMode,
15
- } from "../config/state.js";
16
- import { herdrAdvisorActivity } from "../herdr.js";
17
- import type { AdvisorSessionState } from "../session-state.js";
18
- import { advisorUsageCost, snapshotAdvisorUsage } from "../usage.js";
19
- import type { runAdvisorGate } from "./consultation.js";
15
+ } from "../config/state.ts";
16
+ import type { GateFailureMode } from "../config/types.ts";
17
+ import { herdrAdvisorActivity } from "../herdr.ts";
18
+ import type { AdvisorSessionState } from "../session-state.ts";
19
+ import { advisorUsageCost, snapshotAdvisorUsage } from "../usage.ts";
20
+ import type { runAdvisorGate } from "./consultation.ts";
20
21
  import {
21
22
  blockedDecisionEffect,
22
23
  failureEffect,
23
24
  gateDecisionEffect,
24
25
  gateFailureEffectForMode,
25
26
  updateAdvisorUsageStatus,
26
- } from "./gate-policy.js";
27
- import { adviceForGateText } from "./gate-protocol.js";
27
+ } from "./gate-policy.ts";
28
+ import { adviceForGateText } from "./gate-protocol.ts";
28
29
  import {
29
30
  appendScoutLifecycleEntry,
30
31
  type ScoutStatusManager,
31
- } from "./scout-status.js";
32
- import type { AdvisorGateResult, ScoutToolDetails } from "./types.js";
32
+ } from "./scout-status.ts";
33
+ import type { AdvisorGateResult, ScoutToolDetails } from "./types.ts";
33
34
 
34
35
  const sendAutomaticGateCall = (pi: ExtensionAPI, event: ToolCallEvent) => {
35
36
  pi.sendMessage(
@@ -87,6 +88,65 @@ const sendAutomaticGateResult = (
87
88
  );
88
89
  };
89
90
 
91
+ /** Records the invocation, reports it, and maps the gate decision to its
92
+ * tool-call result (block reason or proceed). */
93
+ const applyGateDecision = (
94
+ pi: ExtensionAPI,
95
+ ctx: ExtensionContext,
96
+ session: AdvisorSessionState,
97
+ result: Awaited<ReturnType<typeof runAdvisorGate>>,
98
+ reason: string,
99
+ failureMode: GateFailureMode
100
+ ): ToolCallEventResult | undefined => {
101
+ if (!result.ok) {
102
+ session.recordInvocation({
103
+ executionEffect: gateFailureEffectForMode(failureMode),
104
+ failure: result.category,
105
+ kind: "gate",
106
+ model: advisorRef,
107
+ trigger: "repeated-tool-call",
108
+ usage: result.usage,
109
+ });
110
+ updateAdvisorUsageStatus(ctx, session);
111
+ const failure = failureEffect(
112
+ result.category,
113
+ result.message,
114
+ ctx,
115
+ session,
116
+ failureMode
117
+ );
118
+ sendAutomaticGateFailure(
119
+ pi,
120
+ `**Advisor gate failure (${result.category}):** ${result.message}`,
121
+ result.usage
122
+ );
123
+ return failure.block
124
+ ? { block: true, reason: `${reason}\n${failure.reason}` }
125
+ : undefined;
126
+ }
127
+ session.recordInvocation({
128
+ cost: advisorUsageCost(result.usage),
129
+ decision: result.decision,
130
+ executionEffect: gateDecisionEffect(result.decision, failureMode),
131
+ kind: "gate",
132
+ model: result.model,
133
+ trigger: result.trigger,
134
+ usage: result.usage,
135
+ });
136
+ updateAdvisorUsageStatus(ctx, session);
137
+ sendAutomaticGateResult(pi, result);
138
+ if (result.decision === "proceed") {
139
+ session.resetRepetition();
140
+ return;
141
+ }
142
+ const gateReason = `Advisor loop review: ${result.markdown}`;
143
+ if (result.decision === "blocked") {
144
+ const effect = blockedDecisionEffect(gateReason, ctx, session, failureMode);
145
+ return effect.block ? { block: true, reason: effect.reason } : undefined;
146
+ }
147
+ return { block: true, reason: gateReason };
148
+ };
149
+
90
150
  export const handleAutomaticGate = async (
91
151
  pi: ExtensionAPI,
92
152
  event: ToolCallEvent,
@@ -151,58 +211,7 @@ export const handleAutomaticGate = async (
151
211
  event.toolCallId
152
212
  );
153
213
  ensureGateCall();
154
- if (!result.ok) {
155
- session.recordInvocation({
156
- executionEffect: gateFailureEffectForMode(failureMode),
157
- failure: result.category,
158
- kind: "gate",
159
- model: advisorRef,
160
- trigger: "repeated-tool-call",
161
- usage: result.usage,
162
- });
163
- updateAdvisorUsageStatus(ctx, session);
164
- const failure = failureEffect(
165
- result.category,
166
- result.message,
167
- ctx,
168
- session,
169
- failureMode
170
- );
171
- sendAutomaticGateFailure(
172
- pi,
173
- `**Advisor gate failure (${result.category}):** ${result.message}`,
174
- result.usage
175
- );
176
- return failure.block
177
- ? { block: true, reason: `${reason}\n${failure.reason}` }
178
- : undefined;
179
- }
180
- session.recordInvocation({
181
- cost: advisorUsageCost(result.usage),
182
- decision: result.decision,
183
- executionEffect: gateDecisionEffect(result.decision, failureMode),
184
- kind: "gate",
185
- model: result.model,
186
- trigger: result.trigger,
187
- usage: result.usage,
188
- });
189
- updateAdvisorUsageStatus(ctx, session);
190
- sendAutomaticGateResult(pi, result);
191
- if (result.decision === "proceed") {
192
- session.resetRepetition();
193
- return;
194
- }
195
- const gateReason = `Advisor loop review: ${result.markdown}`;
196
- if (result.decision === "blocked") {
197
- const effect = blockedDecisionEffect(
198
- gateReason,
199
- ctx,
200
- session,
201
- failureMode
202
- );
203
- return effect.block ? { block: true, reason: effect.reason } : undefined;
204
- }
205
- return { block: true, reason: gateReason };
214
+ return applyGateDecision(pi, ctx, session, result, reason, failureMode);
206
215
  } finally {
207
216
  scoutStatus.release(ctx, scoutStatusToken);
208
217
  herdrAdvisorActivity.finish();
@@ -6,14 +6,14 @@ import {
6
6
  advisorPlanGateRef,
7
7
  contextMaxCharsRef,
8
8
  isSimpleMode,
9
- } from "../config/state.js";
10
- import { recentConversation } from "../conversation.js";
9
+ } from "../config/state.ts";
10
+ import { recentConversation } from "../conversation.ts";
11
11
  import {
12
12
  capRepositoryContext,
13
13
  escapeRepositoryText,
14
14
  type GitContextLevel,
15
15
  type GitContextResult,
16
- } from "../git.js";
16
+ } from "../git.ts";
17
17
 
18
18
  export const advisorMessageText = (
19
19
  conversation: string,
@@ -47,10 +47,7 @@ export const advisorMessageText = (
47
47
  );
48
48
  };
49
49
 
50
- /**
51
- * Splits the character budget so repository context can never starve the
52
- * conversation: it may claim its own cap or half the budget, whichever is less.
53
- */
50
+ /** Splits the character budget so repository context can never starve the conversation. */
54
51
  export const advisorGitContextBudget = (
55
52
  contextMaxChars: number,
56
53
  gitContextMaxChars: number
@@ -5,35 +5,30 @@ import {
5
5
  getAdvisorMaxCallsPerSession,
6
6
  getAdvisorSettings,
7
7
  isSimpleMode,
8
- } from "../config/state.js";
9
- import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "../herdr.js";
8
+ } from "../config/state.ts";
9
+ import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "../herdr.ts";
10
10
  import {
11
11
  ADVISOR_STREAM_UPDATE_INTERVAL_MS,
12
12
  createCoalescedUpdate,
13
- } from "../model-stream.js";
13
+ } from "../model-stream.ts";
14
14
  import {
15
15
  advisorUsageCost,
16
16
  advisorUsageForPi,
17
17
  snapshotAdvisorUsage,
18
- } from "../usage.js";
19
- import { notifyLocalFailure, updateAdvisorUsageStatus } from "./gate-policy.js";
20
- import { renderAdvisorResult } from "./render-advisor-result.js";
18
+ } from "../usage.ts";
19
+ import { notifyLocalFailure, updateAdvisorUsageStatus } from "./gate-policy.ts";
20
+ import { renderAdvisorResult } from "./render-advisor-result.ts";
21
21
  import {
22
22
  renderAdvisorCallBox,
23
23
  resolveAdvisorRequest,
24
- } from "./render-common.js";
25
- import { scoutDetailsFromEvent } from "./scout-status.js";
24
+ } from "./render-common.ts";
25
+ import { scoutDetailsFromEvent } from "./scout-status.ts";
26
26
  import type {
27
27
  AdvisorToolContext,
28
28
  AdvisorToolDetails,
29
29
  ToolRegistrationContext,
30
- } from "./types.js";
30
+ } from "./types.ts";
31
31
 
32
- /**
33
- * Validates a tracked-file follow-up and consumes the one-shot handoff only
34
- * when the call is worth it: consent must be enabled and the session budget
35
- * was checked by the caller. A rejected call never burns the claim.
36
- */
37
32
  const claimTrackedHandoff = (
38
33
  session: ToolRegistrationContext["session"],
39
34
  includeTrackedFiles: string[] | undefined
@@ -2,13 +2,13 @@ import {
2
2
  advisorSessionSummaryRef,
3
3
  getAdvisorMaxCallsPerSession,
4
4
  isSimpleMode,
5
- } from "../config/state.js";
6
- import { loadConfig } from "../config/storage.js";
7
- import { herdrAdvisorBlock } from "../herdr.js";
8
- import { reserveAdvisorCall } from "./gate-policy.js";
9
- import { handleAutomaticGate } from "./loop-gate.js";
10
- import { advisorInvocationGuidelines } from "./prompts.js";
11
- import type { ToolRegistrationContext } from "./types.js";
5
+ } from "../config/state.ts";
6
+ import { loadConfig } from "../config/storage.ts";
7
+ import { herdrAdvisorBlock } from "../herdr.ts";
8
+ import { reserveAdvisorCall } from "./gate-policy.ts";
9
+ import { handleAutomaticGate } from "./loop-gate.ts";
10
+ import { advisorInvocationGuidelines } from "./prompts.ts";
11
+ import type { ToolRegistrationContext } from "./types.ts";
12
12
 
13
13
  export const registerToolLifecycle = ({
14
14
  pi,
@@ -1,10 +1,10 @@
1
1
  import { Text } from "@earendil-works/pi-tui";
2
2
  import { Type } from "typebox";
3
- import { advisorOutcomeLoggingRef } from "../config/state.js";
4
- import { loadConfig } from "../config/storage.js";
5
- import { textFrom } from "../conversation.js";
6
- import { ADOPTIONS, VALIDATIONS } from "../outcomes.js";
7
- import type { ToolRegistrationContext } from "./types.js";
3
+ import { advisorOutcomeLoggingRef } from "../config/state.ts";
4
+ import { loadConfig } from "../config/storage.ts";
5
+ import { textFrom } from "../conversation.ts";
6
+ import { ADOPTIONS, VALIDATIONS } from "../outcomes.ts";
7
+ import type { ToolRegistrationContext } from "./types.ts";
8
8
 
9
9
  export const registerOutcomeTool = ({
10
10
  appendOutcome: appendAdvisorOutcome,
@@ -3,11 +3,11 @@ import {
3
3
  getMarkdownTheme,
4
4
  } from "@earendil-works/pi-coding-agent";
5
5
  import { Box, Markdown, Text } from "@earendil-works/pi-tui";
6
- import { getAdvisorSettings } from "../config/state.js";
7
- import { formatAdvisorUsage } from "../usage.js";
8
- import { adviceForDisplay, renderAdvisorCallBox } from "./render-common.js";
9
- import { renderScoutDetails } from "./scout-status.js";
10
- import type { GateDecision, ScoutToolDetails } from "./types.js";
6
+ import { getAdvisorSettings } from "../config/state.ts";
7
+ import { formatAdvisorUsage } from "../usage.ts";
8
+ import { adviceForDisplay, renderAdvisorCallBox } from "./render-common.ts";
9
+ import { renderScoutDetails } from "./scout-status.ts";
10
+ import type { GateDecision, ScoutToolDetails } from "./types.ts";
11
11
 
12
12
  export const registerToolRenderers = (pi: ExtensionAPI): void => {
13
13
  pi.registerEntryRenderer?.(
@@ -1,17 +1,17 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { appendOutcome } from "../outcomes.js";
3
- import type { AdvisorSessionState } from "../session-state.js";
4
- import { consultAdvisor, runAdvisorGate } from "./consultation.js";
5
- import { registerAskAdvisorTool } from "./register-ask-advisor.js";
6
- import { registerToolLifecycle } from "./register-lifecycle.js";
7
- import { registerOutcomeTool } from "./register-outcome.js";
8
- import { registerToolRenderers } from "./register-renderers.js";
9
- import { ScoutStatusManager } from "./scout-status.js";
10
- import { advisorSessionState } from "./session.js";
2
+ import { appendOutcome } from "../outcomes.ts";
3
+ import type { AdvisorSessionState } from "../session-state.ts";
4
+ import { consultAdvisor, runAdvisorGate } from "./consultation.ts";
5
+ import { registerAskAdvisorTool } from "./register-ask-advisor.ts";
6
+ import { registerToolLifecycle } from "./register-lifecycle.ts";
7
+ import { registerOutcomeTool } from "./register-outcome.ts";
8
+ import { registerToolRenderers } from "./register-renderers.ts";
9
+ import { ScoutStatusManager } from "./scout-status.ts";
10
+ import { advisorSessionState } from "./session.ts";
11
11
  import type {
12
12
  ToolRegistrationContext,
13
13
  ToolRegistrationDependencies,
14
- } from "./types.js";
14
+ } from "./types.ts";
15
15
 
16
16
  export const registerAdvisorTool = (
17
17
  pi: ExtensionAPI,
@@ -4,19 +4,19 @@ import {
4
4
  type Theme,
5
5
  type ToolRenderResultOptions,
6
6
  } from "@earendil-works/pi-coding-agent";
7
- import { Box, Markdown, Text } from "@earendil-works/pi-tui";
8
- import { getAdvisorSettings } from "../config/state.js";
9
- import { textFrom } from "../conversation.js";
10
- import { formatAdvisorUsage } from "../usage.js";
7
+ import { Box, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
8
+ import { getAdvisorSettings } from "../config/state.ts";
9
+ import { textFrom } from "../conversation.ts";
10
+ import { formatAdvisorUsage } from "../usage.ts";
11
11
  import {
12
12
  adviceForDisplay,
13
13
  hasSoundVerdict,
14
14
  renderAdvisorResponseHeader,
15
15
  renderThinkingMarkdown,
16
16
  SPINNER_FRAMES,
17
- } from "./render-common.js";
18
- import { renderScoutDetails } from "./scout-status.js";
19
- import type { AdvisorToolContext, AdvisorToolDetails } from "./types.js";
17
+ } from "./render-common.ts";
18
+ import { renderScoutDetails } from "./scout-status.ts";
19
+ import type { AdvisorToolContext, AdvisorToolDetails } from "./types.ts";
20
20
 
21
21
  const advisorResultDetails = (result: AgentToolResult<AdvisorToolDetails>) =>
22
22
  result.details;
@@ -153,10 +153,11 @@ export const renderAdvisorResult = (
153
153
  theme: Theme,
154
154
  context: AdvisorToolContext
155
155
  ) => {
156
+ // Pi frames this inside its own padded tool box; padding here doubles the gap below the request.
156
157
  const box =
157
158
  context.lastComponent instanceof Box
158
159
  ? context.lastComponent
159
- : new Box(1, 1, (text: string) => theme.bg("customMessageBg", text));
160
+ : new Box(1, 0, (text: string) => theme.bg("customMessageBg", text));
160
161
  box.setBgFn((text) => theme.bg("customMessageBg", text));
161
162
  box.clear();
162
163
  if (isPartial) {
@@ -164,5 +165,7 @@ export const renderAdvisorResult = (
164
165
  } else {
165
166
  renderFinalAdvisorResult(box, result, expanded, theme, context);
166
167
  }
168
+ // Restores the bottom breathing room the removed vertical padding provided.
169
+ box.addChild(new Spacer(1));
167
170
  return box;
168
171
  };
@@ -7,7 +7,8 @@ import {
7
7
  truncateToWidth,
8
8
  visibleWidth,
9
9
  } from "@earendil-works/pi-tui";
10
- import { advisorCollapseResponsesRef } from "../config/state.js";
10
+ import { advisorCollapseResponsesRef } from "../config/state.ts";
11
+ import { piHideThinkingEnabled } from "../pi-settings.ts";
11
12
 
12
13
  export const SPINNER_FRAMES = [
13
14
  "⠋",
@@ -22,20 +23,11 @@ export const SPINNER_FRAMES = [
22
23
  "⠏",
23
24
  ];
24
25
 
25
- const THINKING_PREFIX = " 💭 ";
26
+ const THINKING_PREFIX = " ";
26
27
  const THINKING_PREFIX_WIDTH = visibleWidth(THINKING_PREFIX);
27
28
  export type ThinkingTheme = Pick<Theme, "fg">;
28
29
 
29
- /**
30
- * Renders visible nested-model thinking with the same Markdown semantics as
31
- * Pi's assistant thinking blocks while keeping the compact speech-bubble cue.
32
- * The prefix is added after Markdown parsing so it cannot change block syntax.
33
- *
34
- * Note: During streaming, incomplete Markdown (e.g., `**text` without closing **)
35
- * displays raw markers transiently until delimiters arrive. This mirrors expected
36
- * behavior when typing Markdown incrementally and is acceptable in the context
37
- * of brief thinking previews. When thinking is complete, all markers render.
38
- */
30
+ /** Renders nested-model thinking as Markdown with the compact speech-bubble cue; the prefix is added after parsing so it cannot change block syntax. */
39
31
  class ThinkingMarkdown implements Component {
40
32
  private readonly markdown: Markdown;
41
33
  private readonly prefix: string;
@@ -66,10 +58,30 @@ class ThinkingMarkdown implements Component {
66
58
  }
67
59
  }
68
60
 
61
+ /** 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
+ }
77
+
69
78
  export const renderThinkingMarkdown = (
70
79
  thinking: string,
71
80
  theme: ThinkingTheme
72
- ): Component => new ThinkingMarkdown(thinking, theme);
81
+ ): Component =>
82
+ piHideThinkingEnabled()
83
+ ? new HiddenThinkingLabel(theme)
84
+ : new ThinkingMarkdown(thinking, theme);
73
85
 
74
86
  export const resolveAdvisorRequest = (question?: string) =>
75
87
  question?.trim() || undefined;
@@ -4,11 +4,11 @@ import type {
4
4
  Theme,
5
5
  } from "@earendil-works/pi-coding-agent";
6
6
  import { type Box, Text } from "@earendil-works/pi-tui";
7
- import { executorRef, getAdvisorSettings } from "../config/state.js";
8
- import type { ScoutLifecycleEvent } from "../scout.js";
9
- import { formatAdvisorUsage, snapshotAdvisorUsage } from "../usage.js";
10
- import { renderThinkingMarkdown, SPINNER_FRAMES } from "./render-common.js";
11
- import type { ScoutToolDetails } from "./types.js";
7
+ import { executorRef, getAdvisorSettings } from "../config/state.ts";
8
+ import type { ScoutLifecycleEvent } from "../scout.ts";
9
+ import { formatAdvisorUsage, snapshotAdvisorUsage } from "../usage.ts";
10
+ import { renderThinkingMarkdown, SPINNER_FRAMES } from "./render-common.ts";
11
+ import type { ScoutToolDetails } from "./types.ts";
12
12
 
13
13
  export const scoutDetailsFromEvent = (
14
14
  event: ScoutLifecycleEvent,
@@ -1,3 +1,3 @@
1
- import { AdvisorSessionState } from "../session-state.js";
1
+ import { AdvisorSessionState } from "../session-state.ts";
2
2
 
3
3
  export const advisorSessionState = new AdvisorSessionState();
@@ -1,21 +1,21 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import type { appendOutcome } from "../outcomes.js";
3
- import type { ScoutOutcome } from "../scout.js";
2
+ import type { appendOutcome } from "../outcomes.ts";
3
+ import type { ScoutOutcome } from "../scout.ts";
4
4
  import type {
5
5
  AdvisorSessionState,
6
6
  ConsultationTrigger,
7
7
  GateDecision,
8
8
  GateTrigger,
9
- } from "../session-state.js";
10
- import type { consultAdvisor, runAdvisorGate } from "./consultation.js";
11
- import type { ScoutStatusManager } from "./scout-status.js";
9
+ } from "../session-state.ts";
10
+ import type { consultAdvisor, runAdvisorGate } from "./consultation.ts";
11
+ import type { ScoutStatusManager } from "./scout-status.ts";
12
12
 
13
13
  export type {
14
14
  AdvisorInvocationRecord,
15
15
  ConsultationTrigger,
16
16
  GateDecision,
17
17
  GateTrigger,
18
- } from "../session-state.js";
18
+ } from "../session-state.ts";
19
19
 
20
20
  export interface ScoutToolDetails {
21
21
  availableCount?: number;
@@ -89,7 +89,7 @@ export interface AdvisorToolDetails {
89
89
  usage?: unknown;
90
90
  }
91
91
 
92
- export interface AdvisorRenderState {
92
+ interface AdvisorRenderState {
93
93
  phase?: string;
94
94
  scout?: ScoutToolDetails;
95
95
  timerId?: ReturnType<typeof setInterval>;
package/src/tools.ts CHANGED
@@ -3,9 +3,9 @@ export {
3
3
  consultAdvisor,
4
4
  curateAdvisorConversation,
5
5
  runAdvisorGate,
6
- } from "./tools/consultation.js";
7
- export { gateFailureEffectForMode } from "./tools/gate-policy.js";
8
- export { parseAutomaticDecision } from "./tools/gate-protocol.js";
6
+ } from "./tools/consultation.ts";
7
+ export { gateFailureEffectForMode } from "./tools/gate-policy.ts";
8
+ export { parseAutomaticDecision } from "./tools/gate-protocol.ts";
9
9
  export {
10
10
  ADVISOR_DECISION_SYSTEM,
11
11
  ADVISOR_SYSTEM,
@@ -15,8 +15,8 @@ export {
15
15
  advisorRepositoryContext,
16
16
  advisorRequestConversation,
17
17
  gitContextNote,
18
- } from "./tools/prompts.js";
19
- export { registerAdvisorTool } from "./tools/registration.js";
18
+ } from "./tools/prompts.ts";
19
+ export { registerAdvisorTool } from "./tools/registration.ts";
20
20
  export {
21
21
  adviceForDisplay,
22
22
  hasSoundVerdict,
@@ -25,14 +25,14 @@ export {
25
25
  renderThinkingMarkdown,
26
26
  resolveAdvisorRequest,
27
27
  SPINNER_FRAMES,
28
- } from "./tools/render-common.js";
28
+ } from "./tools/render-common.ts";
29
29
  export {
30
30
  appendScoutLifecycleEntry,
31
31
  renderScoutDetails,
32
32
  ScoutStatusManager,
33
33
  scoutDetailsFromEvent,
34
- } from "./tools/scout-status.js";
35
- export { advisorSessionState } from "./tools/session.js";
34
+ } from "./tools/scout-status.ts";
35
+ export { advisorSessionState } from "./tools/session.ts";
36
36
  export type {
37
37
  AdvisorConsultationResult,
38
38
  AdvisorGateFailure,
@@ -44,4 +44,4 @@ export type {
44
44
  GateFailureCategory,
45
45
  GateTrigger,
46
46
  ScoutToolDetails,
47
- } from "./tools/types.js";
47
+ } from "./tools/types.ts";
@@ -3,8 +3,8 @@ import {
3
3
  visibleWidth,
4
4
  wrapTextWithAnsi,
5
5
  } from "@earendil-works/pi-tui";
6
- import type { GitContextLevel } from "../git.js";
7
- import type { ManualAdvisorDialogView } from "./types.js";
6
+ import type { GitContextLevel } from "../git.ts";
7
+ import type { ManualAdvisorDialogView } from "./types.ts";
8
8
 
9
9
  const MANUAL_GIT_CONTEXT_COPY: Record<
10
10
  GitContextLevel,
@@ -135,8 +135,15 @@ export const renderManualAdvisorDialog = (
135
135
  const cancelLabel = cancel
136
136
  ? theme.fg("accent", "[Cancel]")
137
137
  : theme.fg("text", "[Cancel]");
138
+ const actionButtons = `${submitLabel} ${cancelLabel}`;
139
+ // Center the buttons in the content area; the 2-column focus marker sits
140
+ // left of the computed offset.
141
+ const buttonOffset = Math.max(
142
+ 0,
143
+ Math.floor((contentWidth - visibleWidth(actionButtons)) / 2) - 2
144
+ );
138
145
  addLine(
139
- `${focusMarker("actions")} ${submitLabel} ${cancelLabel}`
146
+ `${focusMarker("actions")}${" ".repeat(buttonOffset)}${actionButtons}`
140
147
  );
141
148
  addWrapped(interactionHint, "dim");
142
149
  addLine("");
@@ -12,24 +12,19 @@ import {
12
12
  clampGitContextLevel,
13
13
  GIT_CONTEXT_LEVELS,
14
14
  type GitContextLevel,
15
- } from "../git.js";
16
- import { renderManualAdvisorDialog } from "./manual-dialog-render.js";
15
+ } from "../git.ts";
16
+ import { renderManualAdvisorDialog } from "./manual-dialog-render.ts";
17
17
  import type {
18
18
  ManualAdvisorDialogOptions,
19
19
  ManualAdvisorFocus,
20
- } from "./types.js";
20
+ } from "./types.ts";
21
21
 
22
22
  // Keep the keybinding components separate from Socket's URL-string heuristic.
23
23
  const TUI_INPUT_TAB: keyof Keybindings = ["tui", "input", "tab"].join(
24
24
  "."
25
25
  ) as keyof Keybindings;
26
26
 
27
- /**
28
- * The small, deliberately non-persistent form used by `/advisor-manual`.
29
- *
30
- * The dialog owns only local input state. Callers decide when to consume a
31
- * budget or start a consultation after the submitted request is returned.
32
- */
27
+ /** The small, non-persistent /advisor-manual form; callers own budget and consultation decisions. */
33
28
  export class ManualAdvisorDialog implements Component, Focusable {
34
29
  private readonly options: ManualAdvisorDialogOptions;
35
30
  private readonly editor: Editor;
@@ -6,8 +6,9 @@ import {
6
6
  Input,
7
7
  type Keybindings,
8
8
  type KeybindingsManager,
9
+ truncateToWidth,
9
10
  } from "@earendil-works/pi-tui";
10
- import type { SearchableModelSelectorOptions } from "./types.js";
11
+ import type { SearchableModelSelectorOptions } from "./types.ts";
11
12
 
12
13
  export class SearchableModelSelector implements Component, Focusable {
13
14
  private readonly tui: SearchableModelSelectorOptions["tui"];
@@ -58,9 +59,9 @@ export class SearchableModelSelector implements Component, Focusable {
58
59
  }
59
60
 
60
61
  render(width: number): string[] {
61
- const lines: string[] = ["═".repeat(width)];
62
+ const lines: string[] = [this.theme.fg("border", "─".repeat(width))];
62
63
  lines.push(` ${this.theme.fg("accent", this.theme.bold(this.title))}`);
63
- const inputLines = this.searchInput.render(width - 12);
64
+ const inputLines = this.searchInput.render(Math.max(1, width - 10));
64
65
  lines.push(
65
66
  ` ${this.theme.fg("accent", "Search: ")}${inputLines[0] || ""}`
66
67
  );
@@ -108,10 +109,10 @@ export class SearchableModelSelector implements Component, Focusable {
108
109
  }
109
110
  lines.push("");
110
111
  lines.push(
111
- ` ${this.theme.fg("muted", "Type to search · ↑↓: navigate · Enter: select · Esc: cancel")}`
112
+ ` ${this.theme.fg("dim", "Type to search · ↑↓: navigate · Enter: select · Esc: cancel")}`
112
113
  );
113
- lines.push("═".repeat(width));
114
- return lines;
114
+ lines.push(this.theme.fg("border", "─".repeat(width)));
115
+ return lines.map((line) => truncateToWidth(line, width));
115
116
  }
116
117
 
117
118
  handleInput(keyData: string): void {
@@ -1,7 +1,7 @@
1
1
  import { visibleWidth } from "@earendil-works/pi-tui";
2
- import type { ContextPreset } from "./types.js";
2
+ import { DEFAULT_EFFORT_LEVEL } from "../commands/model-options.ts";
3
+ import type { ContextPreset } from "./types.ts";
3
4
 
4
- export const DEFAULT_EFFORT_LEVEL = "Default (Model Default)";
5
5
  export const TOGGLE_VALUES = ["On", "Off"];
6
6
  export const SIMPLE_MODE_GRADIENT_INTERVAL_MS = 100;
7
7
  // Purple steps with a moving light highlight, retained from the original UI.