pi-advisor-flow 0.7.0 → 0.8.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 +19 -18
- package/README.md +9 -8
- package/dist/index.js +1390 -1068
- package/extensions/index.ts +2 -1
- package/package.json +48 -35
- package/src/attachments.ts +11 -10
- package/src/commands/activation-preparation.ts +1 -0
- package/src/commands/activation.ts +2 -0
- package/src/commands/lifecycle.ts +1 -0
- package/src/commands/manual-command.ts +7 -1
- package/src/commands/manual-consultation.ts +102 -92
- package/src/commands/manual-progress.ts +7 -5
- package/src/commands/model-commands.ts +1 -1
- package/src/commands/model-options.ts +14 -3
- package/src/commands/model-picker.ts +1 -0
- package/src/commands/registration.ts +1 -0
- package/src/commands/renderers.ts +67 -55
- package/src/commands/runtime.ts +16 -14
- package/src/commands/settings-commands.ts +10 -4
- package/src/commands/settings-persistence.ts +16 -2
- package/src/commands/types.ts +1 -0
- package/src/commands.ts +0 -1
- package/src/config/args.ts +1 -1
- package/src/config/defaults.ts +8 -1
- package/src/config/schema.ts +42 -17
- package/src/config/state.ts +12 -3
- package/src/config/storage.ts +34 -23
- package/src/config/types.ts +16 -3
- package/src/config/validation.ts +33 -21
- package/src/config.ts +3 -0
- package/src/content-utils.ts +24 -4
- package/src/conversation.ts +16 -15
- package/src/git.ts +10 -3
- package/src/herdr-block.ts +83 -0
- package/src/herdr-shared.ts +108 -0
- package/src/herdr.ts +30 -192
- package/src/jev/client.ts +75 -85
- package/src/jev/failure.ts +19 -0
- package/src/jev/key-store.ts +26 -11
- package/src/jev/ledger.ts +58 -64
- package/src/jev/questions.ts +26 -23
- package/src/jev/state.ts +15 -12
- package/src/jev/transport.ts +8 -3
- package/src/model-stream.ts +35 -37
- package/src/outcomes.ts +15 -11
- package/src/pi-settings.ts +4 -9
- package/src/preferences.ts +4 -2
- package/src/redaction.ts +10 -12
- package/src/scout-context.ts +59 -60
- package/src/scout-curation.ts +4 -13
- package/src/scout-groups.ts +178 -175
- package/src/scout-protocol.ts +74 -70
- package/src/scout-reconstruct.ts +1 -1
- package/src/scout.ts +115 -73
- package/src/session-state.ts +64 -49
- package/src/tool-result-cap.ts +18 -17
- package/src/tools/consult-context.ts +7 -15
- package/src/tools/consultation.ts +14 -4
- package/src/tools/gate-policy.ts +1 -0
- package/src/tools/gate-protocol.ts +36 -23
- package/src/tools/jev-filter.ts +43 -41
- package/src/tools/jev-turn-gate.ts +29 -9
- package/src/tools/loop-gate.ts +31 -20
- package/src/tools/model-access.ts +46 -0
- package/src/tools/prompts.ts +20 -25
- package/src/tools/register-ask-advisor.ts +36 -16
- package/src/tools/register-lifecycle.ts +22 -0
- package/src/tools/register-outcome.ts +2 -0
- package/src/tools/register-renderers.ts +146 -128
- package/src/tools/registration.ts +12 -14
- package/src/tools/render-advisor-result.ts +35 -22
- package/src/tools/render-common.ts +16 -19
- package/src/tools/scout-status.ts +42 -27
- package/src/tools/types.ts +1 -0
- package/src/tools.ts +0 -1
- package/src/ui/jev-setup-submenu.ts +65 -59
- package/src/ui/manual-dialog-render.ts +1 -0
- package/src/ui/manual-dialog.ts +39 -43
- package/src/ui/masked-input.ts +5 -11
- package/src/ui/model-multi-selector.ts +9 -0
- package/src/ui/model-selector-adapter.ts +31 -0
- package/src/ui/model-selector.ts +14 -161
- package/src/ui/searchable-model-list.ts +230 -0
- package/src/ui/settings-formatting.ts +24 -14
- package/src/ui/settings-items.ts +38 -1
- package/src/ui/settings-list-adapter.ts +15 -12
- package/src/ui/settings-mutations.ts +157 -70
- package/src/ui/settings-selector.ts +18 -9
- package/src/ui/text-setting-submenu.ts +3 -6
- package/src/ui/types.ts +14 -0
- package/src/ui.ts +0 -1
- package/src/usage.ts +26 -19
package/extensions/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
2
3
|
import { registerCommands } from "../src/commands/registration.ts";
|
|
3
4
|
import { setHerdrBlockedEmitter } from "../src/herdr.ts";
|
|
4
5
|
import { AdvisorSessionState } from "../src/session-state.ts";
|
|
@@ -30,7 +31,7 @@ export const runAdvisorGate = (
|
|
|
30
31
|
...args: Parameters<typeof runAdvisorGateImplementation>
|
|
31
32
|
) => runAdvisorGateImplementation(...args);
|
|
32
33
|
|
|
33
|
-
export default function (pi: ExtensionAPI) {
|
|
34
|
+
export default function registerPiAdvisor(pi: ExtensionAPI) {
|
|
34
35
|
const sessionState = new AdvisorSessionState();
|
|
35
36
|
const scoutStatus = new ScoutStatusManager();
|
|
36
37
|
setHerdrBlockedEmitter((active, label) =>
|
package/package.json
CHANGED
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-advisor-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Advanced Executor/Advisor flow for Pi, fully configurable and extendable.",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"pi-package",
|
|
7
|
-
"pi-extension",
|
|
8
|
-
"pi-coding-agent",
|
|
9
6
|
"advisor",
|
|
10
|
-
"pi-advisor",
|
|
11
7
|
"herdr",
|
|
12
|
-
"jev"
|
|
8
|
+
"jev",
|
|
9
|
+
"pi-advisor",
|
|
10
|
+
"pi-coding-agent",
|
|
11
|
+
"pi-extension",
|
|
12
|
+
"pi-package"
|
|
13
13
|
],
|
|
14
14
|
"homepage": "https://github.com/philipbrembeck/pi-advisor",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Philip Brembeck",
|
|
15
17
|
"repository": {
|
|
16
18
|
"type": "git",
|
|
17
19
|
"url": "https://github.com/philipbrembeck/pi-advisor.git"
|
|
18
20
|
},
|
|
19
|
-
"license": "MIT",
|
|
20
|
-
"author": "Philip Brembeck",
|
|
21
|
-
"type": "module",
|
|
22
|
-
"main": "dist/index.js",
|
|
23
|
-
"types": "extensions/index.ts",
|
|
24
21
|
"files": [
|
|
25
22
|
"dist",
|
|
26
23
|
"extensions",
|
|
@@ -29,6 +26,9 @@
|
|
|
29
26
|
"LICENSE",
|
|
30
27
|
"README.md"
|
|
31
28
|
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "dist/index.js",
|
|
31
|
+
"types": "extensions/index.ts",
|
|
32
32
|
"scripts": {
|
|
33
33
|
"bench:decisions": "bun bench/src/cli.ts decisions",
|
|
34
34
|
"bench:evaluate": "bun bench/src/cli.ts evaluate",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"bench:screen": "bun bench/src/cli.ts screen",
|
|
37
37
|
"build": "bun build ./extensions/index.ts --outfile dist/index.js --target node --format esm --external @earendil-works/pi-ai --external @earendil-works/pi-coding-agent --external @earendil-works/pi-tui --external typebox",
|
|
38
38
|
"check:boundaries": "node scripts/check-module-boundaries.mjs",
|
|
39
|
-
"format": "
|
|
39
|
+
"format": "oxfmt",
|
|
40
40
|
"knip": "knip",
|
|
41
|
-
"lint": "
|
|
42
|
-
"lint:fix": "
|
|
41
|
+
"lint": "oxlint",
|
|
42
|
+
"lint:fix": "oxlint --fix",
|
|
43
43
|
"prepack": "bun run build >/dev/null",
|
|
44
44
|
"package:check": "node scripts/check-package.mjs",
|
|
45
45
|
"prepare": "husky",
|
|
@@ -47,40 +47,30 @@
|
|
|
47
47
|
"test": "bun test && bun run test:node-loader",
|
|
48
48
|
"test:bench": "bun --config=./bunfig.bench.toml test ./bench/test/",
|
|
49
49
|
"test:node-loader": "node --test test/default-resource-loader.node.mjs",
|
|
50
|
-
"typecheck": "tsc --noEmit"
|
|
51
|
-
|
|
52
|
-
"lint-staged": {
|
|
53
|
-
"*.{json,jsonc,ts}": "bun run lint:fix --"
|
|
54
|
-
},
|
|
55
|
-
"resolutions": {
|
|
56
|
-
"is-unicode-supported": "npm:@socketregistry/is-unicode-supported@1.0.5",
|
|
57
|
-
"safe-buffer": "npm:@socketregistry/safe-buffer@1.0.9"
|
|
58
|
-
},
|
|
59
|
-
"overrides": {
|
|
60
|
-
"brace-expansion": "5.0.9",
|
|
61
|
-
"is-unicode-supported": "npm:@socketregistry/is-unicode-supported@1.0.5",
|
|
62
|
-
"safe-buffer": "npm:@socketregistry/safe-buffer@1.0.9",
|
|
63
|
-
"undici": "8.9.0"
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"format:check": "oxfmt --check"
|
|
64
52
|
},
|
|
65
53
|
"devDependencies": {
|
|
66
|
-
"@
|
|
67
|
-
"@earendil-works/pi-
|
|
68
|
-
"@earendil-works/pi-
|
|
69
|
-
"@
|
|
54
|
+
"@earendil-works/pi-ai": "0.87.1",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "0.87.1",
|
|
56
|
+
"@earendil-works/pi-tui": "0.87.1",
|
|
57
|
+
"@oxlint/plugins": "1.85.0",
|
|
70
58
|
"@types/node": "^26.6.2",
|
|
71
59
|
"@typesafe-ai/sdk": "^0.6.0",
|
|
72
60
|
"bun-types": "1.4.2",
|
|
73
61
|
"husky": "^9.1.7",
|
|
74
62
|
"knip": "^6.37.0",
|
|
75
63
|
"lint-staged": "^17.5.1",
|
|
64
|
+
"oxfmt": "0.70.0",
|
|
65
|
+
"oxlint": "1.85.0",
|
|
76
66
|
"typebox": "^1.3.34",
|
|
77
67
|
"typescript": "^7.0.2",
|
|
78
68
|
"ultracite": "7.12.0"
|
|
79
69
|
},
|
|
80
70
|
"peerDependencies": {
|
|
81
|
-
"@earendil-works/pi-ai": "^0.
|
|
82
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
83
|
-
"@earendil-works/pi-tui": "^0.
|
|
71
|
+
"@earendil-works/pi-ai": "^0.85.1 || ^0.87.1",
|
|
72
|
+
"@earendil-works/pi-coding-agent": "^0.85.1 || ^0.87.1",
|
|
73
|
+
"@earendil-works/pi-tui": "^0.85.1 || ^0.87.1",
|
|
84
74
|
"typebox": "^1.1.38"
|
|
85
75
|
},
|
|
86
76
|
"peerDependenciesMeta": {
|
|
@@ -97,6 +87,29 @@
|
|
|
97
87
|
"optional": true
|
|
98
88
|
}
|
|
99
89
|
},
|
|
90
|
+
"resolutions": {
|
|
91
|
+
"is-unicode-supported": "npm:@socketregistry/is-unicode-supported@1.0.5",
|
|
92
|
+
"safe-buffer": "npm:@socketregistry/safe-buffer@1.0.9"
|
|
93
|
+
},
|
|
94
|
+
"overrides": {
|
|
95
|
+
"brace-expansion": "5.0.9",
|
|
96
|
+
"is-unicode-supported": "npm:@socketregistry/is-unicode-supported@1.0.5",
|
|
97
|
+
"safe-buffer": "npm:@socketregistry/safe-buffer@1.0.9",
|
|
98
|
+
"undici": "8.9.0"
|
|
99
|
+
},
|
|
100
|
+
"lint-staged": {
|
|
101
|
+
"*.{ts,tsx}": [
|
|
102
|
+
"oxfmt",
|
|
103
|
+
"oxlint --fix"
|
|
104
|
+
],
|
|
105
|
+
"{src,test,scripts,tools,extensions}/**/*.{ts,tsx}": [
|
|
106
|
+
"oxfmt",
|
|
107
|
+
"oxlint --fix"
|
|
108
|
+
],
|
|
109
|
+
"*.{json,jsonc}": [
|
|
110
|
+
"oxfmt"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
100
113
|
"pi": {
|
|
101
114
|
"extensions": [
|
|
102
115
|
"./dist/index.js"
|
package/src/attachments.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { lstat, open, realpath } from "node:fs/promises";
|
|
4
4
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { isString } from "./content-utils.ts";
|
|
5
7
|
import { redactAndCapText } from "./redaction.ts";
|
|
6
8
|
|
|
7
9
|
export const ADVISOR_FILE_MAX_BYTES = 8 * 1024;
|
|
@@ -12,7 +14,7 @@ export interface UntrackedAttachment {
|
|
|
12
14
|
text: string;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
const PATH_SEGMENTS = /[\\/]
|
|
17
|
+
const PATH_SEGMENTS = /[\\/]/u;
|
|
16
18
|
|
|
17
19
|
const within = (root: string, candidate: string) => {
|
|
18
20
|
const path = relative(root, candidate);
|
|
@@ -21,9 +23,9 @@ const within = (root: string, candidate: string) => {
|
|
|
21
23
|
const normalizeRelativePath = (root: string, path: string) =>
|
|
22
24
|
relative(root, resolve(root, path));
|
|
23
25
|
|
|
24
|
-
const normalizeRequestedPath = (root: string, value:
|
|
26
|
+
const normalizeRequestedPath = (root: string, value: string) => {
|
|
25
27
|
if (
|
|
26
|
-
|
|
28
|
+
!isString(value) ||
|
|
27
29
|
!value ||
|
|
28
30
|
isAbsolute(value) ||
|
|
29
31
|
value.split(PATH_SEGMENTS).includes("..")
|
|
@@ -36,7 +38,7 @@ const normalizeRequestedPath = (root: string, value: unknown) => {
|
|
|
36
38
|
const git = (cwd: string, args: string[]) =>
|
|
37
39
|
execFileSync("git", args, {
|
|
38
40
|
cwd,
|
|
39
|
-
encoding: "
|
|
41
|
+
encoding: "utf-8",
|
|
40
42
|
maxBuffer: 16 * 1024 * 1024,
|
|
41
43
|
shell: false,
|
|
42
44
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -48,7 +50,7 @@ const repositoryRoot = (cwd: string) => {
|
|
|
48
50
|
try {
|
|
49
51
|
return realpath(git(cwd, ["rev-parse", "--show-toplevel"]).trim());
|
|
50
52
|
} catch {
|
|
51
|
-
return Promise.resolve(
|
|
53
|
+
return Promise.resolve();
|
|
52
54
|
}
|
|
53
55
|
};
|
|
54
56
|
|
|
@@ -108,9 +110,9 @@ const readAttachment = async (
|
|
|
108
110
|
if (!within(root, resolved)) {
|
|
109
111
|
return;
|
|
110
112
|
}
|
|
113
|
+
// fs.open needs combined numeric flags for O_NOFOLLOW; O_RDONLY (0) and O_NOFOLLOW occupy disjoint bits, so + is exact.
|
|
111
114
|
const flags = constants.O_NOFOLLOW
|
|
112
|
-
?
|
|
113
|
-
constants.O_RDONLY | constants.O_NOFOLLOW
|
|
115
|
+
? constants.O_RDONLY + constants.O_NOFOLLOW
|
|
114
116
|
: constants.O_RDONLY;
|
|
115
117
|
const file = await open(resolved, flags);
|
|
116
118
|
try {
|
|
@@ -120,13 +122,13 @@ const readAttachment = async (
|
|
|
120
122
|
}
|
|
121
123
|
const buffer = Buffer.alloc(available + 1);
|
|
122
124
|
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
123
|
-
const raw = buffer.subarray(0, bytesRead).toString("
|
|
125
|
+
const raw = buffer.subarray(0, bytesRead).toString("utf-8");
|
|
124
126
|
if (raw.includes("\0")) {
|
|
125
127
|
return;
|
|
126
128
|
}
|
|
127
129
|
const text = redactAndCapText(raw, available, redact);
|
|
128
130
|
return {
|
|
129
|
-
bytes: Buffer.byteLength(text, "
|
|
131
|
+
bytes: Buffer.byteLength(text, "utf-8"),
|
|
130
132
|
path: normalizedName,
|
|
131
133
|
text,
|
|
132
134
|
};
|
|
@@ -169,7 +171,6 @@ const readFiles = async (
|
|
|
169
171
|
break;
|
|
170
172
|
}
|
|
171
173
|
// Sequentially enforce the aggregate disclosure budget.
|
|
172
|
-
// biome-ignore lint/performance/noAwaitInLoops: each accepted file consumes the remaining budget.
|
|
173
174
|
const attachment = await readAttachment(
|
|
174
175
|
root,
|
|
175
176
|
normalizedName,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
2
3
|
import { parseArgs } from "../config/args.ts";
|
|
3
4
|
import {
|
|
4
5
|
advisorEffortRef,
|
|
@@ -116,6 +117,7 @@ export const activateAdvisor = async (
|
|
|
116
117
|
// later attempt.
|
|
117
118
|
runtime.pendingExecutorModelRef = undefined;
|
|
118
119
|
if (executorEffortRef) {
|
|
120
|
+
// SAFETY: executorEffortRef is operator-configured and trusted to name a ThinkingLevel.
|
|
119
121
|
runtime.pi.setThinkingLevel(executorEffortRef as ThinkingLevel);
|
|
120
122
|
}
|
|
121
123
|
if (!runtime.flowEnabled()) {
|
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
isSimpleMode,
|
|
5
5
|
} from "../config/state.ts";
|
|
6
6
|
import type { GitContextLevel } from "../git.ts";
|
|
7
|
+
import { advisorModelAccessReason } from "../tools/model-access.ts";
|
|
7
8
|
import { resolveAdvisorRequest } from "../tools/render-common.ts";
|
|
8
9
|
import { ManualAdvisorDialog } from "../ui/manual-dialog.ts";
|
|
9
10
|
import type { ManualAdvisorRequest } from "../ui/types.ts";
|
|
10
11
|
import { loadCommandConfig } from "./activation-preparation.ts";
|
|
11
12
|
import { startManualConsultation } from "./manual-consultation.ts";
|
|
13
|
+
import { notify } from "./runtime.ts";
|
|
12
14
|
import type { CommandRuntime, ManualAdvisorProgressState } from "./types.ts";
|
|
13
15
|
|
|
14
16
|
export const registerManualCommand = (runtime: CommandRuntime) => {
|
|
@@ -19,6 +21,11 @@ export const registerManualCommand = (runtime: CommandRuntime) => {
|
|
|
19
21
|
if (!loadCommandConfig(ctx)) {
|
|
20
22
|
return;
|
|
21
23
|
}
|
|
24
|
+
const accessReason = advisorModelAccessReason(ctx);
|
|
25
|
+
if (accessReason) {
|
|
26
|
+
notify(ctx, accessReason, "warning");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
22
29
|
if (
|
|
23
30
|
!(
|
|
24
31
|
isSimpleMode() ||
|
|
@@ -96,7 +103,6 @@ export const registerManualCommand = (runtime: CommandRuntime) => {
|
|
|
96
103
|
// Intentional fire-and-forget: the consultation streams after the command
|
|
97
104
|
// handler returns. void satisfies noFloatingPromises; noVoid is ignored here
|
|
98
105
|
// because this Biome version offers no ignoreVoidAsExpression option.
|
|
99
|
-
// biome-ignore lint/complexity/noVoid: marks an intentional fire-and-forget promise
|
|
100
106
|
void startManualConsultation(
|
|
101
107
|
runtime,
|
|
102
108
|
ctx,
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
2
3
|
import { advisorRef } from "../config/state.ts";
|
|
4
|
+
import { isString } from "../content-utils.ts";
|
|
3
5
|
import type { GitContextLevel } from "../git.ts";
|
|
4
6
|
import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "../herdr.ts";
|
|
5
7
|
import { normalizeScreeningQuestion } from "../tools/jev-filter.ts";
|
|
6
8
|
import { appendScoutLifecycleEntry } from "../tools/scout-status.ts";
|
|
7
9
|
import type { ScoutToolDetails } from "../tools/types.ts";
|
|
8
10
|
import { advisorUsageCost, snapshotAdvisorUsage } from "../usage.ts";
|
|
11
|
+
import type { AdvisorUsageSnapshot } from "../usage.ts";
|
|
9
12
|
import { notify } from "./runtime.ts";
|
|
10
13
|
import type { CommandRuntime, ManualAdvisorProgressState } from "./types.ts";
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
interface ManualResultDetails {
|
|
16
|
+
advisor: string;
|
|
17
|
+
question?: string;
|
|
18
|
+
text: string;
|
|
19
|
+
usage?: AdvisorUsageSnapshot;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const startManualConsultation = async (
|
|
13
23
|
runtime: CommandRuntime,
|
|
14
24
|
ctx: ExtensionContext,
|
|
15
25
|
question: string | undefined,
|
|
@@ -35,8 +45,8 @@ export const startManualConsultation = (
|
|
|
35
45
|
runtime.manualProgressTimers.set(controller, timer);
|
|
36
46
|
}
|
|
37
47
|
let scoutDetails: ScoutToolDetails | undefined;
|
|
38
|
-
|
|
39
|
-
.requestAdvisor(
|
|
48
|
+
try {
|
|
49
|
+
const { adviceId, markdown, usage } = await runtime.requestAdvisor(
|
|
40
50
|
ctx,
|
|
41
51
|
question,
|
|
42
52
|
controller.signal,
|
|
@@ -63,96 +73,96 @@ export const startManualConsultation = (
|
|
|
63
73
|
}
|
|
64
74
|
},
|
|
65
75
|
gitContext
|
|
66
|
-
)
|
|
67
|
-
.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
normalizeScreeningQuestion(question)
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
runtime.updateAdvisorUsageStatus(ctx);
|
|
93
|
-
const normalizedUsage = snapshotAdvisorUsage(usage);
|
|
94
|
-
runtime.pi.sendMessage(
|
|
95
|
-
{
|
|
96
|
-
content: `Manual Advisor consultation${question ? ` (${question})` : ""} — for your awareness; no action or follow-up consultation is needed unless the user asks:\n\n${markdown}`,
|
|
97
|
-
customType: "advisor-manual-result",
|
|
98
|
-
details: {
|
|
99
|
-
advisor: advisorRef,
|
|
100
|
-
question,
|
|
101
|
-
text: markdown,
|
|
102
|
-
...(normalizedUsage ? { usage: normalizedUsage } : {}),
|
|
103
|
-
},
|
|
104
|
-
display: true,
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
// Steer lets the current turn finish its active work; the Executor sees
|
|
108
|
-
// the result before its next model call rather than being interrupted.
|
|
109
|
-
deliverAs: "steer",
|
|
110
|
-
triggerTurn: true,
|
|
111
|
-
}
|
|
112
|
-
);
|
|
113
|
-
})
|
|
114
|
-
.catch((error: unknown) => {
|
|
115
|
-
if (controller.signal.aborted) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
progress.phase = "error";
|
|
119
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
120
|
-
runtime.advisorSessionState.resetTurnsSinceConsultation();
|
|
121
|
-
runtime.advisorSessionState.recordInvocation({
|
|
122
|
-
executionEffect: "continued",
|
|
123
|
-
failure: "provider-error",
|
|
124
|
-
kind: "markdown",
|
|
125
|
-
model: advisorRef,
|
|
126
|
-
trigger: "manual",
|
|
127
|
-
});
|
|
128
|
-
runtime.updateAdvisorUsageStatus(ctx);
|
|
129
|
-
runtime.pi.sendMessage(
|
|
130
|
-
{
|
|
131
|
-
content: `Manual Advisor consultation failed: ${message}`,
|
|
132
|
-
customType: "advisor-manual-result",
|
|
133
|
-
details: {
|
|
134
|
-
advisor: advisorRef,
|
|
135
|
-
text: `**Advisor consultation failed:** ${message}`,
|
|
136
|
-
},
|
|
137
|
-
display: true,
|
|
138
|
-
},
|
|
139
|
-
{ deliverAs: "steer", triggerTurn: true }
|
|
76
|
+
);
|
|
77
|
+
if (controller.signal.aborted) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
progress.phase = "complete";
|
|
81
|
+
runtime.advisorSessionState.resetTurnsSinceConsultation();
|
|
82
|
+
runtime.advisorSessionState.recordInvocation({
|
|
83
|
+
cost: advisorUsageCost(usage),
|
|
84
|
+
executionEffect: "continued",
|
|
85
|
+
kind: "markdown",
|
|
86
|
+
model: advisorRef,
|
|
87
|
+
trigger: "manual",
|
|
88
|
+
usage,
|
|
89
|
+
});
|
|
90
|
+
if (isString(adviceId)) {
|
|
91
|
+
// Registering the manual question lets an identical later ask_advisor
|
|
92
|
+
// hit repeat detection instead of re-consulting.
|
|
93
|
+
runtime.advisorSessionState.issueAdvice(
|
|
94
|
+
adviceId,
|
|
95
|
+
markdown,
|
|
96
|
+
"manual",
|
|
97
|
+
false,
|
|
98
|
+
normalizeScreeningQuestion(question)
|
|
140
99
|
);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
100
|
+
}
|
|
101
|
+
runtime.updateAdvisorUsageStatus(ctx);
|
|
102
|
+
const details: ManualResultDetails = {
|
|
103
|
+
advisor: advisorRef,
|
|
104
|
+
question,
|
|
105
|
+
text: markdown,
|
|
106
|
+
};
|
|
107
|
+
const normalizedUsage = snapshotAdvisorUsage(usage);
|
|
108
|
+
if (normalizedUsage) {
|
|
109
|
+
details.usage = normalizedUsage;
|
|
110
|
+
}
|
|
111
|
+
runtime.pi.sendMessage(
|
|
112
|
+
{
|
|
113
|
+
content: `Manual Advisor consultation${question ? ` (${question})` : ""} — for your awareness; no action or follow-up consultation is needed unless the user asks:\n\n${markdown}`,
|
|
114
|
+
customType: "advisor-manual-result",
|
|
115
|
+
details,
|
|
116
|
+
display: true,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
// Steer lets the current turn finish its active work; the Executor sees
|
|
120
|
+
// the result before its next model call rather than being interrupted.
|
|
121
|
+
deliverAs: "steer",
|
|
122
|
+
triggerTurn: true,
|
|
152
123
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
124
|
+
);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (controller.signal.aborted) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
progress.phase = "error";
|
|
130
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
131
|
+
runtime.advisorSessionState.resetTurnsSinceConsultation();
|
|
132
|
+
runtime.advisorSessionState.recordInvocation({
|
|
133
|
+
executionEffect: "continued",
|
|
134
|
+
failure: "provider-error",
|
|
135
|
+
kind: "markdown",
|
|
136
|
+
model: advisorRef,
|
|
137
|
+
trigger: "manual",
|
|
157
138
|
});
|
|
139
|
+
runtime.updateAdvisorUsageStatus(ctx);
|
|
140
|
+
runtime.pi.sendMessage(
|
|
141
|
+
{
|
|
142
|
+
content: `Manual Advisor consultation failed: ${message}`,
|
|
143
|
+
customType: "advisor-manual-result",
|
|
144
|
+
details: {
|
|
145
|
+
advisor: advisorRef,
|
|
146
|
+
text: `**Advisor consultation failed:** ${message}`,
|
|
147
|
+
},
|
|
148
|
+
display: true,
|
|
149
|
+
},
|
|
150
|
+
{ deliverAs: "steer", triggerTurn: true }
|
|
151
|
+
);
|
|
152
|
+
notify(ctx, `Advisor consultation failed: ${message}`, "error");
|
|
153
|
+
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
154
|
+
} finally {
|
|
155
|
+
if (controller.signal.aborted) {
|
|
156
|
+
progress.phase = "cancelled";
|
|
157
|
+
}
|
|
158
|
+
const timer = runtime.manualProgressTimers.get(controller);
|
|
159
|
+
if (timer) {
|
|
160
|
+
clearInterval(timer);
|
|
161
|
+
runtime.manualProgressTimers.delete(controller);
|
|
162
|
+
}
|
|
163
|
+
runtime.requestManualRender(ctx);
|
|
164
|
+
runtime.scoutStatus.release(ctx, scoutStatusToken);
|
|
165
|
+
runtime.manualConsultations.delete(controller);
|
|
166
|
+
herdrAdvisorActivity.finish();
|
|
167
|
+
}
|
|
158
168
|
};
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { getMarkdownTheme
|
|
2
|
-
import
|
|
1
|
+
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Markdown, Text } from "@earendil-works/pi-tui";
|
|
4
|
+
import type { Component } from "@earendil-works/pi-tui";
|
|
5
|
+
|
|
3
6
|
import {
|
|
4
7
|
adviceForDisplay,
|
|
8
|
+
noop,
|
|
5
9
|
renderAdvisorCallBox,
|
|
6
10
|
renderThinkingMarkdown,
|
|
7
11
|
SPINNER_FRAMES,
|
|
@@ -87,7 +91,5 @@ export class ManualAdvisorProgressComponent implements Component {
|
|
|
87
91
|
return box.render(width);
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
invalidate
|
|
91
|
-
// The live progress state is read during each render.
|
|
92
|
-
}
|
|
94
|
+
readonly invalidate = noop;
|
|
93
95
|
}
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
setExecutorRef,
|
|
11
11
|
} from "../config/state.ts";
|
|
12
12
|
import { saveConfig } from "../config/storage.ts";
|
|
13
|
-
import { activateAdvisor } from "./activation.ts";
|
|
14
13
|
import { loadCommandConfig } from "./activation-preparation.ts";
|
|
14
|
+
import { activateAdvisor } from "./activation.ts";
|
|
15
15
|
import { selectAdvisorModels } from "./model-picker.ts";
|
|
16
16
|
import type { CommandRuntime } from "./types.ts";
|
|
17
17
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import { splitRef } from "../config/state.ts";
|
|
4
|
+
import type { getPersistedModelRefs } from "../config/state.ts";
|
|
3
5
|
import type { ContextPreset } from "../ui/types.ts";
|
|
4
6
|
|
|
5
7
|
export const DEFAULT_EFFORT_LEVEL = "Default (Model Default)";
|
|
@@ -33,7 +35,7 @@ export const selectedEffort = (choice: string): string | undefined => {
|
|
|
33
35
|
export const ADVISOR_ACTIVATION_EXPLANATION =
|
|
34
36
|
"The Advisor is a second-opinion model that reviews the Executor's context and returns risks, alternatives, and verification steps without changing files or running tools.";
|
|
35
37
|
|
|
36
|
-
const ARGUMENT_WHITESPACE = /\s
|
|
38
|
+
const ARGUMENT_WHITESPACE = /\s+/u;
|
|
37
39
|
const hasModelOverride = (args: string, key: "advisor" | "executor") =>
|
|
38
40
|
args
|
|
39
41
|
.trim()
|
|
@@ -91,13 +93,14 @@ export const findConfiguredModel = (
|
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
93
95
|
const [provider, modelId] = splitRef(ref);
|
|
96
|
+
// oxlint-disable-next-line unicorn/no-array-method-this-argument -- Pi ModelRegistry.find(provider, id), not Array#find.
|
|
94
97
|
return ctx.modelRegistry.find(provider, modelId);
|
|
95
98
|
};
|
|
96
99
|
|
|
97
100
|
export const getAvailableModelRefs = (
|
|
98
101
|
ctx: ExtensionContext
|
|
99
102
|
): string[] | undefined => {
|
|
100
|
-
if (
|
|
103
|
+
if (!ctx.modelRegistry.getAvailable) {
|
|
101
104
|
return undefined;
|
|
102
105
|
}
|
|
103
106
|
return ctx.modelRegistry
|
|
@@ -105,6 +108,14 @@ export const getAvailableModelRefs = (
|
|
|
105
108
|
.map((model) => `${model.provider}/${model.id}`);
|
|
106
109
|
};
|
|
107
110
|
|
|
111
|
+
export const getConfiguredModelRefs = (ctx: ExtensionContext): string[] => {
|
|
112
|
+
const registry = ctx.modelRegistry;
|
|
113
|
+
const models = registry?.getAvailable ? registry.getAvailable() : [];
|
|
114
|
+
return [
|
|
115
|
+
...new Set(models.map((model) => `${model.provider}/${model.id}`)),
|
|
116
|
+
].toSorted((left, right) => left.localeCompare(right));
|
|
117
|
+
};
|
|
118
|
+
|
|
108
119
|
const isSelectableModel = (
|
|
109
120
|
ctx: ExtensionContext,
|
|
110
121
|
ref: string | undefined,
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ExtensionAPI,
|
|
3
3
|
ExtensionContext,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
5
6
|
import { activateAdvisor } from "./activation.ts";
|
|
6
7
|
import { registerCommandLifecycle } from "./lifecycle.ts";
|
|
7
8
|
import { registerManualCommand } from "./manual-command.ts";
|