pi-advisor-flow 0.8.2 → 0.8.3-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/index.js +319 -184
- package/extensions/index.ts +12 -3
- package/package.json +4 -4
- package/src/commands/lifecycle.ts +1 -2
- package/src/commands/manual-consultation.ts +3 -3
- package/src/commands/runtime.ts +3 -1
- package/src/commands/types.ts +3 -0
- package/src/herdr-block.ts +15 -3
- package/src/herdr.ts +58 -8
- package/src/jev/client.ts +14 -6
- package/src/redaction.ts +6 -1
- package/src/session-state.ts +30 -4
- package/src/tools/gate-policy.ts +8 -5
- package/src/tools/jev-filter.ts +3 -2
- package/src/tools/jev-turn-gate.ts +45 -29
- package/src/tools/loop-gate.ts +32 -12
- package/src/tools/register-ask-advisor.ts +181 -144
- package/src/tools/register-lifecycle.ts +16 -4
- package/src/tools/registration.ts +4 -0
- package/src/tools/types.ts +5 -0
- package/src/ui/jev-setup-submenu.ts +8 -2
package/src/tools/loop-gate.ts
CHANGED
|
@@ -15,7 +15,8 @@ import {
|
|
|
15
15
|
isSimpleMode,
|
|
16
16
|
} from "../config/state.ts";
|
|
17
17
|
import type { GateFailureMode } from "../config/types.ts";
|
|
18
|
-
import { herdrAdvisorActivity } from "../herdr.ts";
|
|
18
|
+
import { herdrAdvisorActivity, herdrAdvisorBlock } from "../herdr.ts";
|
|
19
|
+
import type { HerdrAdvisorActivityScope, HerdrAdvisorBlock } from "../herdr.ts";
|
|
19
20
|
import type { AdvisorSessionState, GateDecision } from "../session-state.ts";
|
|
20
21
|
import { advisorUsageCost, snapshotAdvisorUsage } from "../usage.ts";
|
|
21
22
|
import type { AdvisorUsageSnapshot } from "../usage.ts";
|
|
@@ -106,7 +107,8 @@ const applyGateDecision = (
|
|
|
106
107
|
session: AdvisorSessionState,
|
|
107
108
|
result: Awaited<ReturnType<typeof runAdvisorGate>>,
|
|
108
109
|
reason: string,
|
|
109
|
-
failureMode: GateFailureMode
|
|
110
|
+
failureMode: GateFailureMode,
|
|
111
|
+
herdrBlock: HerdrAdvisorBlock
|
|
110
112
|
): ToolCallEventResult | undefined => {
|
|
111
113
|
if (!result.ok) {
|
|
112
114
|
session.recordInvocation({
|
|
@@ -123,7 +125,8 @@ const applyGateDecision = (
|
|
|
123
125
|
result.message,
|
|
124
126
|
ctx,
|
|
125
127
|
session,
|
|
126
|
-
failureMode
|
|
128
|
+
failureMode,
|
|
129
|
+
herdrBlock
|
|
127
130
|
);
|
|
128
131
|
sendAutomaticGateFailure(
|
|
129
132
|
pi,
|
|
@@ -151,7 +154,13 @@ const applyGateDecision = (
|
|
|
151
154
|
}
|
|
152
155
|
const gateReason = `Advisor loop review: ${result.markdown}`;
|
|
153
156
|
if (result.decision === "blocked") {
|
|
154
|
-
const effect = blockedDecisionEffect(
|
|
157
|
+
const effect = blockedDecisionEffect(
|
|
158
|
+
gateReason,
|
|
159
|
+
ctx,
|
|
160
|
+
session,
|
|
161
|
+
failureMode,
|
|
162
|
+
herdrBlock
|
|
163
|
+
);
|
|
155
164
|
return effect.block ? { block: true, reason: effect.reason } : undefined;
|
|
156
165
|
}
|
|
157
166
|
return { block: true, reason: gateReason };
|
|
@@ -163,7 +172,9 @@ export const handleAutomaticGate = async (
|
|
|
163
172
|
ctx: ExtensionContext,
|
|
164
173
|
session: AdvisorSessionState,
|
|
165
174
|
runGate: typeof runAdvisorGate,
|
|
166
|
-
scoutStatus: ScoutStatusManager
|
|
175
|
+
scoutStatus: ScoutStatusManager,
|
|
176
|
+
herdrActivity: HerdrAdvisorActivityScope = herdrAdvisorActivity,
|
|
177
|
+
herdrBlock: HerdrAdvisorBlock = herdrAdvisorBlock
|
|
167
178
|
): Promise<ToolCallEventResult | undefined> => {
|
|
168
179
|
if (
|
|
169
180
|
isSimpleMode() ||
|
|
@@ -186,13 +197,14 @@ export const handleAutomaticGate = async (
|
|
|
186
197
|
"Advisor gate call budget is exhausted.",
|
|
187
198
|
ctx,
|
|
188
199
|
session,
|
|
189
|
-
failureMode
|
|
200
|
+
failureMode,
|
|
201
|
+
herdrBlock
|
|
190
202
|
);
|
|
191
203
|
return failure.block ? { block: true, reason: failure.reason } : undefined;
|
|
192
204
|
}
|
|
193
205
|
session.consumeCall();
|
|
194
206
|
session.resetTurnsSinceConsultation();
|
|
195
|
-
|
|
207
|
+
const finishHerdrActivity = herdrActivity.start();
|
|
196
208
|
let scoutDetails: ScoutToolDetails | undefined;
|
|
197
209
|
const scoutStatusToken = Symbol("automatic-gate-scout");
|
|
198
210
|
scoutStatus.register(scoutStatusToken);
|
|
@@ -203,10 +215,10 @@ export const handleAutomaticGate = async (
|
|
|
203
215
|
gateCallSent = true;
|
|
204
216
|
}
|
|
205
217
|
};
|
|
206
|
-
if (!advisorScoutEnabledRef) {
|
|
207
|
-
ensureGateCall();
|
|
208
|
-
}
|
|
209
218
|
try {
|
|
219
|
+
if (!advisorScoutEnabledRef) {
|
|
220
|
+
ensureGateCall();
|
|
221
|
+
}
|
|
210
222
|
const result = await runGate(
|
|
211
223
|
ctx,
|
|
212
224
|
`${reason} Review the repeated actions and recommend the smallest safe next step.`,
|
|
@@ -223,9 +235,17 @@ export const handleAutomaticGate = async (
|
|
|
223
235
|
event.toolCallId
|
|
224
236
|
);
|
|
225
237
|
ensureGateCall();
|
|
226
|
-
return applyGateDecision(
|
|
238
|
+
return applyGateDecision(
|
|
239
|
+
pi,
|
|
240
|
+
ctx,
|
|
241
|
+
session,
|
|
242
|
+
result,
|
|
243
|
+
reason,
|
|
244
|
+
failureMode,
|
|
245
|
+
herdrBlock
|
|
246
|
+
);
|
|
227
247
|
} finally {
|
|
248
|
+
finishHerdrActivity();
|
|
228
249
|
scoutStatus.release(ctx, scoutStatusToken);
|
|
229
|
-
herdrAdvisorActivity.finish();
|
|
230
250
|
}
|
|
231
251
|
};
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
getAdvisorSettings,
|
|
11
11
|
isSimpleMode,
|
|
12
12
|
} from "../config/state.ts";
|
|
13
|
-
import {
|
|
13
|
+
import { notifyHerdrAdvisorFailure } from "../herdr.ts";
|
|
14
14
|
import {
|
|
15
15
|
ADVISOR_STREAM_UPDATE_INTERVAL_MS,
|
|
16
16
|
createCoalescedUpdate,
|
|
@@ -63,6 +63,7 @@ const claimTrackedHandoff = (
|
|
|
63
63
|
|
|
64
64
|
export const registerAskAdvisorTool = ({
|
|
65
65
|
consult: requestAdvisor,
|
|
66
|
+
herdrActivity,
|
|
66
67
|
pi,
|
|
67
68
|
reservedCalls,
|
|
68
69
|
screen,
|
|
@@ -72,162 +73,198 @@ export const registerAskAdvisorTool = ({
|
|
|
72
73
|
description:
|
|
73
74
|
"Consult the on-demand Advisor model for strategic guidance. Call with an empty object for a contextual review; attach an optional draft for concrete plan or completion review. If the Advisor explicitly names a missing file, you may make a sequential follow-up call with includeTrackedFiles when enabled and relevant.",
|
|
74
75
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
try {
|
|
77
|
+
assertAdvisorModelAccess(ctx);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
session.releaseCall(_id);
|
|
80
|
+
reservedCalls.delete(_id);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
const simpleMode = isSimpleMode();
|
|
79
84
|
if (
|
|
80
|
-
!
|
|
85
|
+
!simpleMode &&
|
|
86
|
+
!session.reserveCall(_id, getAdvisorMaxCallsPerSession())
|
|
81
87
|
) {
|
|
82
88
|
throw new Error("Advisor call budget exhausted for this session.");
|
|
83
89
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
);
|
|
90
|
-
const screening = await screen(ctx, session, {
|
|
91
|
-
draft: params.draft,
|
|
92
|
-
force: params.force,
|
|
93
|
-
question: resolveAdvisorRequest(params.question),
|
|
94
|
-
signal,
|
|
95
|
-
});
|
|
96
|
-
if (screening.decision === "skip") {
|
|
97
|
-
const skipText = screeningSkipText(screening);
|
|
98
|
-
return {
|
|
99
|
-
content: [{ text: skipText, type: "text" }],
|
|
100
|
-
details: {
|
|
101
|
-
jev: {
|
|
102
|
-
kind: screening.kind,
|
|
103
|
-
reason: screening.reason,
|
|
104
|
-
skipped: true,
|
|
105
|
-
},
|
|
106
|
-
text: skipText,
|
|
107
|
-
},
|
|
108
|
-
};
|
|
90
|
+
if (simpleMode) {
|
|
91
|
+
session.releaseCall(_id);
|
|
92
|
+
reservedCalls.delete(_id);
|
|
93
|
+
} else {
|
|
94
|
+
reservedCalls.add(_id);
|
|
109
95
|
}
|
|
110
|
-
|
|
111
|
-
if (!isSimpleMode()) {
|
|
112
|
-
session.consumeCall();
|
|
113
|
-
session.resetTurnsSinceConsultation();
|
|
114
|
-
}
|
|
115
|
-
herdrAdvisorActivity.start();
|
|
116
|
-
let scoutDetails: AdvisorToolDetails["scout"];
|
|
117
|
-
const coalescedUpdate = createCoalescedUpdate(
|
|
118
|
-
(update: Parameters<NonNullable<typeof onUpdate>>[0]) =>
|
|
119
|
-
onUpdate?.(update),
|
|
120
|
-
ADVISOR_STREAM_UPDATE_INTERVAL_MS
|
|
121
|
-
);
|
|
122
|
-
const flushUpdate = () => {
|
|
123
|
-
const result = coalescedUpdate.flush();
|
|
124
|
-
if (result.failed) {
|
|
125
|
-
throw result.error;
|
|
126
|
-
}
|
|
127
|
-
};
|
|
96
|
+
let normalizedQuestion: string | undefined;
|
|
128
97
|
try {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
scout: scoutDetails,
|
|
140
|
-
text: tx,
|
|
141
|
-
thinking: t,
|
|
142
|
-
},
|
|
143
|
-
}),
|
|
144
|
-
"executor-requested",
|
|
145
|
-
// "none" is the model declining repository context for this call.
|
|
146
|
-
params.gitContext === "none" ? "off" : params.gitContext,
|
|
147
|
-
params.draft,
|
|
148
|
-
params.includeUntracked,
|
|
149
|
-
params.includeTrackedFiles,
|
|
150
|
-
(event) => {
|
|
151
|
-
scoutDetails = scoutDetailsFromEvent(event, scoutDetails);
|
|
152
|
-
coalescedUpdate.update({
|
|
153
|
-
content: [{ text: scoutDetails.text ?? "", type: "text" }],
|
|
154
|
-
details: {
|
|
155
|
-
advisor: advisorRef,
|
|
156
|
-
question: resolveAdvisorRequest(params.question),
|
|
157
|
-
scout: scoutDetails,
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
},
|
|
161
|
-
_id
|
|
162
|
-
);
|
|
163
|
-
flushUpdate();
|
|
164
|
-
session.issueAdvice(
|
|
165
|
-
result.adviceId,
|
|
166
|
-
result.markdown,
|
|
167
|
-
result.trigger,
|
|
168
|
-
Boolean(result.draftBytes),
|
|
169
|
-
normalizedQuestion
|
|
98
|
+
if (
|
|
99
|
+
!simpleMode &&
|
|
100
|
+
!session.canConsult(getAdvisorMaxCallsPerSession(), _id)
|
|
101
|
+
) {
|
|
102
|
+
throw new Error("Advisor call budget exhausted for this session.");
|
|
103
|
+
}
|
|
104
|
+
// The Jev screening seam sits before the handoff claim: a skipped call
|
|
105
|
+
// consumes neither the one-shot handoff nor the budget.
|
|
106
|
+
normalizedQuestion = normalizeScreeningQuestion(
|
|
107
|
+
resolveAdvisorRequest(params.question)
|
|
170
108
|
);
|
|
171
|
-
session
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
kind: "markdown",
|
|
175
|
-
model: result.model,
|
|
176
|
-
trigger: "executor-requested",
|
|
177
|
-
usage: result.usage,
|
|
178
|
-
});
|
|
179
|
-
const usage = snapshotAdvisorUsage(result.usage);
|
|
180
|
-
const piUsage = advisorUsageForPi(result.usage);
|
|
181
|
-
updateAdvisorUsageStatus(ctx, session);
|
|
182
|
-
const details: AdvisorToolDetails = {
|
|
183
|
-
adviceId: result.adviceId,
|
|
184
|
-
advisor: result.model,
|
|
185
|
-
draftBytes: result.draftBytes,
|
|
186
|
-
preferenceBytes: result.preferenceBytes,
|
|
109
|
+
const screening = await screen(ctx, session, {
|
|
110
|
+
draft: params.draft,
|
|
111
|
+
force: params.force,
|
|
187
112
|
question: resolveAdvisorRequest(params.question),
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
113
|
+
signal,
|
|
114
|
+
});
|
|
115
|
+
if (screening.decision === "skip") {
|
|
116
|
+
const skipText = screeningSkipText(screening);
|
|
117
|
+
session.releaseCall(_id);
|
|
118
|
+
reservedCalls.delete(_id);
|
|
119
|
+
return {
|
|
120
|
+
content: [{ text: skipText, type: "text" }],
|
|
121
|
+
details: {
|
|
122
|
+
jev: {
|
|
123
|
+
kind: screening.kind,
|
|
124
|
+
reason: screening.reason,
|
|
125
|
+
skipped: true,
|
|
126
|
+
},
|
|
127
|
+
text: skipText,
|
|
202
128
|
},
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (
|
|
132
|
+
!simpleMode &&
|
|
133
|
+
!session.canConsult(getAdvisorMaxCallsPerSession(), _id)
|
|
134
|
+
) {
|
|
135
|
+
throw new Error("Advisor call budget exhausted for this session.");
|
|
136
|
+
}
|
|
137
|
+
claimTrackedHandoff(session, params.includeTrackedFiles);
|
|
138
|
+
if (!simpleMode) {
|
|
139
|
+
session.consumeCall(_id);
|
|
140
|
+
reservedCalls.delete(_id);
|
|
141
|
+
session.resetTurnsSinceConsultation();
|
|
208
142
|
}
|
|
209
|
-
return response;
|
|
210
143
|
} catch (error) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// original error because this path is also used for provider failures.
|
|
214
|
-
coalescedUpdate.flush();
|
|
215
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
216
|
-
session.recordInvocation({
|
|
217
|
-
executionEffect: "continued",
|
|
218
|
-
failure: "provider-error",
|
|
219
|
-
kind: "markdown",
|
|
220
|
-
model: advisorRef,
|
|
221
|
-
trigger: "executor-requested",
|
|
222
|
-
});
|
|
223
|
-
updateAdvisorUsageStatus(ctx, session);
|
|
224
|
-
notifyLocalFailure(ctx, message);
|
|
225
|
-
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
144
|
+
session.releaseCall(_id);
|
|
145
|
+
reservedCalls.delete(_id);
|
|
226
146
|
throw error;
|
|
227
|
-
} finally {
|
|
228
|
-
coalescedUpdate.cancel();
|
|
229
|
-
herdrAdvisorActivity.finish();
|
|
230
147
|
}
|
|
148
|
+
const runConsultation = async () => {
|
|
149
|
+
let scoutDetails: AdvisorToolDetails["scout"];
|
|
150
|
+
const coalescedUpdate = createCoalescedUpdate(
|
|
151
|
+
(update: Parameters<NonNullable<typeof onUpdate>>[0]) =>
|
|
152
|
+
onUpdate?.(update),
|
|
153
|
+
ADVISOR_STREAM_UPDATE_INTERVAL_MS
|
|
154
|
+
);
|
|
155
|
+
const flushUpdate = () => {
|
|
156
|
+
const result = coalescedUpdate.flush();
|
|
157
|
+
if (result.failed) {
|
|
158
|
+
throw result.error;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const finishHerdrActivity = herdrActivity.start();
|
|
162
|
+
try {
|
|
163
|
+
const result = await requestAdvisor(
|
|
164
|
+
ctx,
|
|
165
|
+
resolveAdvisorRequest(params.question),
|
|
166
|
+
signal,
|
|
167
|
+
(t, tx) =>
|
|
168
|
+
coalescedUpdate.update({
|
|
169
|
+
content: [{ text: tx, type: "text" }],
|
|
170
|
+
details: {
|
|
171
|
+
advisor: advisorRef,
|
|
172
|
+
question: resolveAdvisorRequest(params.question),
|
|
173
|
+
scout: scoutDetails,
|
|
174
|
+
text: tx,
|
|
175
|
+
thinking: t,
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
178
|
+
"executor-requested",
|
|
179
|
+
// "none" is the model declining repository context for this call.
|
|
180
|
+
params.gitContext === "none" ? "off" : params.gitContext,
|
|
181
|
+
params.draft,
|
|
182
|
+
params.includeUntracked,
|
|
183
|
+
params.includeTrackedFiles,
|
|
184
|
+
(event) => {
|
|
185
|
+
scoutDetails = scoutDetailsFromEvent(event, scoutDetails);
|
|
186
|
+
coalescedUpdate.update({
|
|
187
|
+
content: [{ text: scoutDetails.text ?? "", type: "text" }],
|
|
188
|
+
details: {
|
|
189
|
+
advisor: advisorRef,
|
|
190
|
+
question: resolveAdvisorRequest(params.question),
|
|
191
|
+
scout: scoutDetails,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
_id
|
|
196
|
+
);
|
|
197
|
+
flushUpdate();
|
|
198
|
+
session.issueAdvice(
|
|
199
|
+
result.adviceId,
|
|
200
|
+
result.markdown,
|
|
201
|
+
result.trigger,
|
|
202
|
+
Boolean(result.draftBytes),
|
|
203
|
+
normalizedQuestion
|
|
204
|
+
);
|
|
205
|
+
session.recordInvocation({
|
|
206
|
+
cost: advisorUsageCost(result.usage),
|
|
207
|
+
executionEffect: "continued",
|
|
208
|
+
kind: "markdown",
|
|
209
|
+
model: result.model,
|
|
210
|
+
trigger: "executor-requested",
|
|
211
|
+
usage: result.usage,
|
|
212
|
+
});
|
|
213
|
+
const usage = snapshotAdvisorUsage(result.usage);
|
|
214
|
+
const piUsage = advisorUsageForPi(result.usage);
|
|
215
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
216
|
+
const details: AdvisorToolDetails = {
|
|
217
|
+
adviceId: result.adviceId,
|
|
218
|
+
advisor: result.model,
|
|
219
|
+
draftBytes: result.draftBytes,
|
|
220
|
+
preferenceBytes: result.preferenceBytes,
|
|
221
|
+
question: resolveAdvisorRequest(params.question),
|
|
222
|
+
scout: scoutDetails,
|
|
223
|
+
text: result.markdown,
|
|
224
|
+
thinking: result.thinkingText,
|
|
225
|
+
trackedBytes: result.trackedBytes,
|
|
226
|
+
untrackedBytes: result.untrackedBytes,
|
|
227
|
+
};
|
|
228
|
+
if (usage) {
|
|
229
|
+
details.usage = usage;
|
|
230
|
+
}
|
|
231
|
+
const response: AgentToolResult<AdvisorToolDetails> = {
|
|
232
|
+
content: [
|
|
233
|
+
{
|
|
234
|
+
text: `Advisor (${result.model})\n\n${result.markdown}`,
|
|
235
|
+
type: "text",
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
details,
|
|
239
|
+
};
|
|
240
|
+
if (piUsage) {
|
|
241
|
+
response.usage = piUsage;
|
|
242
|
+
}
|
|
243
|
+
return response;
|
|
244
|
+
} catch (error) {
|
|
245
|
+
// Publish the latest partial state before surfacing a provider or
|
|
246
|
+
// execution error. A failure from the UI sink must not replace the
|
|
247
|
+
// original error because this path is also used for provider failures.
|
|
248
|
+
coalescedUpdate.flush();
|
|
249
|
+
const message =
|
|
250
|
+
error instanceof Error ? error.message : String(error);
|
|
251
|
+
session.recordInvocation({
|
|
252
|
+
executionEffect: "continued",
|
|
253
|
+
failure: "provider-error",
|
|
254
|
+
kind: "markdown",
|
|
255
|
+
model: advisorRef,
|
|
256
|
+
trigger: "executor-requested",
|
|
257
|
+
});
|
|
258
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
259
|
+
notifyLocalFailure(ctx, message);
|
|
260
|
+
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
261
|
+
throw error;
|
|
262
|
+
} finally {
|
|
263
|
+
finishHerdrActivity();
|
|
264
|
+
coalescedUpdate.cancel();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
return runConsultation();
|
|
231
268
|
},
|
|
232
269
|
label: "Ask Advisor",
|
|
233
270
|
name: "ask_advisor",
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
isSimpleMode,
|
|
5
5
|
} from "../config/state.ts";
|
|
6
6
|
import { loadConfig } from "../config/storage.ts";
|
|
7
|
-
import { herdrAdvisorBlock } from "../herdr.ts";
|
|
8
7
|
import { reserveAdvisorCall } from "./gate-policy.ts";
|
|
9
8
|
import { handleAutomaticGate } from "./loop-gate.ts";
|
|
10
9
|
import { advisorModelAccess } from "./model-access.ts";
|
|
@@ -26,6 +25,8 @@ const modelAccessBlock = (
|
|
|
26
25
|
};
|
|
27
26
|
|
|
28
27
|
export const registerToolLifecycle = ({
|
|
28
|
+
herdrActivity,
|
|
29
|
+
herdrBlock,
|
|
29
30
|
pi,
|
|
30
31
|
reservedCalls,
|
|
31
32
|
runGate,
|
|
@@ -61,7 +62,7 @@ export const registerToolLifecycle = ({
|
|
|
61
62
|
pi.on("session_start", (_event, ctx) => {
|
|
62
63
|
session.resetTask();
|
|
63
64
|
reservedCalls.clear();
|
|
64
|
-
|
|
65
|
+
herdrBlock.clear();
|
|
65
66
|
if (ctx?.hasUI) {
|
|
66
67
|
ctx.ui.setStatus("advisor-usage", undefined);
|
|
67
68
|
}
|
|
@@ -114,13 +115,23 @@ export const registerToolLifecycle = ({
|
|
|
114
115
|
if (event.toolName === "ask_advisor") {
|
|
115
116
|
return reservation;
|
|
116
117
|
}
|
|
117
|
-
return handleAutomaticGate(
|
|
118
|
+
return handleAutomaticGate(
|
|
119
|
+
pi,
|
|
120
|
+
event,
|
|
121
|
+
ctx,
|
|
122
|
+
session,
|
|
123
|
+
runGate,
|
|
124
|
+
scoutStatus,
|
|
125
|
+
herdrActivity,
|
|
126
|
+
herdrBlock
|
|
127
|
+
);
|
|
118
128
|
});
|
|
119
129
|
|
|
120
130
|
pi.on("agent_settled", (_event, ctx) => {
|
|
121
131
|
// Any reservation still present never reached execute (for example because
|
|
122
132
|
// another handler blocked it or the turn was aborted).
|
|
123
133
|
reservedCalls.clear();
|
|
134
|
+
session.clearCallReservations();
|
|
124
135
|
if (isSimpleMode() || session.blocked || !advisorSessionSummaryRef) {
|
|
125
136
|
return;
|
|
126
137
|
}
|
|
@@ -132,8 +143,9 @@ export const registerToolLifecycle = ({
|
|
|
132
143
|
|
|
133
144
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
134
145
|
reservedCalls.clear();
|
|
146
|
+
session.clearCallReservations();
|
|
135
147
|
scoutStatus.clear(ctx);
|
|
136
|
-
|
|
148
|
+
herdrBlock.clear();
|
|
137
149
|
if (ctx?.hasUI) {
|
|
138
150
|
ctx.ui.setStatus("advisor-usage", undefined);
|
|
139
151
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
import { herdrAdvisorActivity, herdrAdvisorBlock } from "../herdr.ts";
|
|
3
4
|
import { appendOutcome } from "../outcomes.ts";
|
|
4
5
|
import type { AdvisorSessionState } from "../session-state.ts";
|
|
5
6
|
import { consultAdvisor, runAdvisorGate } from "./consultation.ts";
|
|
@@ -25,6 +26,8 @@ export const registerAdvisorTool = (
|
|
|
25
26
|
const registration: ToolRegistrationContext = {
|
|
26
27
|
appendOutcome: dependencies.appendOutcome ?? appendOutcome,
|
|
27
28
|
consult: dependencies.consult ?? consultAdvisor,
|
|
29
|
+
herdrActivity: dependencies.herdrActivity ?? herdrAdvisorActivity,
|
|
30
|
+
herdrBlock: dependencies.herdrBlock ?? herdrAdvisorBlock,
|
|
28
31
|
pi,
|
|
29
32
|
reservedCalls: new Set<string>(),
|
|
30
33
|
runGate: dependencies.runGate ?? runAdvisorGate,
|
|
@@ -40,6 +43,7 @@ export const registerAdvisorTool = (
|
|
|
40
43
|
const turnGate: JevTurnGateRegistration = {
|
|
41
44
|
activeTools: () => pi.getActiveTools(),
|
|
42
45
|
consult: registration.consult,
|
|
46
|
+
herdrActivity: registration.herdrActivity,
|
|
43
47
|
send: (message) => pi.sendMessage(message, { deliverAs: "steer" }),
|
|
44
48
|
session,
|
|
45
49
|
};
|
package/src/tools/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
import type { HerdrAdvisorActivityScope, HerdrAdvisorBlock } from "../herdr.ts";
|
|
3
4
|
import type { appendOutcome } from "../outcomes.ts";
|
|
4
5
|
import type { ScoutOutcome } from "../scout.ts";
|
|
5
6
|
import type {
|
|
@@ -112,6 +113,8 @@ export interface AdvisorToolContext {
|
|
|
112
113
|
export interface ToolRegistrationContext {
|
|
113
114
|
appendOutcome: typeof appendOutcome;
|
|
114
115
|
consult: typeof consultAdvisor;
|
|
116
|
+
herdrActivity: HerdrAdvisorActivityScope;
|
|
117
|
+
herdrBlock: HerdrAdvisorBlock;
|
|
115
118
|
pi: ExtensionAPI;
|
|
116
119
|
reservedCalls: Set<string>;
|
|
117
120
|
runGate: typeof runAdvisorGate;
|
|
@@ -123,6 +126,8 @@ export interface ToolRegistrationContext {
|
|
|
123
126
|
export interface ToolRegistrationDependencies {
|
|
124
127
|
appendOutcome?: typeof appendOutcome;
|
|
125
128
|
consult?: typeof consultAdvisor;
|
|
129
|
+
herdrActivity?: HerdrAdvisorActivityScope;
|
|
130
|
+
herdrBlock?: HerdrAdvisorBlock;
|
|
126
131
|
runGate?: typeof runAdvisorGate;
|
|
127
132
|
screen?: typeof screenConsultation;
|
|
128
133
|
statusManager?: ScoutStatusManager;
|
|
@@ -214,7 +214,9 @@ export class JevSetupSubmenu implements Component, Focusable {
|
|
|
214
214
|
|
|
215
215
|
private actions(): SetupAction[] {
|
|
216
216
|
if (!this.credentials) {
|
|
217
|
-
return
|
|
217
|
+
return this.options.currentValue === "On"
|
|
218
|
+
? ["enter-key", "disable", "done"]
|
|
219
|
+
: ["enter-key", "done"];
|
|
218
220
|
}
|
|
219
221
|
const enabled = this.options.currentValue === "On";
|
|
220
222
|
const actions: SetupAction[] = [enabled ? "verify-again" : "verify-enable"];
|
|
@@ -277,8 +279,12 @@ export class JevSetupSubmenu implements Component, Focusable {
|
|
|
277
279
|
return;
|
|
278
280
|
}
|
|
279
281
|
const result = await clear();
|
|
280
|
-
this.credentials = undefined;
|
|
281
282
|
this.notice = result.message;
|
|
283
|
+
if (!result.ok) {
|
|
284
|
+
this.options.tui.requestRender();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
this.credentials = undefined;
|
|
282
288
|
this.options.done("Off");
|
|
283
289
|
}
|
|
284
290
|
|