pi-advisor-flow 0.5.6 → 0.7.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 +29 -0
- package/README.md +1 -0
- package/dist/index.js +3160 -1255
- package/extensions/index.ts +9 -9
- package/package.json +12 -8
- package/src/{untracked.ts → attachments.ts} +2 -5
- package/src/commands/activation-preparation.ts +6 -6
- package/src/commands/activation.ts +7 -7
- package/src/commands/lifecycle.ts +5 -5
- package/src/commands/manual-command.ts +13 -9
- package/src/commands/manual-consultation.ts +24 -10
- package/src/commands/manual-progress.ts +3 -3
- package/src/commands/model-commands.ts +6 -6
- package/src/commands/model-options.ts +4 -4
- package/src/commands/model-picker.ts +2 -2
- package/src/commands/registration.ts +8 -8
- package/src/commands/renderers.ts +5 -5
- package/src/commands/runtime.ts +8 -8
- package/src/commands/settings-commands.ts +8 -8
- package/src/commands/settings-persistence.ts +52 -4
- package/src/commands/types.ts +6 -13
- package/src/commands.ts +2 -2
- package/src/config/args.ts +3 -3
- package/src/config/defaults.ts +78 -3
- package/src/config/schema.ts +434 -0
- package/src/config/state.ts +71 -2
- package/src/config/storage.ts +27 -119
- package/src/config/types.ts +30 -1
- package/src/config/validation.ts +61 -181
- package/src/config.ts +7 -7
- package/src/content-utils.ts +30 -0
- package/src/conversation.ts +13 -168
- package/src/git.ts +4 -18
- package/src/herdr.ts +6 -8
- package/src/jev/client.ts +348 -0
- package/src/jev/key-store.ts +252 -0
- package/src/jev/ledger.ts +234 -0
- package/src/jev/questions.ts +119 -0
- package/src/jev/state.ts +47 -0
- package/src/jev/transport.ts +58 -0
- package/src/model-stream.ts +5 -8
- package/src/outcomes.ts +3 -2
- package/src/pi-settings.ts +59 -0
- package/src/preferences.ts +2 -2
- package/src/redaction.ts +50 -0
- package/src/scout-context.ts +121 -485
- package/src/scout-curation.ts +84 -0
- package/src/scout-groups.ts +485 -0
- package/src/scout-protocol.ts +122 -0
- package/src/scout-reconstruct.ts +44 -0
- package/src/scout-types.ts +66 -0
- package/src/scout.ts +53 -63
- package/src/session-state.ts +215 -78
- package/src/tool-result-cap.ts +88 -0
- package/src/tools/consult-context.ts +155 -0
- package/src/tools/consultation.ts +71 -220
- package/src/tools/gate-policy.ts +5 -5
- package/src/tools/gate-protocol.ts +2 -2
- package/src/tools/jev-filter.ts +202 -0
- package/src/tools/jev-turn-gate.ts +183 -0
- package/src/tools/loop-gate.ts +71 -61
- package/src/tools/outage-notifier.ts +30 -0
- package/src/tools/prompts.ts +4 -7
- package/src/tools/register-ask-advisor.ts +46 -15
- package/src/tools/register-lifecycle.ts +7 -7
- package/src/tools/register-outcome.ts +5 -5
- package/src/tools/register-renderers.ts +63 -5
- package/src/tools/registration.ts +27 -10
- package/src/tools/render-advisor-result.ts +54 -22
- package/src/tools/render-common.ts +25 -13
- package/src/tools/scout-status.ts +5 -5
- package/src/tools/session.ts +1 -1
- package/src/tools/types.ts +17 -7
- package/src/tools.ts +9 -9
- package/src/ui/jev-setup-submenu.ts +344 -0
- package/src/ui/manual-dialog-render.ts +2 -2
- package/src/ui/manual-dialog.ts +4 -9
- package/src/ui/masked-input.ts +68 -0
- package/src/ui/model-selector.ts +1 -1
- package/src/ui/settings-formatting.ts +2 -2
- package/src/ui/settings-items.ts +166 -5
- package/src/ui/settings-mutations.ts +37 -2
- package/src/ui/settings-selector.ts +5 -5
- package/src/ui/text-setting-submenu.ts +1 -1
- package/src/ui/types.ts +13 -2
- package/src/ui.ts +4 -4
- package/src/usage.ts +4 -7
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { Fetch } from "@typesafe-ai/sdk";
|
|
3
|
+
import {
|
|
4
|
+
advisorJevTurnGateEveryTurnsRef,
|
|
5
|
+
advisorJevTurnGateNoulThresholdRef,
|
|
6
|
+
getAdvisorMaxCallsPerSession,
|
|
7
|
+
isSimpleMode,
|
|
8
|
+
} from "../config/state.ts";
|
|
9
|
+
import { herdrAdvisorActivity } from "../herdr.ts";
|
|
10
|
+
import { JevFailure, jevClientFromCredentials } from "../jev/client.ts";
|
|
11
|
+
import { composeTurnGateVerdict } from "../jev/questions.ts";
|
|
12
|
+
import { buildJevState } from "../jev/state.ts";
|
|
13
|
+
import { type JevCredentials, resolveJevTransport } from "../jev/transport.ts";
|
|
14
|
+
import type { AdvisorSessionState } from "../session-state.ts";
|
|
15
|
+
import { advisorUsageCost } from "../usage.ts";
|
|
16
|
+
import type { consultAdvisor } from "./consultation.ts";
|
|
17
|
+
import { updateAdvisorUsageStatus } from "./gate-policy.ts";
|
|
18
|
+
import { createOutageNotifier } from "./outage-notifier.ts";
|
|
19
|
+
|
|
20
|
+
export const turnGateQuestion = {
|
|
21
|
+
criteria: {
|
|
22
|
+
false:
|
|
23
|
+
"The executor is progressing soundly on work that matches the user's intent; interrupting would add nothing material.",
|
|
24
|
+
true: "The executor is approaching a material decision, repeating a failure, about to claim success without validation, or drifting from the user's intent; a second opinion now would change what happens next.",
|
|
25
|
+
},
|
|
26
|
+
instructions:
|
|
27
|
+
"Should a senior engineering advisor be consulted right now, before the executor continues? Judge from `recent_conversation`.",
|
|
28
|
+
type: "noul",
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
export interface JevTurnGateDeps {
|
|
32
|
+
consult?: typeof consultAdvisor;
|
|
33
|
+
fetch?: Fetch;
|
|
34
|
+
resolveTransport?: () => Promise<JevCredentials | undefined>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface JevTurnGateRegistration {
|
|
38
|
+
activeTools: () => string[];
|
|
39
|
+
consult: typeof consultAdvisor;
|
|
40
|
+
deps?: JevTurnGateDeps;
|
|
41
|
+
send: (message: {
|
|
42
|
+
content: string;
|
|
43
|
+
customType: string;
|
|
44
|
+
details: Record<string, unknown>;
|
|
45
|
+
display: boolean;
|
|
46
|
+
}) => void;
|
|
47
|
+
session: AdvisorSessionState;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const outageNotifier = createOutageNotifier(
|
|
51
|
+
(category, message) =>
|
|
52
|
+
`Advisor Jev turn gate failed (${category}); continuing without a proactive consultation. ${message}`
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const notifyFailureOnce = outageNotifier.notify;
|
|
56
|
+
|
|
57
|
+
/** Test-only: re-arms the once-per-outage notification. */
|
|
58
|
+
export const resetJevTurnGateNotification = outageNotifier.reset;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* turn_end handler: counts turns without a consultation and, every Nth such
|
|
62
|
+
* turn, asks Jev whether the Advisor should proactively weigh in. Confident
|
|
63
|
+
* true runs a real consultation delivered as a steer message; everything else
|
|
64
|
+
* keeps the status quo.
|
|
65
|
+
*/
|
|
66
|
+
export const handleJevTurnEnd = async (
|
|
67
|
+
registration: JevTurnGateRegistration,
|
|
68
|
+
ctx: ExtensionContext
|
|
69
|
+
): Promise<void> => {
|
|
70
|
+
const { session } = registration;
|
|
71
|
+
session.recordCompletedTurn();
|
|
72
|
+
const interval = advisorJevTurnGateEveryTurnsRef;
|
|
73
|
+
if (
|
|
74
|
+
interval <= 0 ||
|
|
75
|
+
session.turnsSinceConsultation <= 0 ||
|
|
76
|
+
session.turnsSinceConsultation % interval !== 0
|
|
77
|
+
) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (
|
|
81
|
+
isSimpleMode() ||
|
|
82
|
+
session.blocked ||
|
|
83
|
+
!registration.activeTools().includes("ask_advisor") ||
|
|
84
|
+
!session.canConsult(getAdvisorMaxCallsPerSession())
|
|
85
|
+
) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const consult = registration.deps?.consult ?? registration.consult;
|
|
90
|
+
const deps = registration.deps ?? {};
|
|
91
|
+
try {
|
|
92
|
+
const credentials = await (deps.resolveTransport ?? resolveJevTransport)(
|
|
93
|
+
ctx
|
|
94
|
+
);
|
|
95
|
+
if (!credentials) {
|
|
96
|
+
session.recordJevGateFailure();
|
|
97
|
+
notifyFailureOnce(
|
|
98
|
+
ctx,
|
|
99
|
+
"missing-key",
|
|
100
|
+
"No Jev credentials resolved (no TypeSafe key and no OpenRouter login)."
|
|
101
|
+
);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const client = jevClientFromCredentials(credentials, deps.fetch);
|
|
105
|
+
const result = await client.ask(buildJevState(ctx, {}), {
|
|
106
|
+
should_consult: turnGateQuestion,
|
|
107
|
+
});
|
|
108
|
+
session.recordJevGateCheck(result.usage);
|
|
109
|
+
const shouldConsult = composeTurnGateVerdict(
|
|
110
|
+
result.answers,
|
|
111
|
+
advisorJevTurnGateNoulThresholdRef
|
|
112
|
+
);
|
|
113
|
+
if (!shouldConsult) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
session.consumeCall();
|
|
118
|
+
herdrAdvisorActivity.start();
|
|
119
|
+
registration.send({
|
|
120
|
+
content: "Proactive Advisor turn review",
|
|
121
|
+
customType: "advisor-turn-gate-call",
|
|
122
|
+
details: {
|
|
123
|
+
question: `Turn gate: ${session.turnsSinceConsultation} turns without a consultation`,
|
|
124
|
+
turn: session.sessionTurnOrdinal,
|
|
125
|
+
},
|
|
126
|
+
display: true,
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
const consulted = await consult(
|
|
130
|
+
ctx,
|
|
131
|
+
undefined,
|
|
132
|
+
ctx.signal,
|
|
133
|
+
undefined,
|
|
134
|
+
"turn-gate"
|
|
135
|
+
);
|
|
136
|
+
session.recordJevGateConsultation();
|
|
137
|
+
session.resetTurnsSinceConsultation();
|
|
138
|
+
session.recordInvocation({
|
|
139
|
+
cost: advisorUsageCost(consulted.usage),
|
|
140
|
+
executionEffect: "continued",
|
|
141
|
+
kind: "markdown",
|
|
142
|
+
model: consulted.model,
|
|
143
|
+
trigger: "turn-gate",
|
|
144
|
+
usage: consulted.usage,
|
|
145
|
+
});
|
|
146
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
147
|
+
registration.send({
|
|
148
|
+
content: consulted.markdown,
|
|
149
|
+
customType: "advisor-turn-gate-result",
|
|
150
|
+
details: {
|
|
151
|
+
advisor: consulted.model,
|
|
152
|
+
text: consulted.markdown,
|
|
153
|
+
usage: consulted.usage,
|
|
154
|
+
},
|
|
155
|
+
display: true,
|
|
156
|
+
});
|
|
157
|
+
} finally {
|
|
158
|
+
herdrAdvisorActivity.finish();
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
session.recordJevGateFailure();
|
|
162
|
+
if (error instanceof JevFailure) {
|
|
163
|
+
notifyFailureOnce(ctx, error.category, error.message);
|
|
164
|
+
} else if (!ctx.signal?.aborted) {
|
|
165
|
+
notifyFailureOnce(
|
|
166
|
+
ctx,
|
|
167
|
+
"error",
|
|
168
|
+
error instanceof Error ? error.message : String(error)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/** Wires the turn_end handler on an ExtensionAPI-like surface. */
|
|
175
|
+
export const registerJevTurnGate = <T extends string>(
|
|
176
|
+
on: (
|
|
177
|
+
event: T,
|
|
178
|
+
handler: (event: unknown, ctx: ExtensionContext) => unknown
|
|
179
|
+
) => void,
|
|
180
|
+
registration: JevTurnGateRegistration
|
|
181
|
+
): void => {
|
|
182
|
+
on("turn_end" as T, (_event, ctx) => handleJevTurnEnd(registration, ctx));
|
|
183
|
+
};
|
package/src/tools/loop-gate.ts
CHANGED
|
@@ -12,24 +12,25 @@ import {
|
|
|
12
12
|
advisorScoutEnabledRef,
|
|
13
13
|
getAdvisorMaxCallsPerSession,
|
|
14
14
|
isSimpleMode,
|
|
15
|
-
} from "../config/state.
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import
|
|
15
|
+
} from "../config/state.ts";
|
|
16
|
+
import type { GateFailureMode } from "../config/types.ts";
|
|
17
|
+
import { herdrAdvisorActivity } from "../herdr.ts";
|
|
18
|
+
import type { AdvisorSessionState } from "../session-state.ts";
|
|
19
|
+
import { advisorUsageCost, snapshotAdvisorUsage } from "../usage.ts";
|
|
20
|
+
import type { runAdvisorGate } from "./consultation.ts";
|
|
20
21
|
import {
|
|
21
22
|
blockedDecisionEffect,
|
|
22
23
|
failureEffect,
|
|
23
24
|
gateDecisionEffect,
|
|
24
25
|
gateFailureEffectForMode,
|
|
25
26
|
updateAdvisorUsageStatus,
|
|
26
|
-
} from "./gate-policy.
|
|
27
|
-
import { adviceForGateText } from "./gate-protocol.
|
|
27
|
+
} from "./gate-policy.ts";
|
|
28
|
+
import { adviceForGateText } from "./gate-protocol.ts";
|
|
28
29
|
import {
|
|
29
30
|
appendScoutLifecycleEntry,
|
|
30
31
|
type ScoutStatusManager,
|
|
31
|
-
} from "./scout-status.
|
|
32
|
-
import type { AdvisorGateResult, ScoutToolDetails } from "./types.
|
|
32
|
+
} from "./scout-status.ts";
|
|
33
|
+
import type { AdvisorGateResult, ScoutToolDetails } from "./types.ts";
|
|
33
34
|
|
|
34
35
|
const sendAutomaticGateCall = (pi: ExtensionAPI, event: ToolCallEvent) => {
|
|
35
36
|
pi.sendMessage(
|
|
@@ -87,6 +88,65 @@ const sendAutomaticGateResult = (
|
|
|
87
88
|
);
|
|
88
89
|
};
|
|
89
90
|
|
|
91
|
+
/** Records the invocation, reports it, and maps the gate decision to its
|
|
92
|
+
* tool-call result (block reason or proceed). */
|
|
93
|
+
const applyGateDecision = (
|
|
94
|
+
pi: ExtensionAPI,
|
|
95
|
+
ctx: ExtensionContext,
|
|
96
|
+
session: AdvisorSessionState,
|
|
97
|
+
result: Awaited<ReturnType<typeof runAdvisorGate>>,
|
|
98
|
+
reason: string,
|
|
99
|
+
failureMode: GateFailureMode
|
|
100
|
+
): ToolCallEventResult | undefined => {
|
|
101
|
+
if (!result.ok) {
|
|
102
|
+
session.recordInvocation({
|
|
103
|
+
executionEffect: gateFailureEffectForMode(failureMode),
|
|
104
|
+
failure: result.category,
|
|
105
|
+
kind: "gate",
|
|
106
|
+
model: advisorRef,
|
|
107
|
+
trigger: "repeated-tool-call",
|
|
108
|
+
usage: result.usage,
|
|
109
|
+
});
|
|
110
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
111
|
+
const failure = failureEffect(
|
|
112
|
+
result.category,
|
|
113
|
+
result.message,
|
|
114
|
+
ctx,
|
|
115
|
+
session,
|
|
116
|
+
failureMode
|
|
117
|
+
);
|
|
118
|
+
sendAutomaticGateFailure(
|
|
119
|
+
pi,
|
|
120
|
+
`**Advisor gate failure (${result.category}):** ${result.message}`,
|
|
121
|
+
result.usage
|
|
122
|
+
);
|
|
123
|
+
return failure.block
|
|
124
|
+
? { block: true, reason: `${reason}\n${failure.reason}` }
|
|
125
|
+
: undefined;
|
|
126
|
+
}
|
|
127
|
+
session.recordInvocation({
|
|
128
|
+
cost: advisorUsageCost(result.usage),
|
|
129
|
+
decision: result.decision,
|
|
130
|
+
executionEffect: gateDecisionEffect(result.decision, failureMode),
|
|
131
|
+
kind: "gate",
|
|
132
|
+
model: result.model,
|
|
133
|
+
trigger: result.trigger,
|
|
134
|
+
usage: result.usage,
|
|
135
|
+
});
|
|
136
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
137
|
+
sendAutomaticGateResult(pi, result);
|
|
138
|
+
if (result.decision === "proceed") {
|
|
139
|
+
session.resetRepetition();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const gateReason = `Advisor loop review: ${result.markdown}`;
|
|
143
|
+
if (result.decision === "blocked") {
|
|
144
|
+
const effect = blockedDecisionEffect(gateReason, ctx, session, failureMode);
|
|
145
|
+
return effect.block ? { block: true, reason: effect.reason } : undefined;
|
|
146
|
+
}
|
|
147
|
+
return { block: true, reason: gateReason };
|
|
148
|
+
};
|
|
149
|
+
|
|
90
150
|
export const handleAutomaticGate = async (
|
|
91
151
|
pi: ExtensionAPI,
|
|
92
152
|
event: ToolCallEvent,
|
|
@@ -120,6 +180,7 @@ export const handleAutomaticGate = async (
|
|
|
120
180
|
return failure.block ? { block: true, reason: failure.reason } : undefined;
|
|
121
181
|
}
|
|
122
182
|
session.consumeCall();
|
|
183
|
+
session.resetTurnsSinceConsultation();
|
|
123
184
|
herdrAdvisorActivity.start();
|
|
124
185
|
let scoutDetails: ScoutToolDetails | undefined;
|
|
125
186
|
const scoutStatusToken = Symbol("automatic-gate-scout");
|
|
@@ -151,58 +212,7 @@ export const handleAutomaticGate = async (
|
|
|
151
212
|
event.toolCallId
|
|
152
213
|
);
|
|
153
214
|
ensureGateCall();
|
|
154
|
-
|
|
155
|
-
session.recordInvocation({
|
|
156
|
-
executionEffect: gateFailureEffectForMode(failureMode),
|
|
157
|
-
failure: result.category,
|
|
158
|
-
kind: "gate",
|
|
159
|
-
model: advisorRef,
|
|
160
|
-
trigger: "repeated-tool-call",
|
|
161
|
-
usage: result.usage,
|
|
162
|
-
});
|
|
163
|
-
updateAdvisorUsageStatus(ctx, session);
|
|
164
|
-
const failure = failureEffect(
|
|
165
|
-
result.category,
|
|
166
|
-
result.message,
|
|
167
|
-
ctx,
|
|
168
|
-
session,
|
|
169
|
-
failureMode
|
|
170
|
-
);
|
|
171
|
-
sendAutomaticGateFailure(
|
|
172
|
-
pi,
|
|
173
|
-
`**Advisor gate failure (${result.category}):** ${result.message}`,
|
|
174
|
-
result.usage
|
|
175
|
-
);
|
|
176
|
-
return failure.block
|
|
177
|
-
? { block: true, reason: `${reason}\n${failure.reason}` }
|
|
178
|
-
: undefined;
|
|
179
|
-
}
|
|
180
|
-
session.recordInvocation({
|
|
181
|
-
cost: advisorUsageCost(result.usage),
|
|
182
|
-
decision: result.decision,
|
|
183
|
-
executionEffect: gateDecisionEffect(result.decision, failureMode),
|
|
184
|
-
kind: "gate",
|
|
185
|
-
model: result.model,
|
|
186
|
-
trigger: result.trigger,
|
|
187
|
-
usage: result.usage,
|
|
188
|
-
});
|
|
189
|
-
updateAdvisorUsageStatus(ctx, session);
|
|
190
|
-
sendAutomaticGateResult(pi, result);
|
|
191
|
-
if (result.decision === "proceed") {
|
|
192
|
-
session.resetRepetition();
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
const gateReason = `Advisor loop review: ${result.markdown}`;
|
|
196
|
-
if (result.decision === "blocked") {
|
|
197
|
-
const effect = blockedDecisionEffect(
|
|
198
|
-
gateReason,
|
|
199
|
-
ctx,
|
|
200
|
-
session,
|
|
201
|
-
failureMode
|
|
202
|
-
);
|
|
203
|
-
return effect.block ? { block: true, reason: effect.reason } : undefined;
|
|
204
|
-
}
|
|
205
|
-
return { block: true, reason: gateReason };
|
|
215
|
+
return applyGateDecision(pi, ctx, session, result, reason, failureMode);
|
|
206
216
|
} finally {
|
|
207
217
|
scoutStatus.release(ctx, scoutStatusToken);
|
|
208
218
|
herdrAdvisorActivity.finish();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export interface OutageNotifier {
|
|
4
|
+
/** Notifies once per distinct category+message; records the key even headless. */
|
|
5
|
+
notify: (ctx: ExtensionContext, category: string, message: string) => void;
|
|
6
|
+
/** Test-only: re-arms the once-per-outage notification. */
|
|
7
|
+
reset: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Creates an independently stateful once-per-outage notifier. */
|
|
11
|
+
export const createOutageNotifier = (
|
|
12
|
+
format: (category: string, message: string) => string
|
|
13
|
+
): OutageNotifier => {
|
|
14
|
+
let lastKey: string | undefined;
|
|
15
|
+
return {
|
|
16
|
+
notify: (ctx, category, message) => {
|
|
17
|
+
const key = `${category}:${message}`;
|
|
18
|
+
if (key === lastKey) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
lastKey = key;
|
|
22
|
+
if (ctx.hasUI) {
|
|
23
|
+
ctx.ui.notify(format(category, message), "warning");
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
reset: () => {
|
|
27
|
+
lastKey = undefined;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
};
|
package/src/tools/prompts.ts
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
advisorPlanGateRef,
|
|
7
7
|
contextMaxCharsRef,
|
|
8
8
|
isSimpleMode,
|
|
9
|
-
} from "../config/state.
|
|
10
|
-
import { recentConversation } from "../conversation.
|
|
9
|
+
} from "../config/state.ts";
|
|
10
|
+
import { recentConversation } from "../conversation.ts";
|
|
11
11
|
import {
|
|
12
12
|
capRepositoryContext,
|
|
13
13
|
escapeRepositoryText,
|
|
14
14
|
type GitContextLevel,
|
|
15
15
|
type GitContextResult,
|
|
16
|
-
} from "../git.
|
|
16
|
+
} from "../git.ts";
|
|
17
17
|
|
|
18
18
|
export const advisorMessageText = (
|
|
19
19
|
conversation: string,
|
|
@@ -47,10 +47,7 @@ export const advisorMessageText = (
|
|
|
47
47
|
);
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
/**
|
|
51
|
-
* Splits the character budget so repository context can never starve the
|
|
52
|
-
* conversation: it may claim its own cap or half the budget, whichever is less.
|
|
53
|
-
*/
|
|
50
|
+
/** Splits the character budget so repository context can never starve the conversation. */
|
|
54
51
|
export const advisorGitContextBudget = (
|
|
55
52
|
contextMaxChars: number,
|
|
56
53
|
gitContextMaxChars: number
|
|
@@ -5,35 +5,31 @@ import {
|
|
|
5
5
|
getAdvisorMaxCallsPerSession,
|
|
6
6
|
getAdvisorSettings,
|
|
7
7
|
isSimpleMode,
|
|
8
|
-
} from "../config/state.
|
|
9
|
-
import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "../herdr.
|
|
8
|
+
} from "../config/state.ts";
|
|
9
|
+
import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "../herdr.ts";
|
|
10
10
|
import {
|
|
11
11
|
ADVISOR_STREAM_UPDATE_INTERVAL_MS,
|
|
12
12
|
createCoalescedUpdate,
|
|
13
|
-
} from "../model-stream.
|
|
13
|
+
} from "../model-stream.ts";
|
|
14
14
|
import {
|
|
15
15
|
advisorUsageCost,
|
|
16
16
|
advisorUsageForPi,
|
|
17
17
|
snapshotAdvisorUsage,
|
|
18
|
-
} from "../usage.
|
|
19
|
-
import { notifyLocalFailure, updateAdvisorUsageStatus } from "./gate-policy.
|
|
20
|
-
import {
|
|
18
|
+
} from "../usage.ts";
|
|
19
|
+
import { notifyLocalFailure, updateAdvisorUsageStatus } from "./gate-policy.ts";
|
|
20
|
+
import { normalizeScreeningQuestion, screeningSkipText } from "./jev-filter.ts";
|
|
21
|
+
import { renderAdvisorResult } from "./render-advisor-result.ts";
|
|
21
22
|
import {
|
|
22
23
|
renderAdvisorCallBox,
|
|
23
24
|
resolveAdvisorRequest,
|
|
24
|
-
} from "./render-common.
|
|
25
|
-
import { scoutDetailsFromEvent } from "./scout-status.
|
|
25
|
+
} from "./render-common.ts";
|
|
26
|
+
import { scoutDetailsFromEvent } from "./scout-status.ts";
|
|
26
27
|
import type {
|
|
27
28
|
AdvisorToolContext,
|
|
28
29
|
AdvisorToolDetails,
|
|
29
30
|
ToolRegistrationContext,
|
|
30
|
-
} from "./types.
|
|
31
|
+
} from "./types.ts";
|
|
31
32
|
|
|
32
|
-
/**
|
|
33
|
-
* Validates a tracked-file follow-up and consumes the one-shot handoff only
|
|
34
|
-
* when the call is worth it: consent must be enabled and the session budget
|
|
35
|
-
* was checked by the caller. A rejected call never burns the claim.
|
|
36
|
-
*/
|
|
37
33
|
const claimTrackedHandoff = (
|
|
38
34
|
session: ToolRegistrationContext["session"],
|
|
39
35
|
includeTrackedFiles: string[] | undefined
|
|
@@ -57,6 +53,7 @@ export const registerAskAdvisorTool = ({
|
|
|
57
53
|
consult: requestAdvisor,
|
|
58
54
|
pi,
|
|
59
55
|
reservedCalls,
|
|
56
|
+
screen,
|
|
60
57
|
session,
|
|
61
58
|
}: ToolRegistrationContext): void => {
|
|
62
59
|
pi.registerTool({
|
|
@@ -71,9 +68,36 @@ export const registerAskAdvisorTool = ({
|
|
|
71
68
|
) {
|
|
72
69
|
throw new Error("Advisor call budget exhausted for this session.");
|
|
73
70
|
}
|
|
71
|
+
// The Jev screening seam sits after the budget re-check and before the
|
|
72
|
+
// handoff claim: a skipped call consumes neither the one-shot handoff
|
|
73
|
+
// nor the budget.
|
|
74
|
+
const normalizedQuestion = normalizeScreeningQuestion(
|
|
75
|
+
resolveAdvisorRequest(params.question)
|
|
76
|
+
);
|
|
77
|
+
const screening = await screen(ctx, session, {
|
|
78
|
+
draft: params.draft,
|
|
79
|
+
force: params.force,
|
|
80
|
+
question: resolveAdvisorRequest(params.question),
|
|
81
|
+
signal,
|
|
82
|
+
});
|
|
83
|
+
if (screening.decision === "skip") {
|
|
84
|
+
const skipText = screeningSkipText(screening);
|
|
85
|
+
return {
|
|
86
|
+
content: [{ text: skipText, type: "text" }],
|
|
87
|
+
details: {
|
|
88
|
+
jev: {
|
|
89
|
+
kind: screening.kind,
|
|
90
|
+
reason: screening.reason,
|
|
91
|
+
skipped: true,
|
|
92
|
+
},
|
|
93
|
+
text: skipText,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
74
97
|
claimTrackedHandoff(session, params.includeTrackedFiles);
|
|
75
98
|
if (!isSimpleMode()) {
|
|
76
99
|
session.consumeCall();
|
|
100
|
+
session.resetTurnsSinceConsultation();
|
|
77
101
|
}
|
|
78
102
|
herdrAdvisorActivity.start();
|
|
79
103
|
let scoutDetails: AdvisorToolDetails["scout"];
|
|
@@ -128,7 +152,8 @@ export const registerAskAdvisorTool = ({
|
|
|
128
152
|
result.adviceId,
|
|
129
153
|
result.markdown,
|
|
130
154
|
result.trigger,
|
|
131
|
-
Boolean(result.draftBytes)
|
|
155
|
+
Boolean(result.draftBytes),
|
|
156
|
+
normalizedQuestion
|
|
132
157
|
);
|
|
133
158
|
session.recordInvocation({
|
|
134
159
|
cost: advisorUsageCost(result.usage),
|
|
@@ -194,6 +219,12 @@ export const registerAskAdvisorTool = ({
|
|
|
194
219
|
"Concise untrusted draft for plan or completion review; claims are not verification evidence.",
|
|
195
220
|
})
|
|
196
221
|
),
|
|
222
|
+
force: Type.Optional(
|
|
223
|
+
Type.Boolean({
|
|
224
|
+
description:
|
|
225
|
+
"Set true only when you judge a decision genuinely material after a consultation was screened out; bypasses screening.",
|
|
226
|
+
})
|
|
227
|
+
),
|
|
197
228
|
gitContext: Type.Optional(
|
|
198
229
|
Type.Union(
|
|
199
230
|
[Type.Literal("none"), Type.Literal("summary"), Type.Literal("full")],
|
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
advisorSessionSummaryRef,
|
|
3
3
|
getAdvisorMaxCallsPerSession,
|
|
4
4
|
isSimpleMode,
|
|
5
|
-
} from "../config/state.
|
|
6
|
-
import { loadConfig } from "../config/storage.
|
|
7
|
-
import { herdrAdvisorBlock } from "../herdr.
|
|
8
|
-
import { reserveAdvisorCall } from "./gate-policy.
|
|
9
|
-
import { handleAutomaticGate } from "./loop-gate.
|
|
10
|
-
import { advisorInvocationGuidelines } from "./prompts.
|
|
11
|
-
import type { ToolRegistrationContext } from "./types.
|
|
5
|
+
} from "../config/state.ts";
|
|
6
|
+
import { loadConfig } from "../config/storage.ts";
|
|
7
|
+
import { herdrAdvisorBlock } from "../herdr.ts";
|
|
8
|
+
import { reserveAdvisorCall } from "./gate-policy.ts";
|
|
9
|
+
import { handleAutomaticGate } from "./loop-gate.ts";
|
|
10
|
+
import { advisorInvocationGuidelines } from "./prompts.ts";
|
|
11
|
+
import type { ToolRegistrationContext } from "./types.ts";
|
|
12
12
|
|
|
13
13
|
export const registerToolLifecycle = ({
|
|
14
14
|
pi,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { advisorOutcomeLoggingRef } from "../config/state.
|
|
4
|
-
import { loadConfig } from "../config/storage.
|
|
5
|
-
import { textFrom } from "../conversation.
|
|
6
|
-
import { ADOPTIONS, VALIDATIONS } from "../outcomes.
|
|
7
|
-
import type { ToolRegistrationContext } from "./types.
|
|
3
|
+
import { advisorOutcomeLoggingRef } from "../config/state.ts";
|
|
4
|
+
import { loadConfig } from "../config/storage.ts";
|
|
5
|
+
import { textFrom } from "../conversation.ts";
|
|
6
|
+
import { ADOPTIONS, VALIDATIONS } from "../outcomes.ts";
|
|
7
|
+
import type { ToolRegistrationContext } from "./types.ts";
|
|
8
8
|
|
|
9
9
|
export const registerOutcomeTool = ({
|
|
10
10
|
appendOutcome: appendAdvisorOutcome,
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
getMarkdownTheme,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { Box, Markdown, Text } from "@earendil-works/pi-tui";
|
|
6
|
-
import { getAdvisorSettings } from "../config/state.
|
|
7
|
-
import { formatAdvisorUsage } from "../usage.
|
|
8
|
-
import { adviceForDisplay, renderAdvisorCallBox } from "./render-common.
|
|
9
|
-
import { renderScoutDetails } from "./scout-status.
|
|
10
|
-
import type { GateDecision, ScoutToolDetails } from "./types.
|
|
6
|
+
import { getAdvisorSettings } from "../config/state.ts";
|
|
7
|
+
import { formatAdvisorUsage } from "../usage.ts";
|
|
8
|
+
import { adviceForDisplay, renderAdvisorCallBox } from "./render-common.ts";
|
|
9
|
+
import { renderScoutDetails } from "./scout-status.ts";
|
|
10
|
+
import type { GateDecision, ScoutToolDetails } from "./types.ts";
|
|
11
11
|
|
|
12
12
|
export const registerToolRenderers = (pi: ExtensionAPI): void => {
|
|
13
13
|
pi.registerEntryRenderer?.(
|
|
@@ -20,6 +20,64 @@ export const registerToolRenderers = (pi: ExtensionAPI): void => {
|
|
|
20
20
|
}
|
|
21
21
|
);
|
|
22
22
|
|
|
23
|
+
pi.registerMessageRenderer?.(
|
|
24
|
+
"advisor-turn-gate-call",
|
|
25
|
+
(message, _options, theme) => {
|
|
26
|
+
const details = message.details as { question?: string } | undefined;
|
|
27
|
+
return renderAdvisorCallBox(details?.question, theme);
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
pi.registerMessageRenderer?.(
|
|
32
|
+
"advisor-turn-gate-result",
|
|
33
|
+
(message, { expanded }, theme) => {
|
|
34
|
+
const details = message.details as
|
|
35
|
+
| { advisor?: string; text?: string; usage?: unknown }
|
|
36
|
+
| undefined;
|
|
37
|
+
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
38
|
+
box.addChild(
|
|
39
|
+
new Text(
|
|
40
|
+
theme.fg("warning", theme.bold("◆ ADVISOR · TURN REVIEW")),
|
|
41
|
+
0,
|
|
42
|
+
0
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
if (details?.advisor) {
|
|
46
|
+
box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
47
|
+
}
|
|
48
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
49
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
50
|
+
if (usage) {
|
|
51
|
+
box.addChild(new Text(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (details?.text) {
|
|
55
|
+
box.addChild(
|
|
56
|
+
new Markdown(
|
|
57
|
+
adviceForDisplay(details.text, Boolean(expanded)),
|
|
58
|
+
0,
|
|
59
|
+
0,
|
|
60
|
+
getMarkdownTheme()
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
} else {
|
|
64
|
+
box.addChild(
|
|
65
|
+
new Text(
|
|
66
|
+
theme.fg(
|
|
67
|
+
"error",
|
|
68
|
+
typeof message.content === "string"
|
|
69
|
+
? message.content
|
|
70
|
+
: "Advisor turn review failed."
|
|
71
|
+
),
|
|
72
|
+
0,
|
|
73
|
+
0
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return box;
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
|
|
23
81
|
pi.registerMessageRenderer?.(
|
|
24
82
|
"advisor-loop-call",
|
|
25
83
|
(message, _options, theme) => {
|