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.
- package/CHANGELOG.md +28 -1
- package/README.md +1 -1
- package/dist/index.js +5786 -0
- package/extensions/index.ts +9 -9
- package/package.json +16 -10
- 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 +113 -103
- 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 +10 -3
- package/src/ui/manual-dialog.ts +4 -9
- package/src/ui/model-selector.ts +7 -6
- 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 +6 -1
- package/src/ui/types.ts +2 -2
- package/src/ui.ts +4 -4
- package/src/usage.ts +1 -6
package/src/tools/loop-gate.ts
CHANGED
|
@@ -12,24 +12,25 @@ import {
|
|
|
12
12
|
advisorScoutEnabledRef,
|
|
13
13
|
getAdvisorMaxCallsPerSession,
|
|
14
14
|
isSimpleMode,
|
|
15
|
-
} from "../config/state.
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import
|
|
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.
|
|
27
|
-
import { adviceForGateText } from "./gate-protocol.
|
|
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.
|
|
32
|
-
import type { AdvisorGateResult, ScoutToolDetails } from "./types.
|
|
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
|
-
|
|
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();
|
package/src/tools/prompts.ts
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
advisorPlanGateRef,
|
|
7
7
|
contextMaxCharsRef,
|
|
8
8
|
isSimpleMode,
|
|
9
|
-
} from "../config/state.
|
|
10
|
-
import { recentConversation } from "../conversation.
|
|
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.
|
|
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.
|
|
9
|
-
import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "../herdr.
|
|
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.
|
|
13
|
+
} from "../model-stream.ts";
|
|
14
14
|
import {
|
|
15
15
|
advisorUsageCost,
|
|
16
16
|
advisorUsageForPi,
|
|
17
17
|
snapshotAdvisorUsage,
|
|
18
|
-
} from "../usage.
|
|
19
|
-
import { notifyLocalFailure, updateAdvisorUsageStatus } from "./gate-policy.
|
|
20
|
-
import { renderAdvisorResult } from "./render-advisor-result.
|
|
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.
|
|
25
|
-
import { scoutDetailsFromEvent } from "./scout-status.
|
|
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.
|
|
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.
|
|
6
|
-
import { loadConfig } from "../config/storage.
|
|
7
|
-
import { herdrAdvisorBlock } from "../herdr.
|
|
8
|
-
import { reserveAdvisorCall } from "./gate-policy.
|
|
9
|
-
import { handleAutomaticGate } from "./loop-gate.
|
|
10
|
-
import { advisorInvocationGuidelines } from "./prompts.
|
|
11
|
-
import type { ToolRegistrationContext } from "./types.
|
|
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.
|
|
4
|
-
import { loadConfig } from "../config/storage.
|
|
5
|
-
import { textFrom } from "../conversation.
|
|
6
|
-
import { ADOPTIONS, VALIDATIONS } from "../outcomes.
|
|
7
|
-
import type { ToolRegistrationContext } from "./types.
|
|
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.
|
|
7
|
-
import { formatAdvisorUsage } from "../usage.
|
|
8
|
-
import { adviceForDisplay, renderAdvisorCallBox } from "./render-common.
|
|
9
|
-
import { renderScoutDetails } from "./scout-status.
|
|
10
|
-
import type { GateDecision, ScoutToolDetails } from "./types.
|
|
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.
|
|
3
|
-
import type { AdvisorSessionState } from "../session-state.
|
|
4
|
-
import { consultAdvisor, runAdvisorGate } from "./consultation.
|
|
5
|
-
import { registerAskAdvisorTool } from "./register-ask-advisor.
|
|
6
|
-
import { registerToolLifecycle } from "./register-lifecycle.
|
|
7
|
-
import { registerOutcomeTool } from "./register-outcome.
|
|
8
|
-
import { registerToolRenderers } from "./register-renderers.
|
|
9
|
-
import { ScoutStatusManager } from "./scout-status.
|
|
10
|
-
import { advisorSessionState } from "./session.
|
|
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.
|
|
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.
|
|
9
|
-
import { textFrom } from "../conversation.
|
|
10
|
-
import { formatAdvisorUsage } from "../usage.
|
|
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.
|
|
18
|
-
import { renderScoutDetails } from "./scout-status.
|
|
19
|
-
import type { AdvisorToolContext, AdvisorToolDetails } from "./types.
|
|
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,
|
|
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.
|
|
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 =>
|
|
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.
|
|
8
|
-
import type { ScoutLifecycleEvent } from "../scout.
|
|
9
|
-
import { formatAdvisorUsage, snapshotAdvisorUsage } from "../usage.
|
|
10
|
-
import { renderThinkingMarkdown, SPINNER_FRAMES } from "./render-common.
|
|
11
|
-
import type { ScoutToolDetails } from "./types.
|
|
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,
|
package/src/tools/session.ts
CHANGED
package/src/tools/types.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { appendOutcome } from "../outcomes.
|
|
3
|
-
import type { ScoutOutcome } from "../scout.
|
|
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.
|
|
10
|
-
import type { consultAdvisor, runAdvisorGate } from "./consultation.
|
|
11
|
-
import type { ScoutStatusManager } from "./scout-status.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
7
|
-
export { gateFailureEffectForMode } from "./tools/gate-policy.
|
|
8
|
-
export { parseAutomaticDecision } from "./tools/gate-protocol.
|
|
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.
|
|
19
|
-
export { registerAdvisorTool } from "./tools/registration.
|
|
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.
|
|
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.
|
|
35
|
-
export { advisorSessionState } from "./tools/session.
|
|
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.
|
|
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.
|
|
7
|
-
import type { ManualAdvisorDialogView } from "./types.
|
|
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")}
|
|
146
|
+
`${focusMarker("actions")}${" ".repeat(buttonOffset)}${actionButtons}`
|
|
140
147
|
);
|
|
141
148
|
addWrapped(interactionHint, "dim");
|
|
142
149
|
addLine("");
|
package/src/ui/manual-dialog.ts
CHANGED
|
@@ -12,24 +12,19 @@ import {
|
|
|
12
12
|
clampGitContextLevel,
|
|
13
13
|
GIT_CONTEXT_LEVELS,
|
|
14
14
|
type GitContextLevel,
|
|
15
|
-
} from "../git.
|
|
16
|
-
import { renderManualAdvisorDialog } from "./manual-dialog-render.
|
|
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.
|
|
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;
|
package/src/ui/model-selector.ts
CHANGED
|
@@ -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.
|
|
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[] = ["
|
|
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 -
|
|
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("
|
|
112
|
+
` ${this.theme.fg("dim", "Type to search · ↑↓: navigate · Enter: select · Esc: cancel")}`
|
|
112
113
|
);
|
|
113
|
-
lines.push("
|
|
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
|
|
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.
|