pi-subagents 0.67.0 → 0.68.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 +70 -0
- package/README.md +1 -1
- package/docs/agents.md +37 -12
- package/docs/configuration.md +61 -19
- package/docs/extension-api.md +5 -1
- package/docs/missions.md +2 -2
- package/docs/models.md +11 -79
- package/docs/observability.md +18 -8
- package/docs/standalone-background.md +13 -3
- package/docs/tool-reference.md +15 -12
- package/docs/watchdog.md +10 -12
- package/docs/workflows.md +11 -1
- package/index.ts +5 -2
- package/package.json +4 -2
- package/runner-peer-loader.mjs +24 -0
- package/runner-peer-preload.mjs +25 -11
- package/skills/pi-subagents/SKILL.md +18 -21
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +4 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +16 -12
- package/skills/pi-subagents/references/review-and-validation.md +3 -3
- package/src/agents/agent-management.ts +57 -58
- package/src/agents/agent-serializer.ts +4 -3
- package/src/agents/agents.ts +184 -71
- package/src/agents/chain-serializer.ts +5 -0
- package/src/agents/runtime-agent-registry.ts +7 -6
- package/src/api/preflight.ts +20 -16
- package/src/api/required-child-extensions.ts +6 -0
- package/src/extension/config.ts +10 -37
- package/src/extension/fanout-child.ts +3 -0
- package/src/extension/herdr-pi-bridge.ts +160 -0
- package/src/extension/index.ts +42 -31
- package/src/extension/public-execution.ts +3 -3
- package/src/extension/schemas.ts +16 -5
- package/src/extension/tool-description.ts +8 -7
- package/src/intercom/native-supervisor-channel.ts +22 -18
- package/src/policy/authority.ts +4 -0
- package/src/profiles/profiles.ts +12 -6
- package/src/runs/background/active-run-index.ts +17 -1
- package/src/runs/background/async-execution.ts +309 -126
- package/src/runs/background/async-job-tracker.ts +8 -6
- package/src/runs/background/async-resume.ts +13 -4
- package/src/runs/background/async-status.ts +15 -4
- package/src/runs/background/auto-drain.ts +20 -10
- package/src/runs/background/binary-bootstrap.ts +5 -0
- package/src/runs/background/chain-append.ts +1 -1
- package/src/runs/background/chain-root-attachment.ts +14 -33
- package/src/runs/background/notify.ts +74 -6
- package/src/runs/background/result-files.ts +8 -4
- package/src/runs/background/result-watcher.ts +19 -2
- package/src/runs/background/run-child-session.ts +20 -29
- package/src/runs/background/runner-aliases.ts +4 -33
- package/src/runs/background/runner-child-launch.ts +4 -1
- package/src/runs/background/runner-child-sessions.ts +2 -2
- package/src/runs/background/runner-http-dispatcher.ts +119 -0
- package/src/runs/background/scheduled-runs.ts +11 -5
- package/src/runs/background/stale-run-reconciler.ts +35 -11
- package/src/runs/background/subagent-runner.ts +396 -275
- package/src/runs/background/subagent-wait.ts +128 -23
- package/src/runs/background/wait-completions.ts +75 -27
- package/src/runs/background/wait-subscriptions.ts +9 -3
- package/src/runs/background/wait-tool.ts +4 -2
- package/src/runs/foreground/async-stop-action.ts +93 -3
- package/src/runs/foreground/execution.ts +91 -218
- package/src/runs/foreground/foreground-history.ts +2 -1
- package/src/runs/foreground/subagent-executor.ts +266 -80
- package/src/runs/shared/acceptance.ts +34 -10
- package/src/runs/shared/async-status-projection.ts +123 -33
- package/src/runs/shared/child-launch-plan.ts +15 -3
- package/src/runs/shared/child-launch.ts +19 -6
- package/src/runs/shared/child-runtime-config.ts +5 -0
- package/src/runs/shared/child-session.ts +94 -50
- package/src/runs/shared/child-tool-plan.ts +28 -16
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/external-cli-contract.ts +11 -1
- package/src/runs/shared/external-cli-preflight.ts +6 -2
- package/src/runs/shared/herdr-connection.ts +134 -0
- package/src/runs/shared/herdr-external-adapters.ts +169 -0
- package/src/runs/shared/herdr-machine.ts +279 -0
- package/src/runs/shared/herdr-pi-protocol.ts +59 -0
- package/src/runs/shared/herdr-placed-run.ts +263 -0
- package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
- package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -237
- package/src/runs/shared/model-scope.ts +1 -1
- package/src/runs/shared/nested-events.ts +11 -2
- package/src/runs/shared/parallel-utils.ts +7 -2
- package/src/runs/shared/pi-spawn.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +4 -2
- package/src/runs/shared/worktree-setup-command.ts +27 -4
- package/src/runs/shared/worktree.ts +3 -3
- package/src/shared/child-cache-retention.ts +43 -0
- package/src/shared/launch-contract.ts +6 -9
- package/src/shared/pruned-fork.ts +1 -1
- package/src/shared/required-child-extensions.ts +81 -0
- package/src/shared/settings.ts +5 -2
- package/src/shared/shortcuts.ts +0 -4
- package/src/shared/types.ts +70 -29
- package/src/slash/slash-commands.ts +0 -6
- package/src/slash/subagents-admin.ts +13 -9
- package/src/tui/render.ts +20 -10
- package/src/watchdog/child-status.ts +28 -36
- package/src/watchdog/lsp-diagnostics.ts +1 -1
- package/src/watchdog/model-selection.ts +1 -1
- package/src/watchdog/register-child.ts +10 -3
- package/src/watchdog/register-main.ts +20 -20
- package/src/watchdog/render.ts +1 -1
- package/src/watchdog/review.ts +14 -30
- package/src/watchdog/rules.ts +1 -1
- package/src/watchdog/runtime.ts +23 -12
- package/src/watchdog/settings.ts +3 -6
- package/src/watchdog/types.ts +3 -5
- package/src/watchdog/warning-format.ts +1 -1
- package/src/workflows/scripted-workflow.ts +42 -3
- package/src/workflows/workflow-receipt.ts +21 -3
- package/src/workflows/workflow-resources.ts +13 -2
- package/src/runs/shared/model-exclusions.ts +0 -374
- package/src/runs/shared/readonly-model-continuation.ts +0 -69
- package/src/runs/shared/readonly-session-evidence.ts +0 -307
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChildWatchdogProgress, ChildWatchdogWarningSummary } from "../shared/types.ts";
|
|
2
|
-
import {
|
|
2
|
+
import { WATCHDOG_WARNING_CATEGORIES, WATCHDOG_WARNING_IMPORTANCES, type ResolvedWatchdogConfig, type WatchdogCadenceConfig, type WatchdogLspConfig } from "./types.ts";
|
|
3
3
|
|
|
4
4
|
export const CHILD_WATCHDOG_WARNING_LIMIT = 20;
|
|
5
5
|
|
|
@@ -16,7 +16,6 @@ export interface ChildWatchdogConfig {
|
|
|
16
16
|
agentEndTimeoutMs: number;
|
|
17
17
|
maxWarnings: number | null;
|
|
18
18
|
model?: string;
|
|
19
|
-
fallbackModels?: string[];
|
|
20
19
|
thinking?: string | false;
|
|
21
20
|
lsp: WatchdogLspConfig;
|
|
22
21
|
stalemateRepeats: number;
|
|
@@ -34,6 +33,7 @@ export interface ChildWatchdogStatusEvent {
|
|
|
34
33
|
phase: ChildWatchdogPhase;
|
|
35
34
|
ts: number;
|
|
36
35
|
reason?: string;
|
|
36
|
+
warning?: ChildWatchdogWarningSummary;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export type ChildWatchdogStateSnapshot = ChildWatchdogProgress;
|
|
@@ -48,7 +48,6 @@ export function resolveChildWatchdogConfig(input: {
|
|
|
48
48
|
const enabled = input.config.enabled && (override?.enabled ?? input.config.children.enabled);
|
|
49
49
|
if (!enabled) return undefined;
|
|
50
50
|
const model = override?.model ?? input.config.children.model;
|
|
51
|
-
const fallbackModels = override?.fallbackModels ?? input.config.children.fallbackModels;
|
|
52
51
|
const thinking = override?.thinking ?? input.config.children.thinking;
|
|
53
52
|
const cadence = override?.cadence ?? input.config.children.cadence ?? input.config.cadence;
|
|
54
53
|
return {
|
|
@@ -59,7 +58,6 @@ export function resolveChildWatchdogConfig(input: {
|
|
|
59
58
|
agentEndTimeoutMs: input.config.agentEndTimeoutMs,
|
|
60
59
|
maxWarnings: input.config.maxWarnings,
|
|
61
60
|
...(model ? { model } : {}),
|
|
62
|
-
...(fallbackModels !== undefined ? { fallbackModels: [...fallbackModels] } : {}),
|
|
63
61
|
...(thinking !== undefined ? { thinking } : {}),
|
|
64
62
|
lsp: { ...input.config.lsp },
|
|
65
63
|
stalemateRepeats: input.config.stalemateRepeats,
|
|
@@ -130,6 +128,9 @@ function childConfigLsp(value: unknown): WatchdogLspConfig {
|
|
|
130
128
|
export function decodeChildWatchdogConfig(raw: string | undefined): ChildWatchdogConfig | undefined {
|
|
131
129
|
if (!raw) return undefined;
|
|
132
130
|
const parsed = childConfigObject(JSON.parse(raw), "root");
|
|
131
|
+
if (Object.hasOwn(parsed, "fallbackModels")) {
|
|
132
|
+
throw new Error("Invalid child watchdog config: fallbackModels was removed; configure one model instead.");
|
|
133
|
+
}
|
|
133
134
|
if (parsed.enabled === false) return undefined;
|
|
134
135
|
if ("enabled" in parsed && parsed.enabled !== true) throw new Error("Invalid child watchdog config: enabled must be true or false.");
|
|
135
136
|
const thinking = parsed.thinking;
|
|
@@ -140,10 +141,6 @@ export function decodeChildWatchdogConfig(raw: string | undefined): ChildWatchdo
|
|
|
140
141
|
const agent = childConfigOptionalString(parsed, "agent");
|
|
141
142
|
const childIndex = childConfigOptionalIndex(parsed, "childIndex");
|
|
142
143
|
const model = childConfigOptionalString(parsed, "model");
|
|
143
|
-
const fallbackModels = parsed.fallbackModels;
|
|
144
|
-
if (fallbackModels !== undefined && (!Array.isArray(fallbackModels) || fallbackModels.some((value) => typeof value !== "string" || !value.trim()))) {
|
|
145
|
-
throw new Error("Invalid child watchdog config: fallbackModels must be an array of non-empty strings.");
|
|
146
|
-
}
|
|
147
144
|
return {
|
|
148
145
|
...(runId ? { runId } : {}),
|
|
149
146
|
...(agent ? { agent } : {}),
|
|
@@ -152,7 +149,6 @@ export function decodeChildWatchdogConfig(raw: string | undefined): ChildWatchdo
|
|
|
152
149
|
agentEndTimeoutMs: childConfigPositiveInteger(parsed, "agentEndTimeoutMs"),
|
|
153
150
|
maxWarnings: childConfigNullableNonNegativeInteger(parsed, "maxWarnings"),
|
|
154
151
|
...(model ? { model } : {}),
|
|
155
|
-
...(fallbackModels !== undefined ? { fallbackModels: (fallbackModels as string[]).map((value) => value.trim()) } : {}),
|
|
156
152
|
...(thinking !== undefined ? { thinking: thinking as string | false } : {}),
|
|
157
153
|
lsp: childConfigLsp(parsed.lsp),
|
|
158
154
|
stalemateRepeats: childConfigPositiveInteger(parsed, "stalemateRepeats"),
|
|
@@ -163,6 +159,15 @@ export function decodeChildWatchdogConfig(raw: string | undefined): ChildWatchdo
|
|
|
163
159
|
export function isChildWatchdogStatusEvent(value: unknown): value is ChildWatchdogStatusEvent {
|
|
164
160
|
if (!value || typeof value !== "object") return false;
|
|
165
161
|
const event = value as Partial<ChildWatchdogStatusEvent>;
|
|
162
|
+
const warningValue = event.warning;
|
|
163
|
+
const warning = warningValue as Partial<ChildWatchdogWarningSummary>;
|
|
164
|
+
const validWarning = warningValue === undefined || (warningValue !== null && typeof warningValue === "object" && !Array.isArray(warningValue)
|
|
165
|
+
&& (warning.severity === "concern" || warning.severity === "blocker")
|
|
166
|
+
&& typeof warning.importance === "string" && (WATCHDOG_WARNING_IMPORTANCES as readonly string[]).includes(warning.importance)
|
|
167
|
+
&& typeof warning.category === "string" && (WATCHDOG_WARNING_CATEGORIES as readonly string[]).includes(warning.category)
|
|
168
|
+
&& typeof warning.summary === "string" && typeof warning.evidence === "string" && typeof warning.recommendedAction === "string"
|
|
169
|
+
&& typeof warning.addressed === "boolean" && typeof warning.stalemate === "boolean"
|
|
170
|
+
&& (warning.displayedAt === undefined || typeof warning.displayedAt === "string"));
|
|
166
171
|
return event.type === CHILD_WATCHDOG_STATUS_EVENT
|
|
167
172
|
&& typeof event.seq === "number"
|
|
168
173
|
&& Number.isInteger(event.seq)
|
|
@@ -170,7 +175,8 @@ export function isChildWatchdogStatusEvent(value: unknown): value is ChildWatchd
|
|
|
170
175
|
&& typeof event.ts === "number"
|
|
171
176
|
&& Number.isFinite(event.ts)
|
|
172
177
|
&& typeof event.phase === "string"
|
|
173
|
-
&& (CHILD_WATCHDOG_PHASES as readonly string[]).includes(event.phase)
|
|
178
|
+
&& (CHILD_WATCHDOG_PHASES as readonly string[]).includes(event.phase)
|
|
179
|
+
&& validWarning;
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
export function childWatchdogIsActive(snapshot: ChildWatchdogStateSnapshot | undefined): boolean {
|
|
@@ -190,40 +196,20 @@ export function acceptChildWatchdogEvent(input: {
|
|
|
190
196
|
const eventIndex = input.event.childIndex ?? input.event.stepIndex;
|
|
191
197
|
if (input.childIndex !== undefined && eventIndex !== input.childIndex) return undefined;
|
|
192
198
|
if (input.current && input.event.seq <= input.current.seq) return undefined;
|
|
199
|
+
const warnings = input.event.warning
|
|
200
|
+
? [...(input.current?.warnings ?? []), input.event.warning].slice(-CHILD_WATCHDOG_WARNING_LIMIT)
|
|
201
|
+
: input.current?.warnings;
|
|
193
202
|
return {
|
|
194
203
|
phase: input.event.phase,
|
|
195
204
|
seq: input.event.seq,
|
|
196
205
|
lastUpdate: input.event.ts,
|
|
197
206
|
...(input.event.reason ? { reason: input.event.reason } : {}),
|
|
198
|
-
...(
|
|
207
|
+
...(warnings?.length ? { warnings } : {}),
|
|
199
208
|
};
|
|
200
209
|
}
|
|
201
210
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (candidate?.role !== "custom" || candidate.customType !== SUBAGENT_WATCHDOG_WARNING_TYPE) return undefined;
|
|
205
|
-
const details = candidate.details ?? {};
|
|
206
|
-
if (details.severity !== "concern" && details.severity !== "blocker") return undefined;
|
|
207
|
-
if (typeof details.summary !== "string" || typeof details.evidence !== "string" || typeof details.recommendedAction !== "string") return undefined;
|
|
208
|
-
return {
|
|
209
|
-
severity: details.severity,
|
|
210
|
-
category: details.category as WatchdogCategory,
|
|
211
|
-
summary: details.summary,
|
|
212
|
-
evidence: details.evidence,
|
|
213
|
-
recommendedAction: details.recommendedAction,
|
|
214
|
-
...(typeof details.displayedAt === "string" ? { displayedAt: details.displayedAt } : {}),
|
|
215
|
-
addressed: false,
|
|
216
|
-
stalemate: details.state === "stalemate",
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/** A watchdog warning message is appended; an assistant turn marks earlier warnings addressed. Undefined when unchanged. */
|
|
221
|
-
export function applyChildWatchdogMessage(current: ChildWatchdogStateSnapshot | undefined, message: unknown, now = Date.now()): ChildWatchdogStateSnapshot | undefined {
|
|
222
|
-
const warning = childWatchdogWarningFromMessage(message);
|
|
223
|
-
if (warning) {
|
|
224
|
-
const warnings = [...(current?.warnings ?? []), warning].slice(-CHILD_WATCHDOG_WARNING_LIMIT);
|
|
225
|
-
return current ? { ...current, warnings } : { phase: "idle", seq: 0, lastUpdate: now, warnings };
|
|
226
|
-
}
|
|
211
|
+
/** An assistant turn marks earlier warnings addressed. Undefined when unchanged. */
|
|
212
|
+
export function applyChildWatchdogMessage(current: ChildWatchdogStateSnapshot | undefined, message: unknown): ChildWatchdogStateSnapshot | undefined {
|
|
227
213
|
if ((message as { role?: unknown } | undefined)?.role !== "assistant" || !current?.warnings?.some((entry) => !entry.addressed)) return undefined;
|
|
228
214
|
return { ...current, warnings: current.warnings.map((entry) => entry.addressed ? entry : { ...entry, addressed: true }) };
|
|
229
215
|
}
|
|
@@ -231,3 +217,9 @@ export function applyChildWatchdogMessage(current: ChildWatchdogStateSnapshot |
|
|
|
231
217
|
export function unresolvedChildWatchdogBlockers(progress: Pick<ChildWatchdogProgress, "warnings"> | undefined): ChildWatchdogWarningSummary[] {
|
|
232
218
|
return (progress?.warnings ?? []).filter((warning) => warning.severity === "blocker" && (!warning.addressed || warning.stalemate));
|
|
233
219
|
}
|
|
220
|
+
|
|
221
|
+
export function childWatchdogProgressForModel(progress: ChildWatchdogProgress | undefined): ChildWatchdogProgress | undefined {
|
|
222
|
+
if (!progress) return undefined;
|
|
223
|
+
const warnings = (progress.warnings ?? []).filter((warning) => warning.importance === "high");
|
|
224
|
+
return { ...progress, warnings: warnings.length ? warnings : undefined };
|
|
225
|
+
}
|
|
@@ -216,7 +216,7 @@ export function watchdogWarningFromLspDiagnostics(result: WatchdogLspResult): Wa
|
|
|
216
216
|
return {
|
|
217
217
|
severity,
|
|
218
218
|
category: "correctness",
|
|
219
|
-
|
|
219
|
+
importance: "high",
|
|
220
220
|
source: "lsp",
|
|
221
221
|
summary: `LSP found ${count} ${kind}${count === 1 ? "" : "s"} in changed ${count === 1 ? "file" : "files"}.`,
|
|
222
222
|
evidence: evidence || formatDiagnostic(primary),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { normalizeModelSegment, resolveModelCandidate } from "../runs/shared/model-
|
|
2
|
+
import { normalizeModelSegment, resolveModelCandidate } from "../runs/shared/model-resolution.ts";
|
|
3
3
|
import type { WatchdogEndpointConfig } from "./types.ts";
|
|
4
4
|
import {
|
|
5
5
|
getSupportedThinkingLevels,
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
type ChildWatchdogPhase,
|
|
11
11
|
type ChildWatchdogStatusEvent,
|
|
12
12
|
} from "./child-status.ts";
|
|
13
|
-
import type {
|
|
13
|
+
import type { ChildWatchdogWarningSummary } from "../shared/types.ts";
|
|
14
|
+
import { SUBAGENT_WATCHDOG_WARNING_TYPE, type ResolvedWatchdogConfig, type WatchdogWarningDetails } from "./types.ts";
|
|
14
15
|
|
|
15
16
|
export function childResolvedConfig(config: ChildWatchdogConfig): ResolvedWatchdogConfig {
|
|
16
17
|
return {
|
|
@@ -21,7 +22,6 @@ export function childResolvedConfig(config: ChildWatchdogConfig): ResolvedWatchd
|
|
|
21
22
|
main: {
|
|
22
23
|
enabled: true,
|
|
23
24
|
...(config.model ? { model: config.model } : {}),
|
|
24
|
-
...(config.fallbackModels !== undefined ? { fallbackModels: [...config.fallbackModels] } : {}),
|
|
25
25
|
...(config.thinking !== undefined ? { thinking: config.thinking } : {}),
|
|
26
26
|
},
|
|
27
27
|
stalemateRepeats: config.stalemateRepeats,
|
|
@@ -58,7 +58,7 @@ export function registerChildWatchdog(
|
|
|
58
58
|
let currentContext: ExtensionContext | undefined;
|
|
59
59
|
let diffBaseline: WatchdogDiffBaseline | undefined;
|
|
60
60
|
let seq = 0;
|
|
61
|
-
const emitStatus = (phase: ChildWatchdogPhase, reason?: string): void => {
|
|
61
|
+
const emitStatus = (phase: ChildWatchdogPhase, reason?: string, warning?: ChildWatchdogWarningSummary): void => {
|
|
62
62
|
writeStatus({
|
|
63
63
|
type: CHILD_WATCHDOG_STATUS_EVENT,
|
|
64
64
|
...(childConfig.runId ? { runId: childConfig.runId } : {}),
|
|
@@ -68,6 +68,7 @@ export function registerChildWatchdog(
|
|
|
68
68
|
phase,
|
|
69
69
|
ts: Date.now(),
|
|
70
70
|
...(reason ? { reason } : {}),
|
|
71
|
+
...(warning ? { warning } : {}),
|
|
71
72
|
});
|
|
72
73
|
};
|
|
73
74
|
const resolved = childResolvedConfig(childConfig);
|
|
@@ -78,8 +79,14 @@ export function registerChildWatchdog(
|
|
|
78
79
|
reviewChangesOnly: true,
|
|
79
80
|
displayWarning: (details, options) => {
|
|
80
81
|
const childDetails = childWarningDetails(details, childConfig);
|
|
82
|
+
emitStatus("reviewing", undefined, { severity: childDetails.severity, importance: childDetails.importance, category: childDetails.category, summary: childDetails.summary, evidence: childDetails.evidence, recommendedAction: childDetails.recommendedAction, ...(childDetails.displayedAt ? { displayedAt: childDetails.displayedAt } : {}), addressed: false, stalemate: childDetails.state === "stalemate" });
|
|
81
83
|
pi.sendMessage(createWatchdogWarningMessage(childDetails, { display: true, details: childDetails }), options);
|
|
82
84
|
},
|
|
85
|
+
displayUserWarning: (details) => {
|
|
86
|
+
const childDetails = childWarningDetails(details, childConfig);
|
|
87
|
+
emitStatus("reviewing", undefined, { severity: childDetails.severity, importance: childDetails.importance, category: childDetails.category, summary: childDetails.summary, evidence: childDetails.evidence, recommendedAction: childDetails.recommendedAction, ...(childDetails.displayedAt ? { displayedAt: childDetails.displayedAt } : {}), addressed: false, stalemate: childDetails.state === "stalemate" });
|
|
88
|
+
pi.appendEntry(SUBAGENT_WATCHDOG_WARNING_TYPE, childDetails);
|
|
89
|
+
},
|
|
83
90
|
});
|
|
84
91
|
const rememberContext = (ctx: ExtensionContext) => {
|
|
85
92
|
currentContext = ctx;
|
|
@@ -63,16 +63,11 @@ function mainThinkingLine(snapshot: ReturnType<MainWatchdogRuntime["getSnapshot"
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function mainModelLine(snapshot: ReturnType<MainWatchdogRuntime["getSnapshot"]>, ctx: ExtensionContext): string {
|
|
66
|
-
const fallbacks = fallbackLine(snapshot.config.main.fallbackModels);
|
|
67
66
|
if (snapshot.config.main.model) {
|
|
68
67
|
const source = snapshot.sessionModelOverride?.model ? "session override" : "configured";
|
|
69
|
-
return `Main model: ${splitKnownThinkingSuffix(snapshot.config.main.model).baseModel} (${source})
|
|
68
|
+
return `Main model: ${splitKnownThinkingSuffix(snapshot.config.main.model).baseModel} (${source})`;
|
|
70
69
|
}
|
|
71
|
-
return `Main model: ${currentSessionModelLine(ctx)}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function fallbackLine(models: string[] | undefined): string {
|
|
75
|
-
return models === undefined ? "" : ` · fallbacks ${models.length ? models.join(" → ") : "none"}`;
|
|
70
|
+
return `Main model: ${currentSessionModelLine(ctx)}`;
|
|
76
71
|
}
|
|
77
72
|
|
|
78
73
|
function childrenLine(snapshot: ReturnType<MainWatchdogRuntime["getSnapshot"]>): string {
|
|
@@ -85,12 +80,11 @@ function childrenLine(snapshot: ReturnType<MainWatchdogRuntime["getSnapshot"]>):
|
|
|
85
80
|
const bits = [agent];
|
|
86
81
|
if (override.enabled !== undefined) bits.push(boolLabel(override.enabled));
|
|
87
82
|
if (override.model) bits.push(splitKnownThinkingSuffix(override.model).baseModel);
|
|
88
|
-
if (override.fallbackModels !== undefined) bits.push(fallbackLine(override.fallbackModels));
|
|
89
83
|
if (override.thinking !== undefined) bits.push(`thinking ${override.thinking === false ? "off" : override.thinking}`);
|
|
90
84
|
return bits.join(" ");
|
|
91
85
|
}).join("; ")}`
|
|
92
86
|
: "";
|
|
93
|
-
return `Children: ${boolLabel(snapshot.config.enabled && children.enabled)} · model ${model}
|
|
87
|
+
return `Children: ${boolLabel(snapshot.config.enabled && children.enabled)} · model ${model} · thinking ${thinking}${overrideText}`;
|
|
94
88
|
}
|
|
95
89
|
|
|
96
90
|
function recommendationLine(snapshot: ReturnType<MainWatchdogRuntime["getSnapshot"]>, ctx: ExtensionContext): string {
|
|
@@ -160,10 +154,10 @@ export function buildWatchdogStatus(snapshot: ReturnType<MainWatchdogRuntime["ge
|
|
|
160
154
|
return lines.join("\n");
|
|
161
155
|
}
|
|
162
156
|
|
|
163
|
-
function parseTestCommand(input: string): { severity: "concern" | "blocker"; text: string } | undefined {
|
|
164
|
-
const match = input.match(/^test\s+(concern|blocker)\s+([\s\S]+)$/);
|
|
157
|
+
function parseTestCommand(input: string): { severity: "concern" | "blocker"; importance: "low" | "medium" | "high"; text: string } | undefined {
|
|
158
|
+
const match = input.match(/^test\s+(concern|blocker)\s+(low|medium|high)\s+([\s\S]+)$/);
|
|
165
159
|
if (!match) return undefined;
|
|
166
|
-
return { severity: match[1] as "concern" | "blocker",
|
|
160
|
+
return { severity: match[1] as "concern" | "blocker", importance: match[2] as "low" | "medium" | "high", text: match[3]!.trim() };
|
|
167
161
|
}
|
|
168
162
|
|
|
169
163
|
function formatThinking(value: ThinkingLevel | false | undefined): string {
|
|
@@ -231,11 +225,11 @@ function buildCheckText(runtime: MainWatchdogRuntime, ctx: ExtensionCommandConte
|
|
|
231
225
|
return lines.join("\n");
|
|
232
226
|
}
|
|
233
227
|
|
|
234
|
-
function createTestWarning(severity: "concern" | "blocker", text: string): WatchdogWarning {
|
|
228
|
+
function createTestWarning(severity: "concern" | "blocker", importance: "low" | "medium" | "high", text: string): WatchdogWarning {
|
|
235
229
|
return {
|
|
236
230
|
severity,
|
|
237
231
|
category: "other",
|
|
238
|
-
|
|
232
|
+
importance,
|
|
239
233
|
source: "main",
|
|
240
234
|
state: "displayed",
|
|
241
235
|
summary: text,
|
|
@@ -366,15 +360,14 @@ async function handleWatchdogCommand(
|
|
|
366
360
|
const test = parseTestCommand(input);
|
|
367
361
|
if (test) {
|
|
368
362
|
if (!test.text) {
|
|
369
|
-
ctx.ui.notify("Usage: /subagents-watchdog test concern|blocker <text>", "error");
|
|
363
|
+
ctx.ui.notify("Usage: /subagents-watchdog test concern|blocker low|medium|high <text>", "error");
|
|
370
364
|
return;
|
|
371
365
|
}
|
|
372
|
-
const warning = createTestWarning(test.severity, test.text);
|
|
373
|
-
|
|
374
|
-
pi.sendMessage(createWatchdogWarningMessage(details, { display: true, details }));
|
|
366
|
+
const warning = createTestWarning(test.severity, test.importance, test.text);
|
|
367
|
+
runtime.displayRecordedWarning(warning);
|
|
375
368
|
return;
|
|
376
369
|
}
|
|
377
|
-
ctx.ui.notify(`Usage: /subagents-watchdog [status|on|off|session on|session off|recommend-model|model recommended|model <provider/model[:thinking]>|model inherit|thinking ${THINKING_LEVELS.join("|")}|thinking inherit|session model recommended|check|test concern
|
|
370
|
+
ctx.ui.notify(`Usage: /subagents-watchdog [status|on|off|session on|session off|recommend-model|model recommended|model <provider/model[:thinking]>|model inherit|thinking ${THINKING_LEVELS.join("|")}|thinking inherit|session model recommended|check|test concern|blocker low|medium|high <text>]`, "error");
|
|
378
371
|
}
|
|
379
372
|
|
|
380
373
|
export function registerMainWatchdog(pi: ExtensionAPI, options: RegisterMainWatchdogOptions = {}): MainWatchdogRuntime {
|
|
@@ -388,6 +381,7 @@ export function registerMainWatchdog(pi: ExtensionAPI, options: RegisterMainWatc
|
|
|
388
381
|
reviewDescription: options.review ? "injected seam" : "real model review",
|
|
389
382
|
reviewChangesOnly: true,
|
|
390
383
|
displayWarning: (details, options) => pi.sendMessage(createWatchdogWarningMessage(details, { display: true, details }), options),
|
|
384
|
+
displayUserWarning: (details) => pi.appendEntry(SUBAGENT_WATCHDOG_WARNING_TYPE, details),
|
|
391
385
|
displayClarification: (content) => pi.sendMessage({ customType: "subagent_watchdog_clarification", content, display: true }, { deliverAs: "steer", triggerTurn: true }),
|
|
392
386
|
});
|
|
393
387
|
|
|
@@ -401,6 +395,12 @@ export function registerMainWatchdog(pi: ExtensionAPI, options: RegisterMainWatc
|
|
|
401
395
|
}
|
|
402
396
|
return renderWatchdogWarning(details, renderOptions, theme);
|
|
403
397
|
});
|
|
398
|
+
pi.registerEntryRenderer<WatchdogWarningDetails>(SUBAGENT_WATCHDOG_WARNING_TYPE, (entry, renderOptions, theme) => {
|
|
399
|
+
const details = entry.data as WatchdogWarningDetails | undefined;
|
|
400
|
+
return details?.summary && details.evidence && details.recommendedAction
|
|
401
|
+
? renderWatchdogWarning(details, renderOptions, theme)
|
|
402
|
+
: undefined;
|
|
403
|
+
});
|
|
404
404
|
|
|
405
405
|
pi.registerCommand("subagents-watchdog", {
|
|
406
406
|
description: "Show or toggle the default-off subagent watchdog",
|
|
@@ -435,7 +435,7 @@ export function registerMainWatchdog(pi: ExtensionAPI, options: RegisterMainWatc
|
|
|
435
435
|
});
|
|
436
436
|
pi.on("session_before_switch", () => runtime.reset("session switch", { clearReviewInputSignature: true, clearLspLedger: true, clearScope: true }));
|
|
437
437
|
pi.on("session_before_fork", () => runtime.reset("session fork", { clearReviewInputSignature: true, clearLspLedger: true, clearScope: true }));
|
|
438
|
-
pi.on("session_compact", () => runtime.reset("session compact", {
|
|
438
|
+
pi.on("session_compact", () => runtime.reset("session compact", { clearActivity: true }));
|
|
439
439
|
pi.on("session_shutdown", () => {
|
|
440
440
|
currentContext = undefined;
|
|
441
441
|
runtime.dispose();
|
package/src/watchdog/render.ts
CHANGED
|
@@ -26,7 +26,7 @@ export function formatWatchdogWarningRenderText(warning: WatchdogWarningDetails)
|
|
|
26
26
|
`Subagent watchdog ${subject}${labels.length ? ` (${labels.join(", ")})` : ""}: ${warning.summary}`,
|
|
27
27
|
`Evidence: ${warning.evidence}`,
|
|
28
28
|
`Recommended action: ${warning.recommendedAction}`,
|
|
29
|
-
`Category: ${titleCase(warning.category)} · Source: ${warning.source}${warning.agent ? ` · Agent: ${warning.agent}` : ""}${warning.runId ? ` · Run: ${warning.runId}` : ""}`,
|
|
29
|
+
`Importance: ${titleCase(warning.importance)} · Category: ${titleCase(warning.category)} · Source: ${warning.source}${warning.agent ? ` · Agent: ${warning.agent}` : ""}${warning.runId ? ` · Run: ${warning.runId}` : ""}`,
|
|
30
30
|
];
|
|
31
31
|
if (warning.state === "failed" && warning.error) lines.push(`Failure: ${warning.error}`);
|
|
32
32
|
if (warning.state === "stalemate" && warning.stalemateRepeats !== undefined) {
|
package/src/watchdog/review.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createReadOnlyTools, convertToLlm, type ExtensionContext } from "@earen
|
|
|
3
3
|
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
4
4
|
import type { Model, ProviderHeaders } from "@earendil-works/pi-ai";
|
|
5
5
|
import { Type, type Static } from "typebox";
|
|
6
|
-
import {
|
|
6
|
+
import { resolveModelCandidate } from "../runs/shared/model-resolution.ts";
|
|
7
7
|
import { agentStreamOptions } from "../shared/agent-stream-options.ts";
|
|
8
8
|
import { opencodeSessionHeaders } from "../shared/opencode-session-headers.ts";
|
|
9
9
|
import { resolveEffectiveThinking, splitKnownThinkingSuffix, THINKING_LEVELS, toModelInfo } from "../shared/model-info.ts";
|
|
@@ -12,11 +12,11 @@ import { loadWatchdogGuidance } from "./guidance.ts";
|
|
|
12
12
|
import { boundWatchdogReviewText, type WatchdogReviewFunction, type WatchdogReviewRequest } from "./runtime.ts";
|
|
13
13
|
import {
|
|
14
14
|
WATCHDOG_WARNING_CATEGORIES,
|
|
15
|
-
|
|
15
|
+
WATCHDOG_WARNING_IMPORTANCES,
|
|
16
16
|
WATCHDOG_WARNING_SEVERITIES,
|
|
17
17
|
type ResolvedWatchdogConfig,
|
|
18
18
|
type WatchdogCategory,
|
|
19
|
-
type
|
|
19
|
+
type WatchdogImportance,
|
|
20
20
|
type WatchdogSeverity,
|
|
21
21
|
type WatchdogWarning,
|
|
22
22
|
} from "./types.ts";
|
|
@@ -27,11 +27,11 @@ const WatchdogAskParams = Type.Object({ question: Type.String(), evidence: Type.
|
|
|
27
27
|
|
|
28
28
|
const WatchdogWarnParams = Type.Object({
|
|
29
29
|
severity: Type.String({ enum: WATCHDOG_WARNING_SEVERITIES, description: "concern for actionable risk, blocker for a likely wrong or unsafe outcome" }),
|
|
30
|
+
importance: Type.String({ enum: WATCHDOG_WARNING_IMPORTANCES, description: "low or medium for user-only visibility; high when the parent model must receive the finding" }),
|
|
30
31
|
summary: Type.String({ description: "One concise sentence naming the issue." }),
|
|
31
32
|
evidence: Type.String({ description: "Specific evidence from the turn delta or inspected files." }),
|
|
32
33
|
recommendedAction: Type.String({ description: "Specific action the parent should take before accepting or continuing." }),
|
|
33
34
|
category: Type.Optional(Type.String({ enum: WATCHDOG_WARNING_CATEGORIES })),
|
|
34
|
-
confidence: Type.Optional(Type.String({ enum: WATCHDOG_WARNING_CONFIDENCES })),
|
|
35
35
|
}, { additionalProperties: false });
|
|
36
36
|
|
|
37
37
|
type WatchdogWarnParams = Static<typeof WatchdogWarnParams>;
|
|
@@ -182,8 +182,8 @@ function nonEmptyString(value: string, field: string): string {
|
|
|
182
182
|
function toWatchdogWarning(params: WatchdogWarnParams): WatchdogWarning {
|
|
183
183
|
return {
|
|
184
184
|
severity: params.severity as WatchdogSeverity,
|
|
185
|
+
importance: params.importance as WatchdogImportance,
|
|
185
186
|
category: (params.category ?? "other") as WatchdogCategory,
|
|
186
|
-
confidence: (params.confidence ?? "medium") as WatchdogConfidence,
|
|
187
187
|
source: "main",
|
|
188
188
|
summary: nonEmptyString(params.summary, "summary"),
|
|
189
189
|
evidence: nonEmptyString(params.evidence, "evidence"),
|
|
@@ -197,7 +197,7 @@ function createWatchdogWarnTool(request: WatchdogReviewRequest): AgentTool<typeo
|
|
|
197
197
|
label: "Watchdog warning",
|
|
198
198
|
description: [
|
|
199
199
|
"Emit one actionable main-session watchdog warning.",
|
|
200
|
-
"
|
|
200
|
+
"Set importance explicitly: low or medium for user-only visibility, high only when the parent model must receive the finding.",
|
|
201
201
|
"Do not use for nits, praise, informational notes, or clean reviews.",
|
|
202
202
|
].join(" "),
|
|
203
203
|
parameters: WatchdogWarnParams,
|
|
@@ -227,8 +227,8 @@ export function buildWatchdogSystemPrompt(ctx: Pick<ExtensionContext, "cwd">, op
|
|
|
227
227
|
options.hasScope ? "Use the Current scope record alongside supplied activity evidence; an unrelated user question does not cancel older authorized work." : undefined,
|
|
228
228
|
`You are read-only. You may use ${options.hasDiff ? "read, grep, find, ls, and watchdog_diff (the full repo diff since the session baseline; pass a path to narrow it)" : "read, grep, find, and ls"}. Do not edit files, run shell commands, spawn agents, or mutate state.`,
|
|
229
229
|
"Emit warnings only by calling watchdog_warn. Freeform assistant text is ignored and must not be used to report warnings.",
|
|
230
|
-
"Emit only
|
|
231
|
-
"Do not emit nits, style preferences,
|
|
230
|
+
"Emit only actionable concerns or blockers: missed user constraints, correctness risks, test gaps that matter, unsafe changes, stale facts, loop risks, or scope drift.",
|
|
231
|
+
"Do not emit nits, style preferences, unsupported guesses, informational notes, praise, or summaries.",
|
|
232
232
|
"If the turn is clean, call no tools and end normally.",
|
|
233
233
|
"Use severity='blocker' only when the issue should stop acceptance until addressed; otherwise use severity='concern'.",
|
|
234
234
|
guidance ? `\nStanding instructions from WATCHDOG.md (project first, then user):\n${guidance}` : undefined,
|
|
@@ -256,32 +256,17 @@ export function createMainWatchdogReview(provider: WatchdogContextProvider, opti
|
|
|
256
256
|
if (!ctx) throw new Error("Main watchdog review cannot run without an active Pi extension context.");
|
|
257
257
|
const aborted = () => ctx.signal?.aborted || request.signal?.aborted;
|
|
258
258
|
if (aborted()) return { stopReason: "aborted" };
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
for (let index = 0; index < candidates.length; index++) {
|
|
265
|
-
if (aborted()) return { stopReason: "aborted" };
|
|
266
|
-
const candidate = candidates[index];
|
|
267
|
-
const config = { ...request.config, main: { ...request.config.main, model: candidate === inherited ? undefined : candidate } };
|
|
268
|
-
try {
|
|
269
|
-
const attempt = await runWatchdogAttempt(ctx, { ...request, config }, options);
|
|
270
|
-
if (aborted()) return { stopReason: "aborted" };
|
|
271
|
-
if (!attempt.retryable || index === candidates.length - 1) return attempt.result;
|
|
272
|
-
} catch (error) {
|
|
273
|
-
if (aborted()) return { stopReason: "aborted" };
|
|
274
|
-
if (!(error instanceof WatchdogAuthError)) throw error;
|
|
275
|
-
if (isContextOverflow(error.message) || !isRetryableModelFailureAttempt({ error: error.message }) || index === candidates.length - 1) throw error.cause ?? error;
|
|
276
|
-
}
|
|
259
|
+
try {
|
|
260
|
+
return (await runWatchdogAttempt(ctx, request, options)).result;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (error instanceof WatchdogAuthError) throw error.cause ?? error;
|
|
263
|
+
throw error;
|
|
277
264
|
}
|
|
278
|
-
throw new Error("No usable watchdog model candidates.");
|
|
279
265
|
};
|
|
280
266
|
}
|
|
281
267
|
|
|
282
268
|
async function runWatchdogAttempt(ctx: ExtensionContext, request: WatchdogReviewRequest, options: CreateMainWatchdogReviewOptions): Promise<{
|
|
283
269
|
result: Awaited<ReturnType<WatchdogReviewFunction>>;
|
|
284
|
-
retryable?: boolean;
|
|
285
270
|
}> {
|
|
286
271
|
const selection = await resolveWatchdogReviewModel(ctx, request.config, {
|
|
287
272
|
currentThinkingLevel: options.getThinkingLevel?.(),
|
|
@@ -373,7 +358,6 @@ async function runWatchdogAttempt(ctx: ExtensionContext, request: WatchdogReview
|
|
|
373
358
|
const stopReason = reason === "error" || reason === "aborted" || reason === "length" ? reason : "stop";
|
|
374
359
|
const error = terminal && "errorMessage" in terminal && typeof terminal.errorMessage === "string" ? terminal.errorMessage : undefined;
|
|
375
360
|
return {
|
|
376
|
-
result: clarification ? { clarification } : { stopReason },
|
|
377
|
-
retryable: !clarification && stopReason === "error" && !isContextOverflow(error) && isRetryableModelFailureAttempt({ error, messages: agent.state.messages, toolCount }),
|
|
361
|
+
result: clarification ? { clarification } : error ? { stopReason, errorMessage: error } : { stopReason },
|
|
378
362
|
};
|
|
379
363
|
}
|
package/src/watchdog/rules.ts
CHANGED
|
@@ -51,7 +51,7 @@ export function evaluateLaunchRule(rules: WatchdogRulesConfig | undefined, agent
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export function ruleViolationWarning(violation: WatchdogRuleViolation): WatchdogWarning {
|
|
54
|
-
return { severity: "concern", category: "missed-constraint",
|
|
54
|
+
return { severity: "concern", category: "missed-constraint", importance: "high", source: "main", ...violation };
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export function applyWatchdogLaunchRules(input: { cwd: string; agent: string; model?: string; warn?: (violation: WatchdogRuleViolation) => void }): string | undefined {
|
package/src/watchdog/runtime.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { WatchdogScopeArtifact } from "./scope.ts";
|
|
|
14
14
|
import { resolveWatchdogConfig } from "./settings.ts";
|
|
15
15
|
import { formatWatchdogOrchestrationActivity, formatWatchdogTurnDelta } from "./turn-delta.ts";
|
|
16
16
|
import {
|
|
17
|
+
WATCHDOG_WARNING_IMPORTANCES,
|
|
17
18
|
type ResolvedWatchdogConfig,
|
|
18
19
|
type WatchdogEndpointConfig,
|
|
19
20
|
type WatchdogLspRuntimeSnapshot,
|
|
@@ -31,6 +32,8 @@ type ReviewStopReason = "stop" | "error" | "aborted" | "length";
|
|
|
31
32
|
export interface WatchdogReviewResult {
|
|
32
33
|
warnings?: WatchdogWarning[];
|
|
33
34
|
stopReason?: ReviewStopReason;
|
|
35
|
+
/** Provider error text for a failed review, surfaced in status `Last error`. */
|
|
36
|
+
errorMessage?: string;
|
|
34
37
|
clarification?: { question: string; evidence: string };
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -83,6 +86,7 @@ interface MainWatchdogRuntimeOptions {
|
|
|
83
86
|
review?: WatchdogReviewFunction;
|
|
84
87
|
reviewDescription?: string;
|
|
85
88
|
displayWarning?: (warning: WatchdogWarningDetails, options?: WatchdogWarningSendOptions) => void;
|
|
89
|
+
displayUserWarning?: (warning: WatchdogWarningDetails) => void;
|
|
86
90
|
/** Supplying this main-session delivery capability gates clarification (children omit it). */
|
|
87
91
|
displayClarification?: (content: string) => void;
|
|
88
92
|
reviewChangesOnly?: boolean;
|
|
@@ -130,6 +134,7 @@ export class MainWatchdogRuntime {
|
|
|
130
134
|
private readonly reviewConnected: boolean;
|
|
131
135
|
private readonly reviewDescription: string;
|
|
132
136
|
private readonly displayWarning: ((warning: WatchdogWarningDetails, options?: WatchdogWarningSendOptions) => void) | undefined;
|
|
137
|
+
private readonly displayUserWarning: ((warning: WatchdogWarningDetails) => void) | undefined;
|
|
133
138
|
private readonly reviewChangesOnly: boolean;
|
|
134
139
|
private readonly lspDiagnostics: WatchdogLspDiagnosticsFunction;
|
|
135
140
|
private readonly repoChangeSignature: typeof computeWatchdogRepoChangeSignature;
|
|
@@ -187,6 +192,7 @@ export class MainWatchdogRuntime {
|
|
|
187
192
|
this.reviewConnected = Boolean(options.review);
|
|
188
193
|
this.reviewDescription = options.reviewDescription ?? (options.review ? "injected seam" : "not wired");
|
|
189
194
|
this.displayWarning = options.displayWarning;
|
|
195
|
+
this.displayUserWarning = options.displayUserWarning;
|
|
190
196
|
this.displayClarification = options.displayClarification;
|
|
191
197
|
this.reviewChangesOnly = options.reviewChangesOnly === true;
|
|
192
198
|
this.lspDiagnostics = options.lspDiagnostics ?? collectWatchdogLspDiagnostics;
|
|
@@ -268,7 +274,7 @@ export class MainWatchdogRuntime {
|
|
|
268
274
|
return this.getSnapshot();
|
|
269
275
|
}
|
|
270
276
|
|
|
271
|
-
reset(_reason = "reset", options: { clearReviewInputSignature?: boolean; resetChangeSignature?: boolean; clearLspLedger?: boolean; clearScope?: boolean } = {}): void {
|
|
277
|
+
reset(_reason = "reset", options: { clearReviewInputSignature?: boolean; resetChangeSignature?: boolean; clearLspLedger?: boolean; clearScope?: boolean; clearActivity?: boolean } = {}): void {
|
|
272
278
|
this.activeReviewAbortController?.abort();
|
|
273
279
|
this.abortActiveAgentEnd();
|
|
274
280
|
this.epoch++;
|
|
@@ -290,10 +296,8 @@ export class MainWatchdogRuntime {
|
|
|
290
296
|
this.lspLedger.reset();
|
|
291
297
|
this.lastLspSnapshot = undefined;
|
|
292
298
|
}
|
|
293
|
-
if (options.clearScope)
|
|
294
|
-
|
|
295
|
-
this.clearActivity();
|
|
296
|
-
}
|
|
299
|
+
if (options.clearScope) this.scope.reset();
|
|
300
|
+
if (options.clearScope || options.clearActivity) this.clearActivity();
|
|
297
301
|
if (options.clearReviewInputSignature) this.lastReviewInputSignature = undefined;
|
|
298
302
|
if (options.resetChangeSignature) this.resetRepoChangeBaseline({ reviewed: true });
|
|
299
303
|
this.guard.reset();
|
|
@@ -467,13 +471,13 @@ export class MainWatchdogRuntime {
|
|
|
467
471
|
this.ruleWarningsThisRun.add(violation.summary);
|
|
468
472
|
const details = normalizeWatchdogWarningDetails(ruleViolationWarning(violation), { state: "displayed", displayedAt: new Date().toISOString() });
|
|
469
473
|
this.lastWarning = details;
|
|
470
|
-
this.
|
|
474
|
+
this.routeWarning(details, { deliverAs: "steer" });
|
|
471
475
|
}
|
|
472
476
|
|
|
473
|
-
|
|
477
|
+
displayRecordedWarning(warning: WatchdogWarning): void {
|
|
474
478
|
const details = normalizeWatchdogWarningDetails(warning, { state: "displayed", source: warning.source ?? "main" });
|
|
475
479
|
this.lastWarning = details;
|
|
476
|
-
|
|
480
|
+
this.routeWarning(details);
|
|
477
481
|
}
|
|
478
482
|
|
|
479
483
|
getSnapshot(cwd?: string): WatchdogRuntimeSnapshot {
|
|
@@ -536,7 +540,13 @@ export class MainWatchdogRuntime {
|
|
|
536
540
|
}
|
|
537
541
|
|
|
538
542
|
private warningMeetsThreshold(warning: WatchdogWarning): boolean {
|
|
539
|
-
return
|
|
543
|
+
return (WATCHDOG_WARNING_IMPORTANCES as readonly string[]).includes(warning.importance)
|
|
544
|
+
&& (this.configResult.config.severityThreshold === "concern" || warning.severity === "blocker");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private routeWarning(details: WatchdogWarningDetails, options?: WatchdogWarningSendOptions): void {
|
|
548
|
+
if (details.importance === "high") this.displayWarning?.(details, options);
|
|
549
|
+
else this.displayUserWarning?.(details);
|
|
540
550
|
}
|
|
541
551
|
|
|
542
552
|
private acceptWarning(epoch: number, reviewId: number, warning: WatchdogWarning): boolean {
|
|
@@ -658,7 +668,8 @@ export class MainWatchdogRuntime {
|
|
|
658
668
|
}
|
|
659
669
|
for (const warning of result.warnings ?? []) this.acceptWarning(reviewEpoch, reviewId, warning);
|
|
660
670
|
if (result.stopReason && result.stopReason !== "stop") {
|
|
661
|
-
|
|
671
|
+
const detail = result.errorMessage?.trim() ? ` ${boundWatchdogReviewText(result.errorMessage.trim(), 600)}` : "";
|
|
672
|
+
this.fail(`Watchdog review ended with stop reason '${result.stopReason}'.${detail}`);
|
|
662
673
|
return "completed";
|
|
663
674
|
}
|
|
664
675
|
this.displayAcceptedReviewWarning(options.correction);
|
|
@@ -690,7 +701,7 @@ export class MainWatchdogRuntime {
|
|
|
690
701
|
};
|
|
691
702
|
if (correction) {
|
|
692
703
|
this.lastWarning = details;
|
|
693
|
-
this.
|
|
704
|
+
this.routeWarning(details, { deliverAs: "steer" });
|
|
694
705
|
return;
|
|
695
706
|
}
|
|
696
707
|
this.deliverBoundaryWarning(details);
|
|
@@ -711,7 +722,7 @@ export class MainWatchdogRuntime {
|
|
|
711
722
|
: details;
|
|
712
723
|
this.stalemate = stalemate;
|
|
713
724
|
this.lastWarning = delivered;
|
|
714
|
-
this.
|
|
725
|
+
this.routeWarning(delivered, stalemate ? { triggerTurn: false } : undefined);
|
|
715
726
|
}
|
|
716
727
|
|
|
717
728
|
// The previous boundary finding is a repeat to count, not a duplicate, until stalemate.
|
package/src/watchdog/settings.ts
CHANGED
|
@@ -111,9 +111,9 @@ const ROLE_MODEL_RULE_FIELDS = new Set(["allow", "deny", "note"]);
|
|
|
111
111
|
const GUIDANCE_FIELDS = new Set(["watchdogMd"]);
|
|
112
112
|
const SCOPE_FIELDS = new Set(["enabled"]);
|
|
113
113
|
const CADENCE_FIELDS = new Set(["everyNTools"]);
|
|
114
|
-
const ENDPOINT_FIELDS = new Set(["enabled", "model", "
|
|
115
|
-
const CHILDREN_FIELDS = new Set(["enabled", "model", "
|
|
116
|
-
const CHILD_OVERRIDE_FIELDS = new Set(["enabled", "model", "
|
|
114
|
+
const ENDPOINT_FIELDS = new Set(["enabled", "model", "thinking"]);
|
|
115
|
+
const CHILDREN_FIELDS = new Set(["enabled", "model", "thinking", "watchdogTailTimeoutMs", "cadence", "overrides"]);
|
|
116
|
+
const CHILD_OVERRIDE_FIELDS = new Set(["enabled", "model", "thinking", "cadence"]);
|
|
117
117
|
const LSP_FIELDS = new Set(["enabled", "timeoutMs", "maxFiles", "maxDiagnostics"]);
|
|
118
118
|
|
|
119
119
|
function cloneDefaultConfig(): ResolvedWatchdogConfig {
|
|
@@ -221,7 +221,6 @@ function parseEndpointPatch(value: unknown, field: string, meta: ParseMeta): Wat
|
|
|
221
221
|
const input = parseObject(value, field, meta);
|
|
222
222
|
assertKnownFields(input, ENDPOINT_FIELDS, field, meta);
|
|
223
223
|
const patch: WatchdogEndpointPatch = {};
|
|
224
|
-
if ("fallbackModels" in input) patch.fallbackModels = parseStringList(input.fallbackModels, `${field}.fallbackModels`, meta);
|
|
225
224
|
if ("enabled" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);
|
|
226
225
|
if ("model" in input) patch.model = parseNonEmptyString(input.model, `${field}.model`, meta);
|
|
227
226
|
if ("thinking" in input) patch.thinking = parseThinking(input.thinking, `${field}.thinking`, meta);
|
|
@@ -232,7 +231,6 @@ function parseChildOverridePatch(value: unknown, field: string, meta: ParseMeta)
|
|
|
232
231
|
const input = parseObject(value, field, meta);
|
|
233
232
|
assertKnownFields(input, CHILD_OVERRIDE_FIELDS, field, meta);
|
|
234
233
|
const patch: WatchdogChildOverridePatch = {};
|
|
235
|
-
if ("fallbackModels" in input) patch.fallbackModels = parseStringList(input.fallbackModels, `${field}.fallbackModels`, meta);
|
|
236
234
|
if ("enabled" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);
|
|
237
235
|
if ("model" in input) patch.model = parseNonEmptyString(input.model, `${field}.model`, meta);
|
|
238
236
|
if ("thinking" in input) patch.thinking = parseThinking(input.thinking, `${field}.thinking`, meta);
|
|
@@ -244,7 +242,6 @@ function parseChildrenPatch(value: unknown, field: string, meta: ParseMeta): Wat
|
|
|
244
242
|
const input = parseObject(value, field, meta);
|
|
245
243
|
assertKnownFields(input, CHILDREN_FIELDS, field, meta);
|
|
246
244
|
const patch: WatchdogChildrenPatch = {};
|
|
247
|
-
if ("fallbackModels" in input) patch.fallbackModels = parseStringList(input.fallbackModels, `${field}.fallbackModels`, meta);
|
|
248
245
|
if ("enabled" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);
|
|
249
246
|
if ("model" in input) patch.model = parseNonEmptyString(input.model, `${field}.model`, meta);
|
|
250
247
|
if ("thinking" in input) patch.thinking = parseThinking(input.thinking, `${field}.thinking`, meta);
|