pi-advisor-flow 0.9.2-dev.5 → 0.10.1-dev.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 +10 -0
- package/README.md +40 -5
- package/dist/index.js +464 -86
- package/package.json +2 -2
- package/src/commands/activation-preparation.ts +4 -0
- package/src/commands/manual-consultation.ts +41 -29
- package/src/commands/model-commands.ts +5 -1
- package/src/commands/model-picker.ts +44 -2
- package/src/commands/runtime.ts +9 -1
- package/src/commands/settings-persistence.ts +2 -0
- package/src/commands/types.ts +2 -0
- package/src/config/defaults.ts +6 -0
- package/src/config/schema.ts +7 -0
- package/src/config/state.ts +5 -0
- package/src/config/types.ts +1 -0
- package/src/config.ts +2 -0
- package/src/follow-up.ts +85 -0
- package/src/session-state.ts +33 -0
- package/src/tools/advisor-output.ts +28 -0
- package/src/tools/consultation.ts +311 -41
- package/src/tools/model-access.ts +7 -3
- package/src/tools/register-ask-advisor.ts +133 -31
- package/src/tools/register-lifecycle.ts +11 -0
- package/src/tools/render-advisor-result.ts +3 -0
- package/src/tools/render-common.ts +6 -2
- package/src/tools/types.ts +2 -0
- package/src/ui/settings-items.ts +36 -0
- package/src/ui/settings-mutations.ts +8 -0
- package/src/ui/settings-selector.ts +9 -0
- package/src/ui/types.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-advisor-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1-dev.1",
|
|
4
4
|
"description": "Advanced Executor/Advisor flow for Pi, fully configurable and extendable.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"advisor",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@earendil-works/pi-coding-agent": "1.0.0",
|
|
56
56
|
"@earendil-works/pi-tui": "1.0.0",
|
|
57
57
|
"@oxlint/plugins": "1.86.0",
|
|
58
|
-
"@types/node": "^26.6.
|
|
58
|
+
"@types/node": "^26.6.4",
|
|
59
59
|
"@typesafe-ai/sdk": "^0.6.0",
|
|
60
60
|
"bun-types": "1.4.2",
|
|
61
61
|
"husky": "^9.1.7",
|
|
@@ -2,11 +2,13 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
advisorEffortRef,
|
|
5
|
+
advisorFallbackModelRef,
|
|
5
6
|
advisorRef,
|
|
6
7
|
executorEffortRef,
|
|
7
8
|
executorRef,
|
|
8
9
|
getPersistedModelRefs,
|
|
9
10
|
setAdvisorEffortRef,
|
|
11
|
+
setAdvisorFallbackModelRef,
|
|
10
12
|
setAdvisorRef,
|
|
11
13
|
setExecutorEffortRef,
|
|
12
14
|
setExecutorRef,
|
|
@@ -99,6 +101,7 @@ export const prepareActivationModels = async (
|
|
|
99
101
|
const selection = await selectAdvisorModels(ctx, {
|
|
100
102
|
advisor: advisorOverride || persisted.advisor ? advisorRef : "",
|
|
101
103
|
advisorEffort: advisorEffortRef,
|
|
104
|
+
advisorFallbackModel: advisorFallbackModelRef,
|
|
102
105
|
executor:
|
|
103
106
|
executorOverride || plan.pendingExecutor || persisted.executor
|
|
104
107
|
? executorRef
|
|
@@ -111,6 +114,7 @@ export const prepareActivationModels = async (
|
|
|
111
114
|
return;
|
|
112
115
|
}
|
|
113
116
|
setAdvisorRef(selection.advisor);
|
|
117
|
+
setAdvisorFallbackModelRef(selection.advisorFallbackModel);
|
|
114
118
|
setAdvisorEffortRef(selection.advisorEffort);
|
|
115
119
|
setExecutorRef(selection.executor);
|
|
116
120
|
setExecutorEffortRef(selection.executorEffort);
|
|
@@ -14,8 +14,10 @@ import { notify } from "./runtime.ts";
|
|
|
14
14
|
import type { CommandRuntime, ManualAdvisorProgressState } from "./types.ts";
|
|
15
15
|
|
|
16
16
|
interface ManualResultDetails {
|
|
17
|
+
adviceId?: string;
|
|
17
18
|
advisor: string;
|
|
18
19
|
agentRulesBytes?: number;
|
|
20
|
+
followUp?: boolean;
|
|
19
21
|
preferenceBytes?: number;
|
|
20
22
|
question?: string;
|
|
21
23
|
text: string;
|
|
@@ -50,35 +52,42 @@ export const startManualConsultation = async (
|
|
|
50
52
|
const finishHerdrActivity = runtime.herdrActivity.start();
|
|
51
53
|
let scoutDetails: ScoutToolDetails | undefined;
|
|
52
54
|
try {
|
|
53
|
-
const {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
const {
|
|
56
|
+
adviceId,
|
|
57
|
+
agentRulesBytes,
|
|
58
|
+
followUp,
|
|
59
|
+
markdown,
|
|
60
|
+
model,
|
|
61
|
+
preferenceBytes,
|
|
62
|
+
usage,
|
|
63
|
+
} = await runtime.requestAdvisor(
|
|
64
|
+
ctx,
|
|
65
|
+
question,
|
|
66
|
+
controller.signal,
|
|
67
|
+
(thinking, text) => {
|
|
68
|
+
if (controller.signal.aborted) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
progress.phase = "active";
|
|
72
|
+
progress.thinking = thinking;
|
|
73
|
+
progress.text = text;
|
|
74
|
+
runtime.requestManualRender(ctx);
|
|
75
|
+
},
|
|
76
|
+
(event) => {
|
|
77
|
+
if (!controller.signal.aborted) {
|
|
78
|
+
runtime.scoutStatus.update(ctx, scoutStatusToken, event);
|
|
79
|
+
scoutDetails = appendScoutLifecycleEntry(
|
|
80
|
+
runtime.pi,
|
|
81
|
+
event,
|
|
82
|
+
scoutDetails
|
|
83
|
+
);
|
|
84
|
+
progress.scout = scoutDetails;
|
|
62
85
|
progress.phase = "active";
|
|
63
|
-
progress.thinking = thinking;
|
|
64
|
-
progress.text = text;
|
|
65
86
|
runtime.requestManualRender(ctx);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
scoutDetails = appendScoutLifecycleEntry(
|
|
71
|
-
runtime.pi,
|
|
72
|
-
event,
|
|
73
|
-
scoutDetails
|
|
74
|
-
);
|
|
75
|
-
progress.scout = scoutDetails;
|
|
76
|
-
progress.phase = "active";
|
|
77
|
-
runtime.requestManualRender(ctx);
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
gitContext
|
|
81
|
-
);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
gitContext
|
|
90
|
+
);
|
|
82
91
|
if (controller.signal.aborted) {
|
|
83
92
|
return;
|
|
84
93
|
}
|
|
@@ -87,8 +96,9 @@ export const startManualConsultation = async (
|
|
|
87
96
|
runtime.advisorSessionState.recordInvocation({
|
|
88
97
|
cost: advisorUsageCost(usage),
|
|
89
98
|
executionEffect: "continued",
|
|
99
|
+
followUp,
|
|
90
100
|
kind: "markdown",
|
|
91
|
-
model: advisorRef,
|
|
101
|
+
model: model ?? advisorRef,
|
|
92
102
|
trigger: "manual",
|
|
93
103
|
usage,
|
|
94
104
|
});
|
|
@@ -105,8 +115,10 @@ export const startManualConsultation = async (
|
|
|
105
115
|
}
|
|
106
116
|
runtime.updateAdvisorUsageStatus(ctx);
|
|
107
117
|
const details: ManualResultDetails = {
|
|
108
|
-
|
|
118
|
+
adviceId,
|
|
119
|
+
advisor: model ?? advisorRef,
|
|
109
120
|
agentRulesBytes,
|
|
121
|
+
followUp,
|
|
110
122
|
preferenceBytes,
|
|
111
123
|
question,
|
|
112
124
|
text: markdown,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
advisorEffortRef,
|
|
3
|
+
advisorFallbackModelRef,
|
|
3
4
|
advisorRef,
|
|
4
5
|
executorEffortRef,
|
|
5
6
|
executorRef,
|
|
6
7
|
getPersistedModelRefs,
|
|
7
8
|
setAdvisorEffortRef,
|
|
9
|
+
setAdvisorFallbackModelRef,
|
|
8
10
|
setAdvisorRef,
|
|
9
11
|
setExecutorEffortRef,
|
|
10
12
|
setExecutorRef,
|
|
@@ -24,7 +26,7 @@ export const registerModelCommands = (runtime: CommandRuntime) => {
|
|
|
24
26
|
|
|
25
27
|
runtime.pi.registerCommand("advisor-models", {
|
|
26
28
|
description:
|
|
27
|
-
"Select and persist the Executor and
|
|
29
|
+
"Select and persist the Executor, Advisor, and optional fallback models with reasoning levels",
|
|
28
30
|
handler: async (_args, ctx) => {
|
|
29
31
|
if (!(loadCommandConfig(ctx) && ctx.hasUI)) {
|
|
30
32
|
return;
|
|
@@ -36,6 +38,7 @@ export const registerModelCommands = (runtime: CommandRuntime) => {
|
|
|
36
38
|
const selection = await selectAdvisorModels(ctx, {
|
|
37
39
|
advisor: persisted.advisor ? advisorRef : "",
|
|
38
40
|
advisorEffort: advisorEffortRef,
|
|
41
|
+
advisorFallbackModel: advisorFallbackModelRef,
|
|
39
42
|
executor:
|
|
40
43
|
runtime.pendingExecutorModelRef ??
|
|
41
44
|
(persisted.executor ? executorRef : ""),
|
|
@@ -49,6 +52,7 @@ export const registerModelCommands = (runtime: CommandRuntime) => {
|
|
|
49
52
|
|
|
50
53
|
setExecutorRef(selection.executor);
|
|
51
54
|
setAdvisorRef(selection.advisor);
|
|
55
|
+
setAdvisorFallbackModelRef(selection.advisorFallbackModel);
|
|
52
56
|
setExecutorEffortRef(selection.executorEffort);
|
|
53
57
|
setAdvisorEffortRef(selection.advisorEffort);
|
|
54
58
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import { SearchableModelSelector } from "../ui/model-selector.ts";
|
|
4
|
+
import { FALLBACK_ADVISOR_MODEL_DISABLED } from "../ui/types.ts";
|
|
4
5
|
import {
|
|
5
6
|
effortChoices,
|
|
6
7
|
getAvailableModelRefs,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
export interface AdvisorModelSelection {
|
|
11
12
|
advisor: string;
|
|
12
13
|
advisorEffort: string | undefined;
|
|
14
|
+
advisorFallbackModel: string | undefined;
|
|
13
15
|
executor: string;
|
|
14
16
|
executorEffort: string | undefined;
|
|
15
17
|
}
|
|
@@ -17,6 +19,7 @@ export interface AdvisorModelSelection {
|
|
|
17
19
|
export interface AdvisorModelPickerOptions {
|
|
18
20
|
advisor: string | undefined;
|
|
19
21
|
advisorEffort: string | undefined;
|
|
22
|
+
advisorFallbackModel: string | undefined;
|
|
20
23
|
executor: string | undefined;
|
|
21
24
|
executorEffort: string | undefined;
|
|
22
25
|
selectAdvisor: boolean;
|
|
@@ -46,7 +49,13 @@ export const selectAdvisorModels = async (
|
|
|
46
49
|
)
|
|
47
50
|
),
|
|
48
51
|
];
|
|
49
|
-
let {
|
|
52
|
+
let {
|
|
53
|
+
advisor,
|
|
54
|
+
advisorEffort,
|
|
55
|
+
advisorFallbackModel,
|
|
56
|
+
executor,
|
|
57
|
+
executorEffort,
|
|
58
|
+
} = options;
|
|
50
59
|
|
|
51
60
|
if (options.selectExecutor) {
|
|
52
61
|
const selectedExecutor = await ctx.ui.custom<string | undefined>(
|
|
@@ -104,8 +113,41 @@ export const selectAdvisorModels = async (
|
|
|
104
113
|
advisorEffort = selectedEffort(selectedAdvisorEffort);
|
|
105
114
|
}
|
|
106
115
|
|
|
116
|
+
const selectedFallback = await ctx.ui.custom<string | undefined>(
|
|
117
|
+
(tui, theme, keybindings, done) =>
|
|
118
|
+
new SearchableModelSelector({
|
|
119
|
+
allOptions: [
|
|
120
|
+
FALLBACK_ADVISOR_MODEL_DISABLED,
|
|
121
|
+
...new Set([
|
|
122
|
+
...allOptions,
|
|
123
|
+
...(refs || !advisorFallbackModel ? [] : [advisorFallbackModel]),
|
|
124
|
+
]),
|
|
125
|
+
],
|
|
126
|
+
currentOption: advisorFallbackModel ?? FALLBACK_ADVISOR_MODEL_DISABLED,
|
|
127
|
+
keybindings,
|
|
128
|
+
onCancel: () => done(undefined),
|
|
129
|
+
onSelect: done,
|
|
130
|
+
theme,
|
|
131
|
+
title: "Select Fallback Advisor Model (optional)",
|
|
132
|
+
tui,
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
if (selectedFallback === undefined) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
advisorFallbackModel =
|
|
139
|
+
selectedFallback === FALLBACK_ADVISOR_MODEL_DISABLED
|
|
140
|
+
? undefined
|
|
141
|
+
: selectedFallback;
|
|
142
|
+
|
|
107
143
|
if (!(advisor && executor)) {
|
|
108
144
|
return undefined;
|
|
109
145
|
}
|
|
110
|
-
return {
|
|
146
|
+
return {
|
|
147
|
+
advisor,
|
|
148
|
+
advisorEffort,
|
|
149
|
+
advisorFallbackModel,
|
|
150
|
+
executor,
|
|
151
|
+
executorEffort,
|
|
152
|
+
};
|
|
111
153
|
};
|
package/src/commands/runtime.ts
CHANGED
|
@@ -77,7 +77,15 @@ class CommandRuntime implements CommandRuntimeContract {
|
|
|
77
77
|
undefined,
|
|
78
78
|
undefined,
|
|
79
79
|
undefined,
|
|
80
|
-
onScout
|
|
80
|
+
onScout,
|
|
81
|
+
undefined,
|
|
82
|
+
undefined,
|
|
83
|
+
(adviceId, payload, replacesAdviceId) =>
|
|
84
|
+
this.advisorSessionState.captureFollowUp(
|
|
85
|
+
adviceId,
|
|
86
|
+
payload,
|
|
87
|
+
replacesAdviceId
|
|
88
|
+
)
|
|
81
89
|
));
|
|
82
90
|
}
|
|
83
91
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
setAdvisorCustomInvocationRef,
|
|
12
12
|
setAdvisorDisableSameModelRef,
|
|
13
13
|
setAdvisorEffortRef,
|
|
14
|
+
setAdvisorFallbackModelRef,
|
|
14
15
|
setAdvisorFailureGateRef,
|
|
15
16
|
setAdvisorFailureModeRef,
|
|
16
17
|
setAdvisorGitContextMaxCharsRef,
|
|
@@ -69,6 +70,7 @@ const applySessionSettings = (settings: AdvisorSettings) => {
|
|
|
69
70
|
);
|
|
70
71
|
setContextMaxCharsRef(settings.contextMaxChars);
|
|
71
72
|
setAdvisorPlanGateRef(settings.planGate);
|
|
73
|
+
setAdvisorFallbackModelRef(settings.fallbackModel);
|
|
72
74
|
setAdvisorFailureGateRef(settings.failureGate);
|
|
73
75
|
setAdvisorCompletionGateRef(settings.completionGate);
|
|
74
76
|
setAdvisorDisableSameModelRef(settings.disableSameModel ?? true);
|
package/src/commands/types.ts
CHANGED
package/src/config/defaults.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
setAdvisorPlanGateRef,
|
|
31
31
|
setAdvisorRedactSecretsRef,
|
|
32
32
|
setAdvisorRef,
|
|
33
|
+
setAdvisorFallbackModelRef,
|
|
33
34
|
setAdvisorScoutEnabledRef,
|
|
34
35
|
setAdvisorScoutTimeoutMsRef,
|
|
35
36
|
setAdvisorSessionSummaryRef,
|
|
@@ -69,6 +70,7 @@ import {
|
|
|
69
70
|
export const resetDefaults = () => {
|
|
70
71
|
setExecutorRef("");
|
|
71
72
|
setAdvisorRef("");
|
|
73
|
+
setAdvisorFallbackModelRef(undefined);
|
|
72
74
|
setAdvisorAgentsMdContextRef(true);
|
|
73
75
|
setPersistedModelRefs(undefined, undefined);
|
|
74
76
|
setExecutorEffortRef(undefined);
|
|
@@ -139,6 +141,10 @@ const applyNonEmptyStringConfig = (
|
|
|
139
141
|
export const applyConfig = (config: AdvisorConfig) => {
|
|
140
142
|
applyNonEmptyStringConfig(config.executor, setExecutorRef);
|
|
141
143
|
applyNonEmptyStringConfig(config.advisor, setAdvisorRef);
|
|
144
|
+
applyNonEmptyStringConfig(
|
|
145
|
+
config.advisorFallbackModel,
|
|
146
|
+
setAdvisorFallbackModelRef
|
|
147
|
+
);
|
|
142
148
|
applyNonEmptyStringConfig(config.executorEffort, setExecutorEffortRef);
|
|
143
149
|
applyNonEmptyStringConfig(config.advisorEffort, setAdvisorEffortRef);
|
|
144
150
|
applyOptionalConfig(
|
package/src/config/schema.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
advisorCustomInvocationRef,
|
|
11
11
|
advisorDisableSameModelRef,
|
|
12
12
|
advisorEffortRef,
|
|
13
|
+
advisorFallbackModelRef,
|
|
13
14
|
advisorFailureGateRef,
|
|
14
15
|
advisorFailureModeRef,
|
|
15
16
|
advisorGitContextMaxCharsRef,
|
|
@@ -226,6 +227,12 @@ export const CONFIG_SCHEMA = {
|
|
|
226
227
|
persisted: true,
|
|
227
228
|
type: "boolean",
|
|
228
229
|
},
|
|
230
|
+
advisorFallbackModel: {
|
|
231
|
+
accepted: "a provider/model string",
|
|
232
|
+
current: () => configuredModelRef(advisorFallbackModelRef),
|
|
233
|
+
persisted: true,
|
|
234
|
+
type: "string",
|
|
235
|
+
},
|
|
229
236
|
advisorGitContext: {
|
|
230
237
|
accepted: GIT_CONTEXT_LEVELS.join(", "),
|
|
231
238
|
current: () => advisorGitContextRef,
|
package/src/config/state.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
// An empty ref means no model has been selected yet.
|
|
26
26
|
export let executorRef = "";
|
|
27
27
|
export let advisorRef = "";
|
|
28
|
+
export let advisorFallbackModelRef: string | undefined;
|
|
28
29
|
export let advisorAgentsMdContextRef = true;
|
|
29
30
|
let persistedExecutorRef: string | undefined;
|
|
30
31
|
let persistedAdvisorRef: string | undefined;
|
|
@@ -81,6 +82,9 @@ export const setExecutorRef = (ref: string) => {
|
|
|
81
82
|
export const setAdvisorRef = (ref: string) => {
|
|
82
83
|
advisorRef = ref;
|
|
83
84
|
};
|
|
85
|
+
export const setAdvisorFallbackModelRef = (ref: string | undefined) => {
|
|
86
|
+
advisorFallbackModelRef = ref?.trim() || undefined;
|
|
87
|
+
};
|
|
84
88
|
export const setAdvisorAgentsMdContextRef = (enabled: boolean) => {
|
|
85
89
|
advisorAgentsMdContextRef = enabled;
|
|
86
90
|
};
|
|
@@ -250,6 +254,7 @@ export const getAdvisorSettings = () => ({
|
|
|
250
254
|
effort: advisorEffortRef,
|
|
251
255
|
failureGate: advisorFailureGateRef,
|
|
252
256
|
failureMode: advisorFailureModeRef,
|
|
257
|
+
fallbackModel: advisorFallbackModelRef,
|
|
253
258
|
gitContext: advisorGitContextRef,
|
|
254
259
|
gitContextMaxChars: advisorGitContextMaxCharsRef,
|
|
255
260
|
herdrIntegration: advisorHerdrIntegrationRef,
|
package/src/config/types.ts
CHANGED
package/src/config.ts
CHANGED
|
@@ -8,6 +8,7 @@ export {
|
|
|
8
8
|
advisorCompletionGateRef,
|
|
9
9
|
advisorCustomInvocationRef,
|
|
10
10
|
advisorEffortRef,
|
|
11
|
+
advisorFallbackModelRef,
|
|
11
12
|
advisorFailureGateRef,
|
|
12
13
|
advisorFailureModeRef,
|
|
13
14
|
advisorGitContextMaxCharsRef,
|
|
@@ -42,6 +43,7 @@ export {
|
|
|
42
43
|
setAdvisorCompletionGateRef,
|
|
43
44
|
setAdvisorCustomInvocationRef,
|
|
44
45
|
setAdvisorEffortRef,
|
|
46
|
+
setAdvisorFallbackModelRef,
|
|
45
47
|
setAdvisorFailureGateRef,
|
|
46
48
|
setAdvisorFailureModeRef,
|
|
47
49
|
setAdvisorGitContextMaxCharsRef,
|
package/src/follow-up.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Message } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
|
|
3
|
+
export const FOLLOW_UP_TTL_MS = 5 * 60 * 1000;
|
|
4
|
+
export const FOLLOW_UP_MAX_DEPTH = 3;
|
|
5
|
+
export const FOLLOW_UP_MAX_TOOL_CALLS = 3;
|
|
6
|
+
|
|
7
|
+
export interface AdvisorFollowUpPayload {
|
|
8
|
+
depth: number;
|
|
9
|
+
messages: Message[];
|
|
10
|
+
model: string;
|
|
11
|
+
privacyKey: string;
|
|
12
|
+
redacted: boolean;
|
|
13
|
+
systemPrompt: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AdvisorFollowUpLookup {
|
|
17
|
+
payload?: AdvisorFollowUpPayload;
|
|
18
|
+
reason?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface StoredFollowUpPayload extends AdvisorFollowUpPayload {
|
|
22
|
+
expiresAt: number;
|
|
23
|
+
toolCallsSince: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const copy = <Value>(value: Value): Value => structuredClone(value);
|
|
27
|
+
|
|
28
|
+
export class AdvisorFollowUpCache {
|
|
29
|
+
readonly #entries = new Map<string, StoredFollowUpPayload>();
|
|
30
|
+
readonly #now: () => number;
|
|
31
|
+
|
|
32
|
+
constructor(now: () => number = Date.now) {
|
|
33
|
+
this.#now = now;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
clear() {
|
|
37
|
+
this.#entries.clear();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
advanceToolCall() {
|
|
41
|
+
const now = this.#now();
|
|
42
|
+
for (const [adviceId, entry] of this.#entries) {
|
|
43
|
+
if (
|
|
44
|
+
now >= entry.expiresAt ||
|
|
45
|
+
entry.toolCallsSince + 1 >= FOLLOW_UP_MAX_TOOL_CALLS
|
|
46
|
+
) {
|
|
47
|
+
this.#entries.delete(adviceId);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
entry.toolCallsSince += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
capture(
|
|
55
|
+
adviceId: string,
|
|
56
|
+
payload: AdvisorFollowUpPayload,
|
|
57
|
+
replacesAdviceId?: string
|
|
58
|
+
) {
|
|
59
|
+
if (replacesAdviceId) {
|
|
60
|
+
this.#entries.delete(replacesAdviceId);
|
|
61
|
+
}
|
|
62
|
+
this.#entries.set(adviceId, {
|
|
63
|
+
...copy(payload),
|
|
64
|
+
expiresAt: this.#now() + FOLLOW_UP_TTL_MS,
|
|
65
|
+
toolCallsSince: 0,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get(adviceId: string): AdvisorFollowUpLookup {
|
|
70
|
+
const entry = this.#entries.get(adviceId);
|
|
71
|
+
if (!entry) {
|
|
72
|
+
return { reason: "the adviceId is unknown or no longer cached" };
|
|
73
|
+
}
|
|
74
|
+
if (this.#now() >= entry.expiresAt) {
|
|
75
|
+
this.#entries.delete(adviceId);
|
|
76
|
+
return { reason: "the cached payload has expired" };
|
|
77
|
+
}
|
|
78
|
+
if (entry.depth >= FOLLOW_UP_MAX_DEPTH) {
|
|
79
|
+
return {
|
|
80
|
+
reason: `the follow-up chain is limited to ${FOLLOW_UP_MAX_DEPTH} follow-ups`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { payload: copy(entry) };
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/session-state.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { isRecordOf, isString } from "./content-utils.ts";
|
|
2
2
|
import type { JsonValue } from "./content-utils.ts";
|
|
3
|
+
import { AdvisorFollowUpCache } from "./follow-up.ts";
|
|
4
|
+
import type {
|
|
5
|
+
AdvisorFollowUpLookup,
|
|
6
|
+
AdvisorFollowUpPayload,
|
|
7
|
+
} from "./follow-up.ts";
|
|
3
8
|
import { AdvisorJevLedgerState } from "./jev/ledger.ts";
|
|
4
9
|
import type { AdvisorJevUsage } from "./jev/ledger.ts";
|
|
5
10
|
import {
|
|
@@ -22,6 +27,7 @@ type ExecutionEffect = "continued" | "tool-blocked" | "session-blocked";
|
|
|
22
27
|
export interface AdvisorInvocationRecord {
|
|
23
28
|
cost?: number;
|
|
24
29
|
decision?: GateDecision;
|
|
30
|
+
followUp?: boolean;
|
|
25
31
|
executionEffect: ExecutionEffect;
|
|
26
32
|
failure?: string;
|
|
27
33
|
kind: "markdown" | "gate";
|
|
@@ -179,9 +185,14 @@ export class AdvisorSessionState {
|
|
|
179
185
|
#consumedCalls = 0;
|
|
180
186
|
#callReservations = new Set<string>();
|
|
181
187
|
readonly #jev = new AdvisorJevLedgerState();
|
|
188
|
+
readonly #followUps: AdvisorFollowUpCache;
|
|
182
189
|
#sessionTurnOrdinal = 0;
|
|
183
190
|
#turnsSinceConsultation = 0;
|
|
184
191
|
|
|
192
|
+
constructor(now?: () => number) {
|
|
193
|
+
this.#followUps = new AdvisorFollowUpCache(now);
|
|
194
|
+
}
|
|
195
|
+
|
|
185
196
|
resetTask() {
|
|
186
197
|
this.#repetition = freshRepetition();
|
|
187
198
|
this.#blockedReason = undefined;
|
|
@@ -190,6 +201,7 @@ export class AdvisorSessionState {
|
|
|
190
201
|
this.#consumedCalls = 0;
|
|
191
202
|
this.#callReservations.clear();
|
|
192
203
|
this.#jev.reset();
|
|
204
|
+
this.#followUps.clear();
|
|
193
205
|
this.#sessionTurnOrdinal = 0;
|
|
194
206
|
this.#turnsSinceConsultation = 0;
|
|
195
207
|
}
|
|
@@ -268,6 +280,26 @@ export class AdvisorSessionState {
|
|
|
268
280
|
return this.#consumedCalls;
|
|
269
281
|
}
|
|
270
282
|
|
|
283
|
+
advanceFollowUpToolCall() {
|
|
284
|
+
this.#followUps.advanceToolCall();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
clearFollowUps() {
|
|
288
|
+
this.#followUps.clear();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
followUpFor(adviceId: string): AdvisorFollowUpLookup {
|
|
292
|
+
return this.#followUps.get(adviceId);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
captureFollowUp(
|
|
296
|
+
adviceId: string,
|
|
297
|
+
payload: AdvisorFollowUpPayload,
|
|
298
|
+
replacesAdviceId?: string
|
|
299
|
+
) {
|
|
300
|
+
this.#followUps.capture(adviceId, payload, replacesAdviceId);
|
|
301
|
+
}
|
|
302
|
+
|
|
271
303
|
/** Counts one completed executor turn on both turn counters. The ordinal
|
|
272
304
|
* feeds the filter override window and summary positions and is never reset
|
|
273
305
|
* by anything; turnsSinceConsultation resets on every consultation. */
|
|
@@ -495,6 +527,7 @@ export class AdvisorSessionState {
|
|
|
495
527
|
`Budget: ${budget}`,
|
|
496
528
|
`Usage: ${formatAdvisorUsageTotals(totals)}`,
|
|
497
529
|
`Markdown advice: ${markdown.length} responses (${this.#ledger.draftConsultations} with drafts)`,
|
|
530
|
+
`Follow-ups: ${markdown.filter((item) => item.followUp).length}`,
|
|
498
531
|
`Outcome reports: ${this.#ledger.outcomes}`,
|
|
499
532
|
`Gate decisions: ${this.#decisionsLine()}`,
|
|
500
533
|
`Loop matching: normalized tool signatures; ${this.#repetition.interventions} gate intervention${this.#repetition.interventions === 1 ? "" : "s"}`,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
|
|
3
|
+
export const advisorOutputSchema = Type.Object({
|
|
4
|
+
adviceId: Type.Optional(Type.String()),
|
|
5
|
+
advisor: Type.Optional(Type.String()),
|
|
6
|
+
followUp: Type.Optional(Type.Boolean()),
|
|
7
|
+
jev: Type.Optional(
|
|
8
|
+
Type.Object({
|
|
9
|
+
kind: Type.Union([Type.Literal("screened"), Type.Literal("repeat")]),
|
|
10
|
+
reason: Type.String(),
|
|
11
|
+
skipped: Type.Boolean(),
|
|
12
|
+
})
|
|
13
|
+
),
|
|
14
|
+
skipReason: Type.Optional(Type.String()),
|
|
15
|
+
text: Type.String({
|
|
16
|
+
description: "Advisor Markdown or consultation skip notice.",
|
|
17
|
+
}),
|
|
18
|
+
usage: Type.Optional(
|
|
19
|
+
Type.Object({
|
|
20
|
+
cacheRead: Type.Optional(Type.Number()),
|
|
21
|
+
cacheWrite: Type.Optional(Type.Number()),
|
|
22
|
+
cost: Type.Optional(Type.Number()),
|
|
23
|
+
input: Type.Optional(Type.Number()),
|
|
24
|
+
output: Type.Optional(Type.Number()),
|
|
25
|
+
totalTokens: Type.Optional(Type.Number()),
|
|
26
|
+
})
|
|
27
|
+
),
|
|
28
|
+
});
|