@yagni-app/code-staging 0.3.0-staging.1067.1 → 0.3.0-staging.1071.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extension/execPolicy.d.ts +73 -0
- package/dist/extension/execPolicy.js +399 -0
- package/dist/extension/guardian.d.ts +107 -0
- package/dist/extension/guardian.js +175 -0
- package/dist/extension/index.d.ts +5 -1
- package/dist/extension/index.js +27 -2
- package/dist/extension/permission.d.ts +48 -6
- package/dist/extension/permission.js +233 -24
- package/dist/extension/pipeline/personas.js +22 -0
- package/dist/promptEnrichment.d.ts +1 -1
- package/dist/promptEnrichment.js +17 -0
- package/package.json +2 -2
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Guardian — LLM auto-review of prompt-band bash commands (YAG-504).
|
|
3
|
+
*
|
|
4
|
+
* Pure half: verdict types, state tracking, circuit breaker, JSON parsing.
|
|
5
|
+
* I/O half: reviewCommand spawns a locked-down child pi (same runStage seam
|
|
6
|
+
* the advisor uses) on the efficient tier with read-only tools and a risk
|
|
7
|
+
* policy persona. The child returns a JSON verdict; the gate acts on it.
|
|
8
|
+
*
|
|
9
|
+
* Same pure/IO split as advisor.ts (decideConsult pure, askAdvisorTool I/O)
|
|
10
|
+
* and permission.ts (decideGate pure, registerPermissionGate I/O), for the
|
|
11
|
+
* same reason: the rules are what need exhaustive tests, and they must not
|
|
12
|
+
* require a child process to exercise.
|
|
13
|
+
*
|
|
14
|
+
* Trigger: the exec policy classifies a bash command as "prompt" (not clearly
|
|
15
|
+
* safe, not clearly forbidden). The Guardian reviews it instead of interrupting
|
|
16
|
+
* the user. On allow, the command runs. On deny, the agent sees the rationale
|
|
17
|
+
* and is told to find a safer alternative or ask the user. On timeout/error,
|
|
18
|
+
* auto mode fails closed (block); review mode falls back to the user prompt.
|
|
19
|
+
*
|
|
20
|
+
* Circuit breaker: 3 consecutive denials in one turn → turn interrupted.
|
|
21
|
+
*/
|
|
22
|
+
import { runStage as defaultRunStage } from "./pipeline/runner.js";
|
|
23
|
+
export const DEFAULT_GUARDIAN_LIMITS = {
|
|
24
|
+
maxReviews: 30,
|
|
25
|
+
maxConsecutiveDenials: 3,
|
|
26
|
+
timeoutMs: 15_000,
|
|
27
|
+
};
|
|
28
|
+
/** The model tier the Guardian runs on. Configurable via YAGNI_GUARDIAN_TIER. */
|
|
29
|
+
export const GUARDIAN_MODEL_TIER = "efficient";
|
|
30
|
+
/** Read-only tools — the Guardian can read files for context but cannot write or execute. */
|
|
31
|
+
export const GUARDIAN_TOOLS = ["read"];
|
|
32
|
+
export function makeGuardianState() {
|
|
33
|
+
const state = { reviews: 0, consecutiveDenials: 0 };
|
|
34
|
+
return {
|
|
35
|
+
read: () => ({ ...state }),
|
|
36
|
+
recordReview(outcome) {
|
|
37
|
+
state.reviews += 1;
|
|
38
|
+
if (outcome === "deny") {
|
|
39
|
+
state.consecutiveDenials += 1;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
state.consecutiveDenials = 0;
|
|
43
|
+
}
|
|
44
|
+
return { ...state };
|
|
45
|
+
},
|
|
46
|
+
resetTurn() {
|
|
47
|
+
state.consecutiveDenials = 0;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function checkCircuitBreaker(state, limits) {
|
|
52
|
+
if (state.consecutiveDenials >= limits.maxConsecutiveDenials) {
|
|
53
|
+
return {
|
|
54
|
+
tripped: true,
|
|
55
|
+
reason: `Guardian denied ${state.consecutiveDenials} consecutive actions this turn. Pausing for human review.`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { tripped: false };
|
|
59
|
+
}
|
|
60
|
+
// --- Verdict parsing (fail closed on malformed) ---
|
|
61
|
+
export function parseVerdict(raw) {
|
|
62
|
+
try {
|
|
63
|
+
// Efficient-tier models may wrap JSON in markdown fences despite
|
|
64
|
+
// instructions to output raw JSON. Extract the first {...} block.
|
|
65
|
+
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
66
|
+
const jsonStr = jsonMatch ? jsonMatch[0] : raw;
|
|
67
|
+
const parsed = JSON.parse(jsonStr);
|
|
68
|
+
const outcome = parsed?.outcome;
|
|
69
|
+
if (outcome !== "allow" && outcome !== "deny")
|
|
70
|
+
return null;
|
|
71
|
+
const riskLevel = parsed.riskLevel;
|
|
72
|
+
const validLevels = ["low", "medium", "high", "critical"];
|
|
73
|
+
return {
|
|
74
|
+
outcome,
|
|
75
|
+
riskLevel: typeof riskLevel === "string" && validLevels.includes(riskLevel)
|
|
76
|
+
? riskLevel
|
|
77
|
+
: "medium",
|
|
78
|
+
rationale: typeof parsed.rationale === "string" && parsed.rationale.trim().length > 0
|
|
79
|
+
? parsed.rationale.trim()
|
|
80
|
+
: "No rationale provided.",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// --- /cost subtotal ---
|
|
88
|
+
export function formatGuardianSubtotal(state, limits) {
|
|
89
|
+
if (state.reviews === 0)
|
|
90
|
+
return "";
|
|
91
|
+
const plural = state.reviews === 1 ? "review" : "reviews";
|
|
92
|
+
return `Guardian: ${state.reviews} ${plural}.`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The synthetic stage a Guardian consult runs as. Borrows the `plan` StageId
|
|
96
|
+
* (same pattern as the advisor) so it doesn't ripple into feed/reducers. The
|
|
97
|
+
* agent name selects the guardian persona from PERSONA_BODIES.
|
|
98
|
+
*/
|
|
99
|
+
export function guardianStage(modelTier = GUARDIAN_MODEL_TIER) {
|
|
100
|
+
return {
|
|
101
|
+
id: "plan",
|
|
102
|
+
agent: "guardian",
|
|
103
|
+
model: modelTier,
|
|
104
|
+
tools: GUARDIAN_TOOLS,
|
|
105
|
+
taskTemplate: "{ticket}",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Run a Guardian consult: spawn a locked-down child pi with the risk policy
|
|
110
|
+
* persona and the command as the task. Parse the JSON verdict from the output.
|
|
111
|
+
* Returns { verdict, cost } on success, { verdict: null, error, cost } on failure.
|
|
112
|
+
*/
|
|
113
|
+
export async function reviewCommand(command, deps) {
|
|
114
|
+
const runStage = deps.runStage ?? defaultRunStage;
|
|
115
|
+
const stage = guardianStage(deps.modelTier);
|
|
116
|
+
// Build the abort signal with the Guardian timeout.
|
|
117
|
+
const timeoutMs = DEFAULT_GUARDIAN_LIMITS.timeoutMs;
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
120
|
+
timer.unref?.();
|
|
121
|
+
// Chain with the caller's signal if present.
|
|
122
|
+
if (deps.signal) {
|
|
123
|
+
if (deps.signal.aborted)
|
|
124
|
+
controller.abort();
|
|
125
|
+
else
|
|
126
|
+
deps.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const result = await runStage(stage, { ticket: `Review this shell command for safety. Output only the JSON verdict.\n\nCommand:\n${command}` }, {
|
|
130
|
+
cwd: deps.cwd,
|
|
131
|
+
signal: controller.signal,
|
|
132
|
+
callerLabel: "guardian",
|
|
133
|
+
});
|
|
134
|
+
const cost = result.usage?.cost ?? 0;
|
|
135
|
+
const output = result.finalOutput.trim();
|
|
136
|
+
if (!output) {
|
|
137
|
+
return { verdict: null, error: "empty", cost };
|
|
138
|
+
}
|
|
139
|
+
const verdict = parseVerdict(output);
|
|
140
|
+
if (!verdict) {
|
|
141
|
+
return { verdict: null, error: "malformed", cost };
|
|
142
|
+
}
|
|
143
|
+
return { verdict, cost };
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
// Distinguish timeout from network/process errors.
|
|
147
|
+
if (controller.signal.aborted && !deps.signal?.aborted) {
|
|
148
|
+
return { verdict: null, error: "timeout", cost: 0 };
|
|
149
|
+
}
|
|
150
|
+
return { verdict: null, error: "network", cost: 0 };
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Create a sanitized diagnostic event. Never includes the raw command text
|
|
158
|
+
* (could contain secrets). YAGNI_DEBUG=1 adds rationale and a command hash.
|
|
159
|
+
*/
|
|
160
|
+
export function buildDiagnosticEvent(outcome, opts) {
|
|
161
|
+
const ev = {
|
|
162
|
+
event: "guardian_review",
|
|
163
|
+
outcome,
|
|
164
|
+
...(opts.durationMs !== undefined ? { durationMs: opts.durationMs } : {}),
|
|
165
|
+
...(opts.tier !== undefined ? { tier: opts.tier } : {}),
|
|
166
|
+
};
|
|
167
|
+
if (opts.debug) {
|
|
168
|
+
if (opts.rationale)
|
|
169
|
+
ev.rationale = opts.rationale;
|
|
170
|
+
if (opts.commandHash)
|
|
171
|
+
ev.commandHash = opts.commandHash;
|
|
172
|
+
}
|
|
173
|
+
return ev;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=guardian.js.map
|
|
@@ -106,6 +106,8 @@ export { makeAskYagniTool } from "./askYagniTool.js";
|
|
|
106
106
|
export { makeFileTicketTool, makeUpdateTicketStatusTool } from "./ticketTools.js";
|
|
107
107
|
export { makeAskAdvisorTool, registerAdviseCommand } from "./askAdvisorTool.js";
|
|
108
108
|
export { ADVISOR_TIER, DEFAULT_ADVISOR_LIMITS, decideConsult, formatAdvisorSubtotal, makeAdvisorState, } from "./advisor.js";
|
|
109
|
+
export { DEFAULT_GUARDIAN_LIMITS, GUARDIAN_MODEL_TIER, formatGuardianSubtotal, makeGuardianState, reviewCommand, } from "./guardian.js";
|
|
110
|
+
export type { GuardianOutcome, GuardianVerdict, GuardianState, GuardianStateHandle, GuardianLimits, ReviewResult, ReviewCommandDeps, } from "./guardian.js";
|
|
109
111
|
export type { Citation, MakeAskYagniToolOptions } from "./askYagniTool.js";
|
|
110
112
|
export { makeReviewBusinessMatchTool } from "./reviewTool.js";
|
|
111
113
|
export type { MakeReviewToolOptions } from "./reviewTool.js";
|
|
@@ -141,7 +143,9 @@ export { registerSubagents, makeSubagentTool, discoverSubagents, parseAgentMarkd
|
|
|
141
143
|
export type { SubagentDef, SubagentSource } from "./subagents.js";
|
|
142
144
|
export { registerTodos, makeTodoTool, normalizeTodos, reconstructTodos, renderTodoWidget, formatTodoList, todoSummary, TODO_TOOL_NAME, MAX_TODOS, } from "./todos.js";
|
|
143
145
|
export type { TodoItem, TodoStatus, TodoTheme } from "./todos.js";
|
|
144
|
-
export { decideGate, registerPermissionGate, filterStalePlanContext, DEFAULT_PERMISSION_POLICY, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission.js";
|
|
146
|
+
export { decideGate, registerPermissionGate, filterStaleModeContext, filterStalePlanContext, buildModeContextMessage, DEFAULT_PERMISSION_POLICY, MODE_CONTEXT_TYPE, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission.js";
|
|
147
|
+
export { classifyCommand, DEFAULT_EXEC_POLICY, } from "./execPolicy.js";
|
|
148
|
+
export type { ExecDecision, ExecPolicy, PrefixRule, ExecClassification, } from "./execPolicy.js";
|
|
145
149
|
export type { PermissionMode, PermissionPolicy, GateDecision, RegisterPermissionDeps, BlessRememberInfo, } from "./permission.js";
|
|
146
150
|
export { makeBlessStore, blessPath } from "./bless.js";
|
|
147
151
|
export type { BlessStore, BlessRule } from "./bless.js";
|
package/dist/extension/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { appendFileSync, mkdirSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { DEFAULT_ADVISOR_LIMITS, formatAdvisorSubtotal, makeAdvisorState } from "./advisor.js";
|
|
5
|
+
import { DEFAULT_GUARDIAN_LIMITS, formatGuardianSubtotal, GUARDIAN_MODEL_TIER, makeGuardianState, reviewCommand } from "./guardian.js";
|
|
5
6
|
import { makeAskAdvisorTool, registerAdviseCommand } from "./askAdvisorTool.js";
|
|
6
7
|
import { makeAskYagniTool } from "./askYagniTool.js";
|
|
7
8
|
import { makeFileTicketTool, makeUpdateTicketStatusTool } from "./ticketTools.js";
|
|
@@ -234,8 +235,26 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
234
235
|
// Mutating MCP tools join write/edit/bash in the gate policy: plan mode
|
|
235
236
|
// holds them, review mode confirms them.
|
|
236
237
|
const modeHolder = createModeHolder();
|
|
238
|
+
const guardianState = makeGuardianState();
|
|
239
|
+
const guardianDisabled = env.YAGNI_DISABLE_GUARDIAN === "1" || env.YAGNI_DISABLE_GUARDIAN === "true";
|
|
240
|
+
const guardianTier = env.YAGNI_GUARDIAN_TIER ?? GUARDIAN_MODEL_TIER;
|
|
237
241
|
registerPermissionGate(pi, {
|
|
238
242
|
modeHolder,
|
|
243
|
+
guardianState,
|
|
244
|
+
guardianLimits: DEFAULT_GUARDIAN_LIMITS,
|
|
245
|
+
guardianTier,
|
|
246
|
+
guardianDisabled,
|
|
247
|
+
guardianReview: (command, deps) => reviewCommand(command, { ...deps, modelTier: guardianTier }),
|
|
248
|
+
onGuardianReview: (ev) => {
|
|
249
|
+
try {
|
|
250
|
+
if (process.env.NODE_TEST_CONTEXT)
|
|
251
|
+
return;
|
|
252
|
+
const logPath = join(codeStateHome(null), "logs", "guardian.log");
|
|
253
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
254
|
+
appendFileSync(logPath, JSON.stringify({ ts: new Date().toISOString(), ...ev }) + "\n", "utf8");
|
|
255
|
+
}
|
|
256
|
+
catch { /* logging must never break the session */ }
|
|
257
|
+
},
|
|
239
258
|
...(mcpMutatingTools.length > 0
|
|
240
259
|
? {
|
|
241
260
|
policy: {
|
|
@@ -271,7 +290,11 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
271
290
|
// turn_end accumulator. Thread the subtotal in explicitly for the local
|
|
272
291
|
// fallback line; the server-authoritative line already counts advisor
|
|
273
292
|
// spend as an ordinary caller row.
|
|
274
|
-
advisorSubtotal: () =>
|
|
293
|
+
advisorSubtotal: () => {
|
|
294
|
+
const advisor = formatAdvisorSubtotal(advisorState.read(), DEFAULT_ADVISOR_LIMITS);
|
|
295
|
+
const guardian = formatGuardianSubtotal(guardianState.read(), DEFAULT_GUARDIAN_LIMITS);
|
|
296
|
+
return [advisor, guardian].filter(Boolean).join(" ");
|
|
297
|
+
},
|
|
275
298
|
fetchHeadroom: async (signal) => {
|
|
276
299
|
try {
|
|
277
300
|
const res = await resilientFetch(`${baseUrl}/api/yagni-code/credits`, { method: "GET", headers: { authorization: `Bearer ${getTokenFn() ?? ""}` } }, { fetchImpl: authedFetch, signal, policy: { maxAttempts: 1, backoffBaseMs: 0, backoffMaxMs: 0, timeoutMs: COST_FETCH_TIMEOUT_MS, jitterRatio: 0 } });
|
|
@@ -651,6 +674,7 @@ export { makeAskYagniTool } from "./askYagniTool.js";
|
|
|
651
674
|
export { makeFileTicketTool, makeUpdateTicketStatusTool } from "./ticketTools.js";
|
|
652
675
|
export { makeAskAdvisorTool, registerAdviseCommand } from "./askAdvisorTool.js";
|
|
653
676
|
export { ADVISOR_TIER, DEFAULT_ADVISOR_LIMITS, decideConsult, formatAdvisorSubtotal, makeAdvisorState, } from "./advisor.js";
|
|
677
|
+
export { DEFAULT_GUARDIAN_LIMITS, GUARDIAN_MODEL_TIER, formatGuardianSubtotal, makeGuardianState, reviewCommand, } from "./guardian.js";
|
|
654
678
|
export { makeReviewBusinessMatchTool } from "./reviewTool.js";
|
|
655
679
|
export { makeRecordEngineeringContextTool } from "./recordContextTool.js";
|
|
656
680
|
export { makeRecordDecisionTool } from "./recordDecisionTool.js";
|
|
@@ -681,7 +705,8 @@ export { registerSubagents, makeSubagentTool, discoverSubagents, parseAgentMarkd
|
|
|
681
705
|
export { registerTodos, makeTodoTool, normalizeTodos, reconstructTodos, renderTodoWidget, formatTodoList, todoSummary, TODO_TOOL_NAME, MAX_TODOS, } from "./todos.js";
|
|
682
706
|
// P3 + W4: the permission gate seam (decideGate is pure; policy injectable) plus
|
|
683
707
|
// the session bless-with-remember capture hook.
|
|
684
|
-
export { decideGate, registerPermissionGate, filterStalePlanContext, DEFAULT_PERMISSION_POLICY, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission.js";
|
|
708
|
+
export { decideGate, registerPermissionGate, filterStaleModeContext, filterStalePlanContext, buildModeContextMessage, DEFAULT_PERMISSION_POLICY, MODE_CONTEXT_TYPE, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission.js";
|
|
709
|
+
export { classifyCommand, DEFAULT_EXEC_POLICY, } from "./execPolicy.js";
|
|
685
710
|
// W4 judgment loop: the session bless store (tool + path-prefix, session-only).
|
|
686
711
|
export { makeBlessStore, blessPath } from "./bless.js";
|
|
687
712
|
// W4 judgment loop: the decisions surface (/decide + /decisions) + shared bank.
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
30
30
|
import { type BlessStore } from "./bless.js";
|
|
31
|
+
import { type ExecPolicy } from "./execPolicy.js";
|
|
31
32
|
export type PermissionMode = "auto" | "plan" | "review";
|
|
32
33
|
/**
|
|
33
34
|
* A shared, mutable holder for the current permission mode. Both
|
|
@@ -54,6 +55,12 @@ export interface PermissionPolicy {
|
|
|
54
55
|
* judgment; default undefined (nothing pre-blessed).
|
|
55
56
|
*/
|
|
56
57
|
isBlessed?: (toolName: string, params: Record<string, unknown>) => boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Exec policy for bash command classification. When present, bash commands are
|
|
60
|
+
* classified by the exec policy engine before the tool-granular logic runs.
|
|
61
|
+
* Defaults to DEFAULT_EXEC_POLICY when absent.
|
|
62
|
+
*/
|
|
63
|
+
execPolicy?: ExecPolicy;
|
|
57
64
|
}
|
|
58
65
|
export declare const DEFAULT_PERMISSION_POLICY: PermissionPolicy;
|
|
59
66
|
/** A pure gate verdict: block outright, ask to confirm, or allow. */
|
|
@@ -62,6 +69,13 @@ export interface GateDecision {
|
|
|
62
69
|
reason?: string;
|
|
63
70
|
/** review mode only: the caller should ctx.ui.confirm before allowing. */
|
|
64
71
|
confirm?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Exec policy classification result for bash commands. When present, the
|
|
74
|
+
* tool_call handler should run the Guardian for "prompt" before falling
|
|
75
|
+
* through to the user confirm. Absent for non-bash tools or when no
|
|
76
|
+
* command string is available.
|
|
77
|
+
*/
|
|
78
|
+
classify?: "allow" | "prompt" | "forbidden";
|
|
65
79
|
}
|
|
66
80
|
/**
|
|
67
81
|
* Pure permission decision for one tool call under a mode + policy. Auto allows
|
|
@@ -89,16 +103,44 @@ export interface RegisterPermissionDeps {
|
|
|
89
103
|
onBlessRemember?: (ctx: ExtensionContext, info: BlessRememberInfo) => void | Promise<void>;
|
|
90
104
|
/** Shared holder so the footer can read the live mode on every render. */
|
|
91
105
|
modeHolder?: ModeHolder;
|
|
106
|
+
/**
|
|
107
|
+
* Guardian state handle for the session. When present, prompt-band bash
|
|
108
|
+
* commands are auto-reviewed by the Guardian LLM instead of interrupting
|
|
109
|
+
* the user. When absent, prompt-band commands fall through to the existing
|
|
110
|
+
* tool-granular behavior (review: user prompt, auto: allow).
|
|
111
|
+
*/
|
|
112
|
+
guardianState?: import("./guardian.js").GuardianStateHandle;
|
|
113
|
+
/** Guardian limits (timeouts, circuit breaker). Defaults to DEFAULT_GUARDIAN_LIMITS. */
|
|
114
|
+
guardianLimits?: import("./guardian.js").GuardianLimits;
|
|
115
|
+
/** Override the Guardian model tier (default: efficient). */
|
|
116
|
+
guardianTier?: string;
|
|
117
|
+
/** Injectable Guardian review function (tests pass a stub). */
|
|
118
|
+
guardianReview?: (command: string, deps: import("./guardian.js").ReviewCommandDeps) => Promise<import("./guardian.js").ReviewResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Called (fire-and-forget) after each Guardian review with a sanitized
|
|
121
|
+
* diagnostic event. Fail-soft; never blocks.
|
|
122
|
+
*/
|
|
123
|
+
onGuardianReview?: (event: import("./guardian.js").GuardianDiagnosticEvent) => void;
|
|
124
|
+
/** Disable the Guardian entirely (env var YAGNI_DISABLE_GUARDIAN). */
|
|
125
|
+
guardianDisabled?: boolean;
|
|
92
126
|
}
|
|
93
|
-
/** The customType tag on injected
|
|
94
|
-
export declare const
|
|
127
|
+
/** The customType tag on injected mode-context messages (filterable later). */
|
|
128
|
+
export declare const MODE_CONTEXT_TYPE = "yagni-mode-context";
|
|
129
|
+
/** Legacy alias — the original plan-mode tag, kept for backward compat. */
|
|
130
|
+
export declare const PLAN_CONTEXT_TYPE = "yagni-mode-context";
|
|
95
131
|
export declare const PLAN_CONTEXT_MESSAGE = "[PLAN MODE ACTIVE]\nYou are in plan mode: explore and design, change nothing.\n- write, edit, and bash are held by the permission gate; do not attempt them.\n- Read, search, and ask_yagni freely to ground the plan in how this company works.\n- Produce a concrete numbered plan of the steps you would take, with the files involved.\n- End by asking the user to review the plan; they run /mode auto (or /mode review) to execute it.\n- Once executing, track the plan's steps with todo_write.";
|
|
132
|
+
/** Build the mode-awareness context message for the current permission mode. */
|
|
133
|
+
export declare function buildModeContextMessage(mode: PermissionMode): string;
|
|
96
134
|
/**
|
|
97
|
-
* Drop previously injected
|
|
98
|
-
* model
|
|
99
|
-
*
|
|
135
|
+
* Drop previously injected mode-context messages from a DIFFERENT mode so the
|
|
136
|
+
* model does not keep believing it is in a prior mode. Messages matching the
|
|
137
|
+
* current mode are kept (the fresh injection from before_agent_start should
|
|
138
|
+
* survive). Pure; returns the SAME array when nothing needs filtering so callers
|
|
139
|
+
* can cheaply detect a no-op.
|
|
100
140
|
*/
|
|
101
|
-
export declare function
|
|
141
|
+
export declare function filterStaleModeContext<T>(messages: T[], currentMode?: PermissionMode): T[];
|
|
142
|
+
/** Legacy alias — the original plan-mode filter name. */
|
|
143
|
+
export declare const filterStalePlanContext: typeof filterStaleModeContext;
|
|
102
144
|
/**
|
|
103
145
|
* Wire the tool_call gate + the /mode command onto a shared mode holder. Default
|
|
104
146
|
* auto, so absent any /mode this is a no-op over today's behavior.
|