pi-advisor-flow 0.5.0 → 0.5.2
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 +54 -0
- package/README.md +37 -71
- package/extensions/index.ts +7 -7
- package/package.json +19 -6
- package/src/commands/activation-preparation.ts +117 -0
- package/src/commands/activation.ts +135 -0
- package/src/commands/lifecycle.ts +83 -0
- package/src/commands/manual-command.ts +107 -0
- package/src/commands/manual-consultation.ts +144 -0
- package/src/commands/manual-progress.ts +93 -0
- package/src/commands/model-commands.ts +66 -0
- package/src/commands/model-options.ts +181 -0
- package/src/commands/model-picker.ts +110 -0
- package/src/commands/registration.ts +27 -0
- package/src/commands/renderers.ts +76 -0
- package/src/commands/runtime.ts +114 -0
- package/src/commands/settings-commands.ts +80 -0
- package/src/commands/settings-persistence.ts +81 -0
- package/src/commands/types.ts +78 -0
- package/src/commands.ts +9 -946
- package/src/config/args.ts +37 -0
- package/src/config/defaults.ts +190 -0
- package/src/config/state.ts +199 -0
- package/src/config/storage.ts +224 -0
- package/src/config/types.ts +63 -0
- package/src/config/validation.ts +234 -0
- package/src/config.ts +104 -791
- package/src/conversation.ts +4 -2
- package/src/herdr.ts +1 -1
- package/src/model-stream.ts +25 -2
- package/src/scout-context.ts +2 -2
- package/src/scout.ts +1 -1
- package/src/tools/consultation.ts +337 -0
- package/src/tools/gate-policy.ts +132 -0
- package/src/tools/gate-protocol.ts +127 -0
- package/src/tools/loop-gate.ts +210 -0
- package/src/tools/prompts.ts +159 -0
- package/src/tools/register-ask-advisor.ts +225 -0
- package/src/tools/register-lifecycle.ts +89 -0
- package/src/tools/register-outcome.ts +75 -0
- package/src/tools/register-renderers.ts +88 -0
- package/src/tools/registration.ts +35 -0
- package/src/tools/render-advisor-result.ts +168 -0
- package/src/tools/render-common.ts +120 -0
- package/src/tools/scout-status.ts +206 -0
- package/src/tools/session.ts +3 -0
- package/src/tools/types.ts +119 -0
- package/src/tools.ts +42 -1811
- package/src/ui/manual-dialog-render.ts +150 -0
- package/src/ui/manual-dialog.ts +305 -0
- package/src/ui/model-selector.ts +166 -0
- package/src/ui/settings-formatting.ts +133 -0
- package/src/ui/settings-items.ts +323 -0
- package/src/ui/settings-list-adapter.ts +103 -0
- package/src/ui/settings-mutations.ts +80 -0
- package/src/ui/settings-selector.ts +161 -0
- package/src/ui/text-setting-submenu.ts +66 -0
- package/src/ui/types.ts +111 -0
- package/src/ui.ts +9 -1418
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
type Component,
|
|
4
|
+
type Focusable,
|
|
5
|
+
SettingsList,
|
|
6
|
+
truncateToWidth,
|
|
7
|
+
} from "@earendil-works/pi-tui";
|
|
8
|
+
import {
|
|
9
|
+
rainbowGradient,
|
|
10
|
+
SIMPLE_MODE_GRADIENT_INTERVAL_MS,
|
|
11
|
+
} from "./settings-formatting.js";
|
|
12
|
+
import { createSettingsItems } from "./settings-items.js";
|
|
13
|
+
import { SettingsListAdapter } from "./settings-list-adapter.js";
|
|
14
|
+
import { mutateAdvisorSettings } from "./settings-mutations.js";
|
|
15
|
+
import type {
|
|
16
|
+
AdvisorSettings,
|
|
17
|
+
AdvisorSettingsSelectorOptions,
|
|
18
|
+
ContextPreset,
|
|
19
|
+
} from "./types.js";
|
|
20
|
+
|
|
21
|
+
export class AdvisorSettingsSelector implements Component, Focusable {
|
|
22
|
+
private readonly options: AdvisorSettingsSelectorOptions;
|
|
23
|
+
private readonly settings: AdvisorSettings;
|
|
24
|
+
private readonly presets: ContextPreset[];
|
|
25
|
+
private settingsList: SettingsListAdapter;
|
|
26
|
+
private simpleModeGradientStartedAt: number | undefined;
|
|
27
|
+
private simpleModeGradientTimer: ReturnType<typeof setInterval> | undefined;
|
|
28
|
+
private _focused = false;
|
|
29
|
+
|
|
30
|
+
get focused(): boolean {
|
|
31
|
+
return this._focused;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
set focused(value: boolean) {
|
|
35
|
+
this._focused = value;
|
|
36
|
+
this.settingsList.setFocused(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
constructor(options: AdvisorSettingsSelectorOptions) {
|
|
40
|
+
this.options = options;
|
|
41
|
+
this.settings = { ...options.initial };
|
|
42
|
+
const configuredContext = this.settings.contextMaxChars;
|
|
43
|
+
this.presets = options.presets.some(
|
|
44
|
+
(preset) => preset.value === configuredContext
|
|
45
|
+
)
|
|
46
|
+
? [...options.presets]
|
|
47
|
+
: [
|
|
48
|
+
...options.presets,
|
|
49
|
+
{
|
|
50
|
+
description: "Current custom context limit",
|
|
51
|
+
label: String(configuredContext),
|
|
52
|
+
value: configuredContext,
|
|
53
|
+
},
|
|
54
|
+
].sort((a, b) => a.value - b.value);
|
|
55
|
+
if (this.settings.simpleMode) {
|
|
56
|
+
this.startSimpleModeGradient();
|
|
57
|
+
}
|
|
58
|
+
this.settingsList = this.createSettingsList();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
invalidate(): void {
|
|
62
|
+
this.settingsList.invalidate();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dispose(): void {
|
|
66
|
+
this.stopSimpleModeGradient();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
render(width: number): string[] {
|
|
70
|
+
const border = this.options.theme.fg(
|
|
71
|
+
"border",
|
|
72
|
+
"─".repeat(Math.max(1, width))
|
|
73
|
+
);
|
|
74
|
+
return [border, ...this.settingsList.render(width), border].map((line) =>
|
|
75
|
+
truncateToWidth(line, width)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
handleInput(keyData: string): void {
|
|
80
|
+
if (
|
|
81
|
+
!this.settingsList.changeWithArrow(keyData, (id, value) =>
|
|
82
|
+
this.change(id, value)
|
|
83
|
+
)
|
|
84
|
+
) {
|
|
85
|
+
this.settingsList.handleInput(keyData);
|
|
86
|
+
}
|
|
87
|
+
this.options.tui.requestRender();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private createSettingsList(selectedId?: string): SettingsListAdapter {
|
|
91
|
+
const listTheme = getSettingsListTheme();
|
|
92
|
+
const defaultLabel = listTheme.label;
|
|
93
|
+
listTheme.label = (text, selected) => {
|
|
94
|
+
if (this.settings.simpleMode && text.startsWith("Simple mode")) {
|
|
95
|
+
return `${rainbowGradient("Simple mode", this.simpleModeGradientStartedAt ?? 0)}${text.slice("Simple mode".length)}`;
|
|
96
|
+
}
|
|
97
|
+
return defaultLabel(text, selected);
|
|
98
|
+
};
|
|
99
|
+
const items = createSettingsItems({
|
|
100
|
+
effortLevels: this.options.effortLevels,
|
|
101
|
+
presets: this.presets,
|
|
102
|
+
settings: this.settings,
|
|
103
|
+
theme: this.options.theme,
|
|
104
|
+
tui: this.options.tui,
|
|
105
|
+
});
|
|
106
|
+
const list = new SettingsList(
|
|
107
|
+
items,
|
|
108
|
+
10,
|
|
109
|
+
listTheme,
|
|
110
|
+
(id, value) => this.change(id, value),
|
|
111
|
+
this.options.onCancel,
|
|
112
|
+
{ enableSearch: true }
|
|
113
|
+
);
|
|
114
|
+
const adapter = new SettingsListAdapter(list);
|
|
115
|
+
if (selectedId) {
|
|
116
|
+
adapter.setSelectedId(items, selectedId);
|
|
117
|
+
}
|
|
118
|
+
return adapter;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private startSimpleModeGradient(): void {
|
|
122
|
+
this.stopSimpleModeGradient();
|
|
123
|
+
this.simpleModeGradientStartedAt = Date.now();
|
|
124
|
+
this.simpleModeGradientTimer = setInterval(() => {
|
|
125
|
+
this.options.tui.requestRender();
|
|
126
|
+
}, SIMPLE_MODE_GRADIENT_INTERVAL_MS);
|
|
127
|
+
this.simpleModeGradientTimer.unref?.();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private stopSimpleModeGradient(): void {
|
|
131
|
+
if (this.simpleModeGradientTimer) {
|
|
132
|
+
clearInterval(this.simpleModeGradientTimer);
|
|
133
|
+
this.simpleModeGradientTimer = undefined;
|
|
134
|
+
}
|
|
135
|
+
this.simpleModeGradientStartedAt = undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private change(id: string, value: string): void {
|
|
139
|
+
mutateAdvisorSettings(this.settings, id, value, this.presets);
|
|
140
|
+
if (id === "simpleMode") {
|
|
141
|
+
if (this.settings.simpleMode) {
|
|
142
|
+
this.startSimpleModeGradient();
|
|
143
|
+
} else {
|
|
144
|
+
this.stopSimpleModeGradient();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
(this.options.onChange ?? this.options.onSave)?.({
|
|
148
|
+
...this.settings,
|
|
149
|
+
showUsageDetails: this.settings.showUsageDetails ?? true,
|
|
150
|
+
toolPolicies: { ...(this.settings.toolPolicies ?? {}) },
|
|
151
|
+
});
|
|
152
|
+
if (
|
|
153
|
+
id === "context" ||
|
|
154
|
+
id === "simpleMode" ||
|
|
155
|
+
id === "customRule" ||
|
|
156
|
+
id === "toolPolicies"
|
|
157
|
+
) {
|
|
158
|
+
this.settingsList = this.createSettingsList(id);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
type Focusable,
|
|
4
|
+
Input,
|
|
5
|
+
truncateToWidth,
|
|
6
|
+
} from "@earendil-works/pi-tui";
|
|
7
|
+
import type { TextSettingSubmenuOptions } from "./types.js";
|
|
8
|
+
|
|
9
|
+
/** Small inline editor used by SettingsList for the two free-form settings. */
|
|
10
|
+
export class TextSettingSubmenu implements Component, Focusable {
|
|
11
|
+
private readonly input = new Input();
|
|
12
|
+
private readonly options: TextSettingSubmenuOptions;
|
|
13
|
+
private _focused = true;
|
|
14
|
+
private error: string | undefined;
|
|
15
|
+
|
|
16
|
+
get focused(): boolean {
|
|
17
|
+
return this._focused;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
set focused(value: boolean) {
|
|
21
|
+
this._focused = value;
|
|
22
|
+
this.input.focused = value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
constructor(options: TextSettingSubmenuOptions) {
|
|
26
|
+
this.options = options;
|
|
27
|
+
this.input.setValue(options.initial);
|
|
28
|
+
this.input.focused = true;
|
|
29
|
+
this.input.onSubmit = (value) => {
|
|
30
|
+
const result = options.onSubmit(value);
|
|
31
|
+
if (result.error) {
|
|
32
|
+
this.error = result.error;
|
|
33
|
+
options.tui.requestRender();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this.error = undefined;
|
|
37
|
+
options.onCancel(result.value);
|
|
38
|
+
};
|
|
39
|
+
this.input.onEscape = () => options.onCancel();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
invalidate(): void {
|
|
43
|
+
this.input.invalidate();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
render(width: number): string[] {
|
|
47
|
+
const { theme } = this.options;
|
|
48
|
+
const input = this.input.render(Math.max(10, width - 4))[0] || "";
|
|
49
|
+
const lines = [
|
|
50
|
+
theme.fg("accent", theme.bold(` ${this.options.title}`)),
|
|
51
|
+
"",
|
|
52
|
+
theme.fg("muted", ` ${this.options.description}`),
|
|
53
|
+
"",
|
|
54
|
+
` ${input}`,
|
|
55
|
+
];
|
|
56
|
+
if (this.error) {
|
|
57
|
+
lines.push(theme.fg("error", ` ${this.error}`));
|
|
58
|
+
}
|
|
59
|
+
lines.push("", theme.fg("dim", " Enter: apply · Esc: cancel"));
|
|
60
|
+
return lines.map((line) => truncateToWidth(line, width));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
handleInput(keyData: string): void {
|
|
64
|
+
this.input.handleInput(keyData);
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/ui/types.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { KeybindingsManager, TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { GateFailureMode } from "../config/types.js";
|
|
4
|
+
import type { GitContextLevel } from "../git.js";
|
|
5
|
+
|
|
6
|
+
export interface RenderRequester {
|
|
7
|
+
requestRender: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SearchableModelSelectorOptions {
|
|
11
|
+
allOptions: string[];
|
|
12
|
+
currentOption?: string;
|
|
13
|
+
keybindings: KeybindingsManager;
|
|
14
|
+
onCancel: () => void;
|
|
15
|
+
onSelect: (value: string) => void;
|
|
16
|
+
theme: Theme;
|
|
17
|
+
title: string;
|
|
18
|
+
tui: RenderRequester;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ManualAdvisorRequest {
|
|
22
|
+
/** The repository disclosure level selected for this consultation. */
|
|
23
|
+
gitContext: GitContextLevel;
|
|
24
|
+
/** The unnormalized message entered in the dialog, when one was supplied. */
|
|
25
|
+
message?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ManualAdvisorDialogOptions {
|
|
29
|
+
gitContext: GitContextLevel;
|
|
30
|
+
initialMessage?: string;
|
|
31
|
+
keybindings: KeybindingsManager;
|
|
32
|
+
onCancel: () => void;
|
|
33
|
+
onSubmit: (request: ManualAdvisorRequest) => void;
|
|
34
|
+
theme: Theme;
|
|
35
|
+
tui: TUI;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type ManualAdvisorFocus = "editor" | "git" | "actions";
|
|
39
|
+
|
|
40
|
+
export interface ManualAdvisorDialogView {
|
|
41
|
+
actionIndex: number;
|
|
42
|
+
editorLines: string[];
|
|
43
|
+
focusTarget: ManualAdvisorFocus;
|
|
44
|
+
gitContext: GitContextLevel;
|
|
45
|
+
gitIndex: number;
|
|
46
|
+
gitLevels: GitContextLevel[];
|
|
47
|
+
horizontalPadding: number;
|
|
48
|
+
renderWidth: number;
|
|
49
|
+
theme: Theme;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ContextPreset {
|
|
53
|
+
description: string;
|
|
54
|
+
label: string;
|
|
55
|
+
value: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface AdvisorSettings {
|
|
59
|
+
alwaysOn?: boolean;
|
|
60
|
+
autoLoopGate?: boolean;
|
|
61
|
+
blockOnBlocked?: boolean;
|
|
62
|
+
collapseResponses: boolean;
|
|
63
|
+
completionGate: boolean;
|
|
64
|
+
contextMaxChars: number;
|
|
65
|
+
customRule?: string;
|
|
66
|
+
effort?: string;
|
|
67
|
+
failureGate: boolean;
|
|
68
|
+
failureMode?: GateFailureMode;
|
|
69
|
+
gitContext?: GitContextLevel;
|
|
70
|
+
gitContextMaxChars?: number;
|
|
71
|
+
herdrIntegration?: boolean;
|
|
72
|
+
loopThreshold?: number;
|
|
73
|
+
maxCallsPerSession?: number;
|
|
74
|
+
outcomeLogging?: boolean;
|
|
75
|
+
planGate: boolean;
|
|
76
|
+
redactSecrets?: boolean;
|
|
77
|
+
scoutEnabled?: boolean;
|
|
78
|
+
sessionSummary?: boolean;
|
|
79
|
+
showUsageDetails?: boolean;
|
|
80
|
+
showUsageFooter?: boolean;
|
|
81
|
+
simpleMode?: boolean;
|
|
82
|
+
toolPolicies?: Record<string, "full" | "summary" | "exclude">;
|
|
83
|
+
toolResultMaxBytes?: number;
|
|
84
|
+
toolResultMaxLines?: number;
|
|
85
|
+
trackedFileContent?: boolean;
|
|
86
|
+
untrackedContent?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface AdvisorSettingsSelectorOptions {
|
|
90
|
+
effortLevels: string[];
|
|
91
|
+
initial: AdvisorSettings;
|
|
92
|
+
onCancel: () => void;
|
|
93
|
+
onChange?: (settings: AdvisorSettings) => void;
|
|
94
|
+
/** @deprecated Use onChange; retained for extensions embedding this component. */
|
|
95
|
+
onSave?: (settings: AdvisorSettings) => void;
|
|
96
|
+
presets: ContextPreset[];
|
|
97
|
+
theme: Theme;
|
|
98
|
+
tui: RenderRequester;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface TextSettingSubmenuOptions {
|
|
102
|
+
description: string;
|
|
103
|
+
initial: string;
|
|
104
|
+
onCancel: (value?: string) => void;
|
|
105
|
+
onSubmit: (value: string) => { error?: string; value?: string };
|
|
106
|
+
theme: Theme;
|
|
107
|
+
title: string;
|
|
108
|
+
tui: RenderRequester;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type SettingValue = string | undefined;
|