pi-advisor-flow 0.8.3 → 0.9.1
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 +24 -0
- package/README.md +6 -5
- package/dist/index.js +685 -96
- package/package.json +14 -14
- package/src/attachments.ts +21 -5
- package/src/commands/activation.ts +6 -1
- package/src/commands/lifecycle.ts +6 -0
- package/src/commands/manual-command.ts +44 -12
- package/src/commands/manual-consultation.ts +7 -3
- package/src/commands/model-commands.ts +1 -0
- package/src/commands/runtime.ts +36 -10
- package/src/commands/settings-commands.ts +2 -0
- package/src/commands/settings-persistence.ts +2 -0
- package/src/commands/types.ts +5 -0
- package/src/config/defaults.ts +7 -0
- package/src/config/schema.ts +7 -0
- package/src/config/state.ts +5 -0
- package/src/config/types.ts +1 -0
- package/src/conversation.ts +29 -7
- package/src/image-attachments.ts +118 -0
- package/src/image-validation.ts +288 -0
- package/src/images.ts +151 -0
- package/src/scout-context.ts +1 -0
- package/src/scout-curation.ts +11 -4
- package/src/scout-groups.ts +13 -3
- package/src/scout-types.ts +2 -0
- package/src/tools/consult-context.ts +78 -2
- package/src/tools/consultation.ts +36 -4
- package/src/tools/jev-turn-gate.ts +4 -1
- package/src/tools/model-access.ts +21 -2
- package/src/tools/prompts.ts +14 -2
- package/src/tools/register-ask-advisor.ts +28 -1
- package/src/tools/register-lifecycle.ts +8 -2
- package/src/tools/render-advisor-result.ts +33 -9
- package/src/tools/scout-status.ts +11 -8
- package/src/tools/types.ts +9 -0
- package/src/ui/settings-items.ts +17 -1
- package/src/ui/settings-mutations.ts +1 -0
- package/src/ui/types.ts +1 -0
- package/src/ui-guard.ts +31 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-advisor-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Advanced Executor/Advisor flow for Pi, fully configurable and extendable.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"advisor",
|
|
@@ -51,26 +51,26 @@
|
|
|
51
51
|
"format:check": "oxfmt --check"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@earendil-works/pi-ai": "0.
|
|
55
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
56
|
-
"@earendil-works/pi-tui": "0.
|
|
57
|
-
"@oxlint/plugins": "1.
|
|
54
|
+
"@earendil-works/pi-ai": "0.99.1",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "0.99.1",
|
|
56
|
+
"@earendil-works/pi-tui": "0.99.1",
|
|
57
|
+
"@oxlint/plugins": "1.86.0",
|
|
58
58
|
"@types/node": "^26.6.3",
|
|
59
59
|
"@typesafe-ai/sdk": "^0.6.0",
|
|
60
60
|
"bun-types": "1.4.2",
|
|
61
61
|
"husky": "^9.1.7",
|
|
62
|
-
"knip": "
|
|
62
|
+
"knip": "6.39.0",
|
|
63
63
|
"lint-staged": "^17.6.0",
|
|
64
|
-
"oxfmt": "0.
|
|
65
|
-
"oxlint": "1.
|
|
64
|
+
"oxfmt": "0.71.0",
|
|
65
|
+
"oxlint": "1.86.0",
|
|
66
66
|
"typebox": "^1.3.34",
|
|
67
67
|
"typescript": "^7.0.2",
|
|
68
|
-
"ultracite": "7.12.
|
|
68
|
+
"ultracite": "7.12.2"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
|
-
"@earendil-works/pi-ai": "^0.87.1",
|
|
72
|
-
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
73
|
-
"@earendil-works/pi-tui": "^0.87.1",
|
|
71
|
+
"@earendil-works/pi-ai": "^0.87.1 || ^0.99.1",
|
|
72
|
+
"@earendil-works/pi-coding-agent": "^0.87.1 || ^0.99.1",
|
|
73
|
+
"@earendil-works/pi-tui": "^0.87.1 || ^0.99.1",
|
|
74
74
|
"typebox": "^1.1.38"
|
|
75
75
|
},
|
|
76
76
|
"peerDependenciesMeta": {
|
|
@@ -92,10 +92,10 @@
|
|
|
92
92
|
"safe-buffer": "npm:@socketregistry/safe-buffer@1.0.9"
|
|
93
93
|
},
|
|
94
94
|
"overrides": {
|
|
95
|
-
"brace-expansion": "5.0.
|
|
95
|
+
"brace-expansion": "5.0.12",
|
|
96
96
|
"is-unicode-supported": "npm:@socketregistry/is-unicode-supported@1.0.5",
|
|
97
97
|
"safe-buffer": "npm:@socketregistry/safe-buffer@1.0.9",
|
|
98
|
-
"undici": "8.
|
|
98
|
+
"undici": "8.11.2"
|
|
99
99
|
},
|
|
100
100
|
"lint-staged": {
|
|
101
101
|
"*.{ts,tsx}": [
|
package/src/attachments.ts
CHANGED
|
@@ -14,16 +14,28 @@ export interface UntrackedAttachment {
|
|
|
14
14
|
text: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
const imageExtensions = new Map([
|
|
18
|
+
[".png", "image/png"],
|
|
19
|
+
[".jpg", "image/jpeg"],
|
|
20
|
+
[".jpeg", "image/jpeg"],
|
|
21
|
+
[".gif", "image/gif"],
|
|
22
|
+
[".webp", "image/webp"],
|
|
23
|
+
]);
|
|
24
|
+
export const imageMime = (path: string) =>
|
|
25
|
+
imageExtensions.get(path.slice(path.lastIndexOf(".")).toLowerCase());
|
|
26
|
+
export const imageFileCandidate = (path: string) =>
|
|
27
|
+
Boolean(imageMime(path)) || /\.(?:avif|bmp|heic|svg|tiff?)$/iu.test(path);
|
|
28
|
+
|
|
17
29
|
const PATH_SEGMENTS = /[\\/]/u;
|
|
18
30
|
|
|
19
|
-
const within = (root: string, candidate: string) => {
|
|
31
|
+
export const within = (root: string, candidate: string) => {
|
|
20
32
|
const path = relative(root, candidate);
|
|
21
33
|
return path !== "" && !path.startsWith("..") && !path.includes("../");
|
|
22
34
|
};
|
|
23
35
|
const normalizeRelativePath = (root: string, path: string) =>
|
|
24
36
|
relative(root, resolve(root, path));
|
|
25
37
|
|
|
26
|
-
const normalizeRequestedPath = (root: string, value: string) => {
|
|
38
|
+
export const normalizeRequestedPath = (root: string, value: string) => {
|
|
27
39
|
if (
|
|
28
40
|
!isString(value) ||
|
|
29
41
|
!value ||
|
|
@@ -46,7 +58,7 @@ const git = (cwd: string, args: string[]) =>
|
|
|
46
58
|
windowsHide: true,
|
|
47
59
|
});
|
|
48
60
|
|
|
49
|
-
const repositoryRoot = (cwd: string) => {
|
|
61
|
+
export const repositoryRoot = (cwd: string) => {
|
|
50
62
|
try {
|
|
51
63
|
return realpath(git(cwd, ["rev-parse", "--show-toplevel"]).trim());
|
|
52
64
|
} catch {
|
|
@@ -86,7 +98,7 @@ const tracked = (cwd: string, path: string) => {
|
|
|
86
98
|
});
|
|
87
99
|
};
|
|
88
100
|
|
|
89
|
-
const isPermitted = (
|
|
101
|
+
export const isPermitted = (
|
|
90
102
|
root: string,
|
|
91
103
|
path: string,
|
|
92
104
|
kind: "tracked" | "untracked"
|
|
@@ -158,7 +170,11 @@ const readFiles = async (
|
|
|
158
170
|
let total = 0;
|
|
159
171
|
for (const name of requested) {
|
|
160
172
|
const normalizedName = normalizeRequestedPath(root, name);
|
|
161
|
-
if (
|
|
173
|
+
if (
|
|
174
|
+
!normalizedName ||
|
|
175
|
+
unique.has(normalizedName) ||
|
|
176
|
+
imageFileCandidate(normalizedName)
|
|
177
|
+
) {
|
|
162
178
|
continue;
|
|
163
179
|
}
|
|
164
180
|
unique.add(normalizedName);
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
setExecutorRef,
|
|
20
20
|
} from "../config/state.ts";
|
|
21
21
|
import { saveConfig } from "../config/storage.ts";
|
|
22
|
+
import { sameModelAdvisorDisabled } from "../tools/model-access.ts";
|
|
22
23
|
import {
|
|
23
24
|
loadCommandConfig,
|
|
24
25
|
prepareActivationModels,
|
|
@@ -132,12 +133,16 @@ export const activateAdvisor = async (
|
|
|
132
133
|
"record_advisor_outcome",
|
|
133
134
|
]);
|
|
134
135
|
}
|
|
136
|
+
const activeModel = isMarkedSubagent()
|
|
137
|
+
? ctx.model
|
|
138
|
+
: findConfiguredModel(ctx, executorRef);
|
|
139
|
+
runtime.updateSameModelNotice(ctx, activeModel);
|
|
135
140
|
if (announce) {
|
|
136
141
|
const activeExecutorRef = effectiveExecutorRef(ctx);
|
|
137
142
|
const activeExecutorEffort = effectiveExecutorEffort(ctx);
|
|
138
143
|
notify(
|
|
139
144
|
ctx,
|
|
140
|
-
`${ADVISOR_ACTIVATION_EXPLANATION}\n\nAdvisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
|
|
145
|
+
`${ADVISOR_ACTIVATION_EXPLANATION}\n\nAdvisor flow ${sameModelAdvisorDisabled(ctx, activeModel) ? "configured" : "ready"} — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
|
|
141
146
|
"info"
|
|
142
147
|
);
|
|
143
148
|
}
|
|
@@ -23,12 +23,15 @@ export const registerCommandLifecycle = (
|
|
|
23
23
|
) => {
|
|
24
24
|
runtime.pi.on("session_start", async (_event, ctx) => {
|
|
25
25
|
runtime.pendingExecutorModelRef = undefined;
|
|
26
|
+
runtime.resetSameModelNotice();
|
|
26
27
|
// A malformed advisor.json or a provider auth failure must not reject a
|
|
27
28
|
// lifecycle handler and break session startup.
|
|
28
29
|
try {
|
|
29
30
|
loadConfig(ctx);
|
|
30
31
|
if (alwaysOnRef) {
|
|
31
32
|
await activateAdvisor("", ctx, false);
|
|
33
|
+
} else {
|
|
34
|
+
runtime.updateSameModelNotice(ctx);
|
|
32
35
|
}
|
|
33
36
|
} catch (error) {
|
|
34
37
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -37,6 +40,9 @@ export const registerCommandLifecycle = (
|
|
|
37
40
|
});
|
|
38
41
|
|
|
39
42
|
runtime.pi.on("model_select", (event, ctx) => {
|
|
43
|
+
if (!runtime.suppressModelSelectionSync) {
|
|
44
|
+
runtime.updateSameModelNotice(ctx, event.model);
|
|
45
|
+
}
|
|
40
46
|
// "restore" replays a stored session model and "cycle" changes the active
|
|
41
47
|
// model without an explicit `/model` choice. Neither should redefine the
|
|
42
48
|
// configured Executor.
|
|
@@ -4,7 +4,11 @@ import {
|
|
|
4
4
|
isSimpleMode,
|
|
5
5
|
} from "../config/state.ts";
|
|
6
6
|
import type { GitContextLevel } from "../git.ts";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
advisorModelAccessReason,
|
|
9
|
+
sameModelAdvisorDisabled,
|
|
10
|
+
sameModelAdvisorNotice,
|
|
11
|
+
} from "../tools/model-access.ts";
|
|
8
12
|
import { resolveAdvisorRequest } from "../tools/render-common.ts";
|
|
9
13
|
import { ManualAdvisorDialog } from "../ui/manual-dialog.ts";
|
|
10
14
|
import type { ManualAdvisorRequest } from "../ui/types.ts";
|
|
@@ -21,6 +25,10 @@ export const registerManualCommand = (runtime: CommandRuntime) => {
|
|
|
21
25
|
if (!loadCommandConfig(ctx)) {
|
|
22
26
|
return;
|
|
23
27
|
}
|
|
28
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
29
|
+
notify(ctx, sameModelAdvisorNotice, "info");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
24
32
|
const accessReason = advisorModelAccessReason(ctx);
|
|
25
33
|
if (accessReason) {
|
|
26
34
|
notify(ctx, accessReason, "warning");
|
|
@@ -82,6 +90,10 @@ export const registerManualCommand = (runtime: CommandRuntime) => {
|
|
|
82
90
|
question = resolveAdvisorRequest(args);
|
|
83
91
|
}
|
|
84
92
|
|
|
93
|
+
if (sameModelAdvisorDisabled(ctx)) {
|
|
94
|
+
notify(ctx, sameModelAdvisorNotice, "info");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
85
97
|
if (!isSimpleMode()) {
|
|
86
98
|
runtime.advisorSessionState.consumeCall();
|
|
87
99
|
}
|
|
@@ -100,18 +112,38 @@ export const registerManualCommand = (runtime: CommandRuntime) => {
|
|
|
100
112
|
runtime.scoutStatus.register(scoutStatusToken);
|
|
101
113
|
runtime.manualConsultations.set(controller, scoutStatusToken);
|
|
102
114
|
runtime.pi.appendEntry?.("advisor-manual-call", { progressId, question });
|
|
115
|
+
// Print mode tears the session down when the handler returns, so the
|
|
116
|
+
// consultation must complete inside the handler to keep its ctx valid.
|
|
117
|
+
if (ctx.mode === "print") {
|
|
118
|
+
await startManualConsultation(
|
|
119
|
+
runtime,
|
|
120
|
+
ctx,
|
|
121
|
+
question,
|
|
122
|
+
controller,
|
|
123
|
+
scoutStatusToken,
|
|
124
|
+
progress,
|
|
125
|
+
gitContext
|
|
126
|
+
);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
103
129
|
// Intentional fire-and-forget: the consultation streams after the command
|
|
104
|
-
// handler returns.
|
|
105
|
-
//
|
|
106
|
-
void
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
130
|
+
// handler returns. A rejected consultation must not escape as an
|
|
131
|
+
// unhandled rejection; its error paths already notify and record.
|
|
132
|
+
void (async () => {
|
|
133
|
+
try {
|
|
134
|
+
await startManualConsultation(
|
|
135
|
+
runtime,
|
|
136
|
+
ctx,
|
|
137
|
+
question,
|
|
138
|
+
controller,
|
|
139
|
+
scoutStatusToken,
|
|
140
|
+
progress,
|
|
141
|
+
gitContext
|
|
142
|
+
);
|
|
143
|
+
} catch {
|
|
144
|
+
// Consultation error paths already notify and record; nothing to add.
|
|
145
|
+
}
|
|
146
|
+
})();
|
|
115
147
|
},
|
|
116
148
|
});
|
|
117
149
|
};
|
|
@@ -7,6 +7,7 @@ import { notifyHerdrAdvisorFailure } from "../herdr.ts";
|
|
|
7
7
|
import { normalizeScreeningQuestion } from "../tools/jev-filter.ts";
|
|
8
8
|
import { appendScoutLifecycleEntry } from "../tools/scout-status.ts";
|
|
9
9
|
import type { ScoutToolDetails } from "../tools/types.ts";
|
|
10
|
+
import { uiAvailable } from "../ui-guard.ts";
|
|
10
11
|
import { advisorUsageCost, snapshotAdvisorUsage } from "../usage.ts";
|
|
11
12
|
import type { AdvisorUsageSnapshot } from "../usage.ts";
|
|
12
13
|
import { notify } from "./runtime.ts";
|
|
@@ -28,9 +29,10 @@ export const startManualConsultation = async (
|
|
|
28
29
|
progress: ManualAdvisorProgressState,
|
|
29
30
|
gitContext?: GitContextLevel
|
|
30
31
|
) => {
|
|
32
|
+
const isPrintMode = ctx.mode === "print";
|
|
31
33
|
progress.phase = "preparing";
|
|
32
34
|
runtime.requestManualRender(ctx);
|
|
33
|
-
if (ctx
|
|
35
|
+
if (uiAvailable(ctx)) {
|
|
34
36
|
const timer = setInterval(() => {
|
|
35
37
|
if (
|
|
36
38
|
controller.signal.aborted ||
|
|
@@ -118,8 +120,10 @@ export const startManualConsultation = async (
|
|
|
118
120
|
{
|
|
119
121
|
// Steer lets the current turn finish its active work; the Executor sees
|
|
120
122
|
// the result before its next model call rather than being interrupted.
|
|
123
|
+
// Print mode has no follow-up turn; triggering one trips pi's turn_end
|
|
124
|
+
// entry resolution during teardown.
|
|
121
125
|
deliverAs: "steer",
|
|
122
|
-
triggerTurn:
|
|
126
|
+
triggerTurn: !isPrintMode,
|
|
123
127
|
}
|
|
124
128
|
);
|
|
125
129
|
} catch (error) {
|
|
@@ -147,7 +151,7 @@ export const startManualConsultation = async (
|
|
|
147
151
|
},
|
|
148
152
|
display: true,
|
|
149
153
|
},
|
|
150
|
-
{ deliverAs: "steer", triggerTurn:
|
|
154
|
+
{ deliverAs: "steer", triggerTurn: !isPrintMode }
|
|
151
155
|
);
|
|
152
156
|
notify(ctx, `Advisor consultation failed: ${message}`, "error");
|
|
153
157
|
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
@@ -57,6 +57,7 @@ export const registerModelCommands = (runtime: CommandRuntime) => {
|
|
|
57
57
|
persistExecutor: true,
|
|
58
58
|
});
|
|
59
59
|
runtime.pendingExecutorModelRef = undefined;
|
|
60
|
+
runtime.updateSameModelNotice(ctx);
|
|
60
61
|
ctx.ui.notify(
|
|
61
62
|
`Saved Executor + Advisor configurations to ${path}`,
|
|
62
63
|
"info"
|
package/src/commands/runtime.ts
CHANGED
|
@@ -7,8 +7,13 @@ import { getAdvisorSettings } from "../config/state.ts";
|
|
|
7
7
|
import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "../herdr.ts";
|
|
8
8
|
import type { AdvisorSessionState } from "../session-state.ts";
|
|
9
9
|
import { consultAdvisor } from "../tools/consultation.ts";
|
|
10
|
+
import {
|
|
11
|
+
sameModelAdvisorDisabled,
|
|
12
|
+
sameModelAdvisorNotice,
|
|
13
|
+
} from "../tools/model-access.ts";
|
|
10
14
|
import { ScoutStatusManager } from "../tools/scout-status.ts";
|
|
11
15
|
import { advisorSessionState as defaultAdvisorSessionState } from "../tools/session.ts";
|
|
16
|
+
import { uiAction } from "../ui-guard.ts";
|
|
12
17
|
import type {
|
|
13
18
|
CommandDependencies,
|
|
14
19
|
CommandRuntime as CommandRuntimeContract,
|
|
@@ -21,9 +26,7 @@ export const notify = (
|
|
|
21
26
|
message: string,
|
|
22
27
|
level: "error" | "info" | "warning"
|
|
23
28
|
) => {
|
|
24
|
-
|
|
25
|
-
ctx.ui.notify(message, level);
|
|
26
|
-
}
|
|
29
|
+
uiAction(ctx, (ui) => ui.notify(message, level));
|
|
27
30
|
};
|
|
28
31
|
|
|
29
32
|
const reportManualBudgetExhausted = (ctx: ExtensionContext) => {
|
|
@@ -33,9 +36,7 @@ const reportManualBudgetExhausted = (ctx: ExtensionContext) => {
|
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
const requestManualRender = (ctx: ExtensionContext) => {
|
|
36
|
-
|
|
37
|
-
ctx.ui.setStatus("advisor-manual", undefined);
|
|
38
|
-
}
|
|
39
|
+
uiAction(ctx, (ui) => ui.setStatus("advisor-manual", undefined));
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
class CommandRuntime implements CommandRuntimeContract {
|
|
@@ -55,6 +56,7 @@ class CommandRuntime implements CommandRuntimeContract {
|
|
|
55
56
|
manualProgressSequence = 0;
|
|
56
57
|
pendingExecutorModelRef: string | undefined;
|
|
57
58
|
suppressModelSelectionSync = false;
|
|
59
|
+
private lastSameModelDisabled: boolean | undefined;
|
|
58
60
|
|
|
59
61
|
constructor(pi: ExtensionAPI, dependencies: CommandDependencies = {}) {
|
|
60
62
|
this.pi = pi;
|
|
@@ -84,6 +86,30 @@ class CommandRuntime implements CommandRuntimeContract {
|
|
|
84
86
|
return this.pi.getActiveTools().includes("ask_advisor");
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
resetSameModelNotice() {
|
|
90
|
+
this.lastSameModelDisabled = undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
updateSameModelNotice(
|
|
94
|
+
ctx: ExtensionContext,
|
|
95
|
+
model: { id: string; provider: string } | undefined = ctx.model
|
|
96
|
+
) {
|
|
97
|
+
if (!this.flowEnabled()) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const disabled = sameModelAdvisorDisabled(ctx, model);
|
|
101
|
+
if (disabled && this.lastSameModelDisabled !== true) {
|
|
102
|
+
notify(ctx, sameModelAdvisorNotice, "info");
|
|
103
|
+
} else if (!disabled && this.lastSameModelDisabled === true) {
|
|
104
|
+
notify(
|
|
105
|
+
ctx,
|
|
106
|
+
"Advisor re-enabled: executor and advisor models differ.",
|
|
107
|
+
"info"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
this.lastSameModelDisabled = disabled;
|
|
111
|
+
}
|
|
112
|
+
|
|
87
113
|
nextManualProgressId() {
|
|
88
114
|
this.manualProgressSequence += 1;
|
|
89
115
|
return `manual-${this.manualProgressSequence}`;
|
|
@@ -101,14 +127,14 @@ class CommandRuntime implements CommandRuntimeContract {
|
|
|
101
127
|
}
|
|
102
128
|
|
|
103
129
|
updateAdvisorUsageStatus(ctx: ExtensionContext) {
|
|
104
|
-
|
|
105
|
-
|
|
130
|
+
uiAction(ctx, (ui) =>
|
|
131
|
+
ui.setStatus(
|
|
106
132
|
"advisor-usage",
|
|
107
133
|
getAdvisorSettings().showUsageFooter
|
|
108
134
|
? this.advisorSessionState.usageStatus()
|
|
109
135
|
: undefined
|
|
110
|
-
)
|
|
111
|
-
|
|
136
|
+
)
|
|
137
|
+
);
|
|
112
138
|
}
|
|
113
139
|
}
|
|
114
140
|
|
|
@@ -36,6 +36,7 @@ export const registerSettingsCommands = (runtime: CommandRuntime) => {
|
|
|
36
36
|
onChange: (settings) => {
|
|
37
37
|
try {
|
|
38
38
|
saveAdvisorSettings(ctx, settings);
|
|
39
|
+
runtime.updateSameModelNotice(ctx);
|
|
39
40
|
runtime.updateAdvisorUsageStatus(ctx);
|
|
40
41
|
} catch (error) {
|
|
41
42
|
const message =
|
|
@@ -65,6 +66,7 @@ export const registerSettingsCommands = (runtime: CommandRuntime) => {
|
|
|
65
66
|
name !== "ask_advisor" && name !== "record_advisor_outcome"
|
|
66
67
|
)
|
|
67
68
|
);
|
|
69
|
+
runtime.resetSameModelNotice();
|
|
68
70
|
// Leaving alwaysOn set would silently reactivate the flow next session.
|
|
69
71
|
const wasAlwaysOn = alwaysOnRef;
|
|
70
72
|
if (wasAlwaysOn) {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
setAdvisorCollapseResponsesRef,
|
|
8
8
|
setAdvisorCompletionGateRef,
|
|
9
9
|
setAdvisorCustomInvocationRef,
|
|
10
|
+
setAdvisorDisableSameModelRef,
|
|
10
11
|
setAdvisorEffortRef,
|
|
11
12
|
setAdvisorFailureGateRef,
|
|
12
13
|
setAdvisorFailureModeRef,
|
|
@@ -68,6 +69,7 @@ const applySessionSettings = (settings: AdvisorSettings) => {
|
|
|
68
69
|
setAdvisorPlanGateRef(settings.planGate);
|
|
69
70
|
setAdvisorFailureGateRef(settings.failureGate);
|
|
70
71
|
setAdvisorCompletionGateRef(settings.completionGate);
|
|
72
|
+
setAdvisorDisableSameModelRef(settings.disableSameModel ?? true);
|
|
71
73
|
setAdvisorCollapseResponsesRef(settings.collapseResponses);
|
|
72
74
|
setAdvisorCustomInvocationRef(settings.customRule);
|
|
73
75
|
setAdvisorBlockOnBlockedRef(settings.blockOnBlocked ?? true);
|
package/src/commands/types.ts
CHANGED
|
@@ -71,5 +71,10 @@ export interface CommandRuntime {
|
|
|
71
71
|
model: Parameters<ExtensionAPI["setModel"]>[0]
|
|
72
72
|
) => ReturnType<ExtensionAPI["setModel"]>;
|
|
73
73
|
suppressModelSelectionSync: boolean;
|
|
74
|
+
resetSameModelNotice: () => void;
|
|
75
|
+
updateSameModelNotice: (
|
|
76
|
+
ctx: ExtensionContext,
|
|
77
|
+
model?: { id: string; provider: string }
|
|
78
|
+
) => void;
|
|
74
79
|
updateAdvisorUsageStatus: (ctx: ExtensionContext) => void;
|
|
75
80
|
}
|
package/src/config/defaults.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
setAdvisorCollapseResponsesRef,
|
|
5
5
|
setAdvisorCompletionGateRef,
|
|
6
6
|
setAdvisorCustomInvocationRef,
|
|
7
|
+
setAdvisorDisableSameModelRef,
|
|
7
8
|
setAdvisorEffortRef,
|
|
8
9
|
setAdvisorFailureGateRef,
|
|
9
10
|
setAdvisorFailureModeRef,
|
|
@@ -75,6 +76,7 @@ export const resetDefaults = () => {
|
|
|
75
76
|
setAdvisorFailureGateRef(true);
|
|
76
77
|
setAdvisorCompletionGateRef(true);
|
|
77
78
|
setAdvisorCustomInvocationRef(undefined);
|
|
79
|
+
setAdvisorDisableSameModelRef(true);
|
|
78
80
|
setAdvisorCollapseResponsesRef(false);
|
|
79
81
|
setAdvisorBlockOnBlockedRef(true);
|
|
80
82
|
setAdvisorAutoLoopGateRef(true);
|
|
@@ -150,6 +152,11 @@ export const applyConfig = (config: AdvisorConfig) => {
|
|
|
150
152
|
"advisorCustomInvocation",
|
|
151
153
|
setAdvisorCustomInvocationRef
|
|
152
154
|
);
|
|
155
|
+
applyOptionalConfig(
|
|
156
|
+
config,
|
|
157
|
+
"advisorDisableSameModel",
|
|
158
|
+
setAdvisorDisableSameModelRef
|
|
159
|
+
);
|
|
153
160
|
applyOptionalConfig(
|
|
154
161
|
config,
|
|
155
162
|
"advisorCollapseResponses",
|
package/src/config/schema.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
advisorCollapseResponsesRef,
|
|
8
8
|
advisorCompletionGateRef,
|
|
9
9
|
advisorCustomInvocationRef,
|
|
10
|
+
advisorDisableSameModelRef,
|
|
10
11
|
advisorEffortRef,
|
|
11
12
|
advisorFailureGateRef,
|
|
12
13
|
advisorFailureModeRef,
|
|
@@ -200,6 +201,12 @@ export const CONFIG_SCHEMA = {
|
|
|
200
201
|
persisted: true,
|
|
201
202
|
type: "string",
|
|
202
203
|
},
|
|
204
|
+
advisorDisableSameModel: {
|
|
205
|
+
accepted: "true or false",
|
|
206
|
+
current: () => advisorDisableSameModelRef,
|
|
207
|
+
persisted: true,
|
|
208
|
+
type: "boolean",
|
|
209
|
+
},
|
|
203
210
|
advisorEffort: {
|
|
204
211
|
accepted: "a string",
|
|
205
212
|
current: () => advisorEffortRef,
|
package/src/config/state.ts
CHANGED
|
@@ -34,6 +34,7 @@ export let advisorPlanGateRef = true;
|
|
|
34
34
|
export let advisorFailureGateRef = true;
|
|
35
35
|
export let advisorCompletionGateRef = true;
|
|
36
36
|
export let advisorCustomInvocationRef: string | undefined;
|
|
37
|
+
export let advisorDisableSameModelRef = true;
|
|
37
38
|
export let advisorCollapseResponsesRef = false;
|
|
38
39
|
export let advisorBlockOnBlockedRef = true;
|
|
39
40
|
export let advisorAutoLoopGateRef = true;
|
|
@@ -115,6 +116,9 @@ export const setAdvisorCompletionGateRef = (enabled: boolean) => {
|
|
|
115
116
|
export const setAdvisorCustomInvocationRef = (rule: string | undefined) => {
|
|
116
117
|
advisorCustomInvocationRef = rule?.trim() || undefined;
|
|
117
118
|
};
|
|
119
|
+
export const setAdvisorDisableSameModelRef = (enabled: boolean) => {
|
|
120
|
+
advisorDisableSameModelRef = enabled;
|
|
121
|
+
};
|
|
118
122
|
export const setAdvisorCollapseResponsesRef = (enabled: boolean) => {
|
|
119
123
|
advisorCollapseResponsesRef = enabled;
|
|
120
124
|
};
|
|
@@ -237,6 +241,7 @@ export const getAdvisorSettings = () => ({
|
|
|
237
241
|
completionGate: advisorCompletionGateRef,
|
|
238
242
|
contextMaxChars: contextMaxCharsRef,
|
|
239
243
|
customRule: advisorCustomInvocationRef,
|
|
244
|
+
disableSameModel: advisorDisableSameModelRef,
|
|
240
245
|
effort: advisorEffortRef,
|
|
241
246
|
failureGate: advisorFailureGateRef,
|
|
242
247
|
failureMode: advisorFailureModeRef,
|
package/src/config/types.ts
CHANGED
|
@@ -65,6 +65,7 @@ export interface AdvisorConfig {
|
|
|
65
65
|
advisorCollapseResponses?: boolean;
|
|
66
66
|
advisorCompletionGate?: boolean;
|
|
67
67
|
advisorCustomInvocation?: string;
|
|
68
|
+
advisorDisableSameModel?: boolean;
|
|
68
69
|
advisorEffort?: string;
|
|
69
70
|
advisorFailureGate?: boolean;
|
|
70
71
|
advisorGitContext?: GitContextLevel;
|
package/src/conversation.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import type { AdvisorToolPolicies } from "./config/types.ts";
|
|
10
10
|
import { contentParts, isRecordOf, isString } from "./content-utils.ts";
|
|
11
11
|
import type { RecordValue } from "./content-utils.ts";
|
|
12
|
+
import { imageMarker } from "./images.ts";
|
|
12
13
|
import { redactSecrets } from "./redaction.ts";
|
|
13
14
|
import { capToolResult } from "./tool-result-cap.ts";
|
|
14
15
|
|
|
@@ -25,6 +26,11 @@ const textFromPart = <Part>(part: Part): string => {
|
|
|
25
26
|
export const textFrom = <Content>(content: Content): string =>
|
|
26
27
|
contentParts(content).map(textFromPart).join("\n").trim();
|
|
27
28
|
|
|
29
|
+
const imageNotes = (content: RecordValue["content"], nonce: string) =>
|
|
30
|
+
contentParts(content)
|
|
31
|
+
.map((part) => (isRecordOf(part) ? imageMarker(part, nonce) : undefined))
|
|
32
|
+
.filter(isString);
|
|
33
|
+
|
|
28
34
|
const assistantEntry = (
|
|
29
35
|
message: RecordValue,
|
|
30
36
|
policies: AdvisorToolPolicies,
|
|
@@ -64,12 +70,17 @@ const toolResultEntry = (
|
|
|
64
70
|
toolResultMaxLines: number,
|
|
65
71
|
toolResultMaxBytes: number,
|
|
66
72
|
policies: AdvisorToolPolicies,
|
|
67
|
-
redact: boolean
|
|
73
|
+
redact: boolean,
|
|
74
|
+
describeImages: boolean,
|
|
75
|
+
imageNonce: string
|
|
68
76
|
): string => {
|
|
69
77
|
const status = message.isError ? "error" : "success";
|
|
70
78
|
const toolName = isString(message.toolName) ? message.toolName : "unknown";
|
|
71
79
|
const policy = policies[toolName] ?? "full";
|
|
72
|
-
const
|
|
80
|
+
const notes = describeImages ? imageNotes(message.content, imageNonce) : [];
|
|
81
|
+
const source = [textFrom(message.content), ...notes]
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.join("\n");
|
|
73
84
|
if (policy === "exclude") {
|
|
74
85
|
return `[Tool Result for ${toolName}] (excluded by Advisor tool policy)`;
|
|
75
86
|
}
|
|
@@ -95,7 +106,9 @@ export const conversationEntry = <Entry>(
|
|
|
95
106
|
toolResultMaxLines: number,
|
|
96
107
|
toolResultMaxBytes: number,
|
|
97
108
|
policies: AdvisorToolPolicies,
|
|
98
|
-
redact: boolean
|
|
109
|
+
redact: boolean,
|
|
110
|
+
describeImages = false,
|
|
111
|
+
imageNonce = ""
|
|
99
112
|
): string | undefined => {
|
|
100
113
|
if (!isRecordOf(entry)) {
|
|
101
114
|
return;
|
|
@@ -108,7 +121,10 @@ export const conversationEntry = <Entry>(
|
|
|
108
121
|
}
|
|
109
122
|
const { message } = entry;
|
|
110
123
|
if (message.role === "user") {
|
|
111
|
-
const
|
|
124
|
+
const notes = describeImages ? imageNotes(message.content, imageNonce) : [];
|
|
125
|
+
const text = [textFrom(message.content), ...notes]
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.join("\n");
|
|
112
128
|
return text ? `User: ${redact ? redactSecrets(text) : text}` : undefined;
|
|
113
129
|
}
|
|
114
130
|
if (message.role === "assistant") {
|
|
@@ -120,7 +136,9 @@ export const conversationEntry = <Entry>(
|
|
|
120
136
|
toolResultMaxLines,
|
|
121
137
|
toolResultMaxBytes,
|
|
122
138
|
policies,
|
|
123
|
-
redact
|
|
139
|
+
redact,
|
|
140
|
+
describeImages,
|
|
141
|
+
imageNonce
|
|
124
142
|
);
|
|
125
143
|
}
|
|
126
144
|
};
|
|
@@ -179,7 +197,9 @@ export const recentConversation = (
|
|
|
179
197
|
toolResultMaxLines = advisorToolResultMaxLinesRef,
|
|
180
198
|
toolResultMaxBytes = advisorToolResultMaxBytesRef,
|
|
181
199
|
policies = advisorToolPoliciesRef,
|
|
182
|
-
redact = advisorRedactSecretsRef
|
|
200
|
+
redact = advisorRedactSecretsRef,
|
|
201
|
+
describeImages = false,
|
|
202
|
+
imageNonce = ""
|
|
183
203
|
): string => {
|
|
184
204
|
if (maxChars === 0) {
|
|
185
205
|
return "";
|
|
@@ -192,7 +212,9 @@ export const recentConversation = (
|
|
|
192
212
|
toolResultMaxLines,
|
|
193
213
|
toolResultMaxBytes,
|
|
194
214
|
policies,
|
|
195
|
-
redact
|
|
215
|
+
redact,
|
|
216
|
+
describeImages,
|
|
217
|
+
imageNonce
|
|
196
218
|
)
|
|
197
219
|
)
|
|
198
220
|
.filter((entry): entry is string => entry !== undefined);
|