@yagni-app/code 0.3.0 → 0.3.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/dist/cli.js +12 -0
- package/dist/connectClaudeCode.d.ts +77 -0
- package/dist/connectClaudeCode.js +228 -0
- package/dist/connectCodex.d.ts +75 -0
- package/dist/connectCodex.js +201 -0
- package/dist/crashReport.d.ts +12 -0
- package/dist/crashReport.js +28 -1
- package/dist/extension/approvedPrefixes.d.ts +11 -0
- package/dist/extension/approvedPrefixes.js +30 -0
- package/dist/extension/askAdvisorTool.d.ts +18 -3
- package/dist/extension/askAdvisorTool.js +121 -15
- package/dist/extension/askYagniTool.d.ts +23 -0
- package/dist/extension/askYagniTool.js +42 -2
- package/dist/extension/branding.d.ts +11 -1
- package/dist/extension/branding.js +47 -7
- package/dist/extension/config.d.ts +12 -0
- package/dist/extension/config.js +2 -1
- package/dist/extension/crashReport.d.ts +18 -0
- package/dist/extension/crashReport.js +35 -2
- package/dist/extension/execPolicy.d.ts +17 -1
- package/dist/extension/execPolicy.js +227 -33
- package/dist/extension/flywheel.d.ts +44 -0
- package/dist/extension/flywheel.js +53 -0
- package/dist/extension/footer.d.ts +8 -1
- package/dist/extension/footer.js +33 -19
- package/dist/extension/guardian.d.ts +14 -4
- package/dist/extension/guardian.js +35 -11
- package/dist/extension/index.d.ts +20 -3
- package/dist/extension/index.js +92 -13
- package/dist/extension/mineBeat.d.ts +95 -0
- package/dist/extension/mineBeat.js +193 -0
- package/dist/extension/permission.d.ts +2 -1
- package/dist/extension/permission.js +75 -22
- package/dist/extension/pipeline/goCommand.js +6 -4
- package/dist/extension/pipeline/invocation.d.ts +24 -2
- package/dist/extension/pipeline/invocation.js +30 -2
- package/dist/extension/pipeline/personas.js +2 -2
- package/dist/extension/pipeline/resilience.d.ts +2 -1
- package/dist/extension/pipeline/resilience.js +21 -2
- package/dist/extension/pipeline/runRegistry.d.ts +9 -1
- package/dist/extension/pipeline/runRegistry.js +22 -1
- package/dist/extension/recordDecisionTool.d.ts +8 -0
- package/dist/extension/recordDecisionTool.js +24 -0
- package/dist/extension/subagents.d.ts +7 -1
- package/dist/extension/subagents.js +73 -5
- package/dist/extension/todos.d.ts +28 -1
- package/dist/extension/todos.js +76 -1
- package/dist/extension/ultra.d.ts +27 -0
- package/dist/extension/ultra.js +76 -0
- package/dist/login.d.ts +4 -2
- package/dist/login.js +19 -4
- package/dist/promptEnrichment.d.ts +1 -1
- package/dist/promptEnrichment.js +1 -1
- package/dist/token.d.ts +25 -0
- package/dist/token.js +45 -0
- package/package.json +3 -2
package/dist/extension/footer.js
CHANGED
|
@@ -41,11 +41,20 @@
|
|
|
41
41
|
import { spawnSync } from "node:child_process";
|
|
42
42
|
import { statSync } from "node:fs";
|
|
43
43
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
44
|
-
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
44
|
+
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
45
45
|
export const BRANCH_MAX_WIDTH = 60;
|
|
46
46
|
const WORKTREE_MAX_WIDTH = 30;
|
|
47
47
|
/** Section separator: single space + middle dot + single space. */
|
|
48
48
|
const SEP = " · ";
|
|
49
|
+
const MODE_CYCLE = ["auto", "review", "plan"];
|
|
50
|
+
export function cyclePermissionMode(current) {
|
|
51
|
+
const idx = MODE_CYCLE.indexOf(current);
|
|
52
|
+
return MODE_CYCLE[(idx + 1) % MODE_CYCLE.length];
|
|
53
|
+
}
|
|
54
|
+
export function isShiftTab(data) {
|
|
55
|
+
return matchesKey(data, "shift+tab");
|
|
56
|
+
}
|
|
57
|
+
export const GIT_MUTATING_PATTERN = /\bgit\s+(?:checkout|switch|branch|worktree|reset|restore|rebase|merge|cherry-pick|bisect)\b/;
|
|
49
58
|
/** Default horizontal pad when the launcher didn't forward one (matches outputPad=1). */
|
|
50
59
|
const DEFAULT_PAD_X = 1;
|
|
51
60
|
/**
|
|
@@ -107,10 +116,10 @@ export function collectUsage(sessionManager) {
|
|
|
107
116
|
return totals;
|
|
108
117
|
}
|
|
109
118
|
/** End-cut ellipsis truncation (ANSI-aware) so the branch prefix stays readable. */
|
|
110
|
-
function truncateEnd(text, maxWidth) {
|
|
119
|
+
function truncateEnd(text, maxWidth, ellipsis = "…") {
|
|
111
120
|
if (visibleWidth(text) <= maxWidth)
|
|
112
121
|
return text;
|
|
113
|
-
return truncateToWidth(text, maxWidth,
|
|
122
|
+
return truncateToWidth(text, maxWidth, ellipsis);
|
|
114
123
|
}
|
|
115
124
|
function runGit(args, cwd) {
|
|
116
125
|
try {
|
|
@@ -193,15 +202,23 @@ export function detectGitInfo(cwd, home) {
|
|
|
193
202
|
const worktree = resolveWorktreeLabel(root, branch);
|
|
194
203
|
return { folder: basename(gitMainRepoRoot(root)), inRepo: true, branch, worktree };
|
|
195
204
|
}
|
|
196
|
-
|
|
205
|
+
const MODE_DISPLAY = {
|
|
206
|
+
auto: { text: "⏵⏵ auto mode", color: "accent" },
|
|
207
|
+
review: { text: "✓ review mode", color: "warning" },
|
|
208
|
+
plan: { text: "⏸ plan mode", color: "success" },
|
|
209
|
+
};
|
|
210
|
+
function modeDisplay(mode) {
|
|
211
|
+
return MODE_DISPLAY[mode];
|
|
212
|
+
}
|
|
213
|
+
/** Context color: success below 70, warning 70-90, error above 90. */
|
|
197
214
|
function contextColor(percent) {
|
|
198
215
|
if (percent === null)
|
|
199
|
-
return "
|
|
216
|
+
return "success";
|
|
200
217
|
if (percent > 90)
|
|
201
218
|
return "error";
|
|
202
219
|
if (percent > 70)
|
|
203
220
|
return "warning";
|
|
204
|
-
return "
|
|
221
|
+
return "success";
|
|
205
222
|
}
|
|
206
223
|
/** Pure line-builder, exported for tests. All data injected; colors via theme. */
|
|
207
224
|
export function renderFooterLines(input, theme, width, padX = 0) {
|
|
@@ -218,7 +235,7 @@ export function renderFooterLines(input, theme, width, padX = 0) {
|
|
|
218
235
|
if (input.git.worktree)
|
|
219
236
|
line1Parts.push(theme.fg("warning", `[${input.git.worktree}]`));
|
|
220
237
|
if (input.git.branch)
|
|
221
|
-
line1Parts.push(theme.fg("border", truncateEnd(input.git.branch, BRANCH_MAX_WIDTH)));
|
|
238
|
+
line1Parts.push(theme.fg("border", truncateEnd(input.git.branch, BRANCH_MAX_WIDTH, dim("…"))));
|
|
222
239
|
}
|
|
223
240
|
const line1 = pad + truncateToWidth(line1Parts.join(sep), contentWidth, dim("…"));
|
|
224
241
|
// Line 2: [mode ·] model · ↑in ↓out $cost · ctx%
|
|
@@ -232,8 +249,10 @@ export function renderFooterLines(input, theme, width, padX = 0) {
|
|
|
232
249
|
const stats = statParts.join(" ");
|
|
233
250
|
const percentText = input.contextPercent === null ? "?" : `${Math.round(input.contextPercent)}%`;
|
|
234
251
|
const line2Parts = [];
|
|
235
|
-
if (input.mode)
|
|
236
|
-
|
|
252
|
+
if (input.mode) {
|
|
253
|
+
const modeLabel = modeDisplay(input.mode);
|
|
254
|
+
line2Parts.push(theme.fg(modeLabel.color, modeLabel.text) + dim(" (shift+tab to change)"));
|
|
255
|
+
}
|
|
237
256
|
line2Parts.push(dim(input.model));
|
|
238
257
|
if (stats)
|
|
239
258
|
line2Parts.push(dim(stats));
|
|
@@ -247,17 +266,8 @@ export function renderFooterLines(input, theme, width, padX = 0) {
|
|
|
247
266
|
}
|
|
248
267
|
return lines;
|
|
249
268
|
}
|
|
250
|
-
|
|
251
|
-
* Create a footer factory that captures the session `ctx` (for session data)
|
|
252
|
-
* and returns the component `setFooter` expects. Called from the
|
|
253
|
-
* `session_start` handler in index.ts.
|
|
254
|
-
*/
|
|
255
|
-
export function createYagniFooterFactory(ctx, modeHolder) {
|
|
269
|
+
export function createYagniFooterFactory(ctx, modeHolder, invalidateHandle) {
|
|
256
270
|
return (_tui, theme, footerData) => {
|
|
257
|
-
// Recompute git/worktree info only when the branch actually changes. Optional-
|
|
258
|
-
// chained: onBranchChange is typed on ReadonlyFooterDataProvider, but the runtime
|
|
259
|
-
// provider is whatever pi version is installed — guard so a mismatch can't break
|
|
260
|
-
// footer construction (worst case, git info just doesn't auto-invalidate).
|
|
261
271
|
let gitCache;
|
|
262
272
|
const unsubscribeBranch = footerData.onBranchChange?.(() => {
|
|
263
273
|
gitCache = undefined;
|
|
@@ -268,6 +278,10 @@ export function createYagniFooterFactory(ctx, modeHolder) {
|
|
|
268
278
|
}
|
|
269
279
|
return gitCache;
|
|
270
280
|
};
|
|
281
|
+
if (invalidateHandle) {
|
|
282
|
+
invalidateHandle.invalidateGit = () => { gitCache = undefined; };
|
|
283
|
+
invalidateHandle.requestRender = () => { _tui?.requestRender?.(); };
|
|
284
|
+
}
|
|
271
285
|
return {
|
|
272
286
|
render(width) {
|
|
273
287
|
const statuses = [...footerData.getExtensionStatuses().entries()]
|
|
@@ -37,7 +37,7 @@ export interface GuardianVerdict {
|
|
|
37
37
|
rationale: string;
|
|
38
38
|
}
|
|
39
39
|
export interface GuardianLimits {
|
|
40
|
-
/**
|
|
40
|
+
/** Cap on Guardian reviews within the sliding window ({@link GUARDIAN_REVIEW_WINDOW_MS}). */
|
|
41
41
|
maxReviews: number;
|
|
42
42
|
/** Consecutive denials per turn before the circuit breaker trips. */
|
|
43
43
|
maxConsecutiveDenials: number;
|
|
@@ -47,15 +47,25 @@ export interface GuardianLimits {
|
|
|
47
47
|
export declare const DEFAULT_GUARDIAN_LIMITS: GuardianLimits;
|
|
48
48
|
/**
|
|
49
49
|
* Resolve Guardian limits from the environment. `YAGNI_GUARDIAN_MAX_REVIEWS`
|
|
50
|
-
* overrides the
|
|
51
|
-
* the default (a bad value must never zero out the cap and lock the
|
|
50
|
+
* overrides the sliding-window review cap; anything non-numeric or < 1 falls
|
|
51
|
+
* back to the default (a bad value must never zero out the cap and lock the
|
|
52
|
+
* session).
|
|
52
53
|
*/
|
|
53
54
|
export declare function resolveGuardianLimits(env?: Record<string, string | undefined>): GuardianLimits;
|
|
54
55
|
/** The model tier the Guardian runs on. Configurable via YAGNI_GUARDIAN_TIER. */
|
|
55
56
|
export declare const GUARDIAN_MODEL_TIER = "efficient";
|
|
56
57
|
/** Read-only tools — the Guardian can read files for context but cannot write or execute. */
|
|
57
58
|
export declare const GUARDIAN_TOOLS: string[];
|
|
59
|
+
/**
|
|
60
|
+
* The review-cap window. `reviews` counts consults inside a SLIDING window
|
|
61
|
+
* rather than for the session's lifetime: a 24/7 session (a fleet operator's
|
|
62
|
+
* always-on terminal) must regain review capacity as old consults age out,
|
|
63
|
+
* not hard-block forever after the first N. The cap is a cost/runaway bound,
|
|
64
|
+
* not a safety bound — safety is the verdicts themselves.
|
|
65
|
+
*/
|
|
66
|
+
export declare const GUARDIAN_REVIEW_WINDOW_MS: number;
|
|
58
67
|
export interface GuardianState {
|
|
68
|
+
/** Guardian consults within the last {@link GUARDIAN_REVIEW_WINDOW_MS}. */
|
|
59
69
|
reviews: number;
|
|
60
70
|
consecutiveDenials: number;
|
|
61
71
|
}
|
|
@@ -64,7 +74,7 @@ export interface GuardianStateHandle {
|
|
|
64
74
|
recordReview(outcome: GuardianOutcome): GuardianState;
|
|
65
75
|
resetTurn(): void;
|
|
66
76
|
}
|
|
67
|
-
export declare function makeGuardianState(): GuardianStateHandle;
|
|
77
|
+
export declare function makeGuardianState(now?: () => number): GuardianStateHandle;
|
|
68
78
|
export interface CircuitBreakerResult {
|
|
69
79
|
tripped: boolean;
|
|
70
80
|
reason?: string;
|
|
@@ -29,14 +29,15 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { runStage as defaultRunStage } from "./pipeline/runner.js";
|
|
31
31
|
export const DEFAULT_GUARDIAN_LIMITS = {
|
|
32
|
-
maxReviews:
|
|
32
|
+
maxReviews: 120,
|
|
33
33
|
maxConsecutiveDenials: 3,
|
|
34
34
|
timeoutMs: 15_000,
|
|
35
35
|
};
|
|
36
36
|
/**
|
|
37
37
|
* Resolve Guardian limits from the environment. `YAGNI_GUARDIAN_MAX_REVIEWS`
|
|
38
|
-
* overrides the
|
|
39
|
-
* the default (a bad value must never zero out the cap and lock the
|
|
38
|
+
* overrides the sliding-window review cap; anything non-numeric or < 1 falls
|
|
39
|
+
* back to the default (a bad value must never zero out the cap and lock the
|
|
40
|
+
* session).
|
|
40
41
|
*/
|
|
41
42
|
export function resolveGuardianLimits(env = process.env) {
|
|
42
43
|
const raw = env.YAGNI_GUARDIAN_MAX_REVIEWS?.trim();
|
|
@@ -48,25 +49,48 @@ export function resolveGuardianLimits(env = process.env) {
|
|
|
48
49
|
export const GUARDIAN_MODEL_TIER = "efficient";
|
|
49
50
|
/** Read-only tools — the Guardian can read files for context but cannot write or execute. */
|
|
50
51
|
export const GUARDIAN_TOOLS = ["read"];
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
// --- State ---
|
|
53
|
+
/**
|
|
54
|
+
* The review-cap window. `reviews` counts consults inside a SLIDING window
|
|
55
|
+
* rather than for the session's lifetime: a 24/7 session (a fleet operator's
|
|
56
|
+
* always-on terminal) must regain review capacity as old consults age out,
|
|
57
|
+
* not hard-block forever after the first N. The cap is a cost/runaway bound,
|
|
58
|
+
* not a safety bound — safety is the verdicts themselves.
|
|
59
|
+
*/
|
|
60
|
+
export const GUARDIAN_REVIEW_WINDOW_MS = 60 * 60_000;
|
|
61
|
+
export function makeGuardianState(now = Date.now) {
|
|
62
|
+
const reviewTimes = [];
|
|
63
|
+
let consecutiveDenials = 0;
|
|
64
|
+
const prune = () => {
|
|
65
|
+
const cutoff = now() - GUARDIAN_REVIEW_WINDOW_MS;
|
|
66
|
+
while (reviewTimes.length > 0 && reviewTimes[0] <= cutoff)
|
|
67
|
+
reviewTimes.shift();
|
|
68
|
+
};
|
|
69
|
+
const snapshot = () => ({
|
|
70
|
+
reviews: reviewTimes.length,
|
|
71
|
+
consecutiveDenials,
|
|
72
|
+
});
|
|
53
73
|
return {
|
|
54
|
-
read: () =>
|
|
74
|
+
read: () => {
|
|
75
|
+
prune();
|
|
76
|
+
return snapshot();
|
|
77
|
+
},
|
|
55
78
|
recordReview(outcome) {
|
|
56
|
-
|
|
79
|
+
prune();
|
|
80
|
+
reviewTimes.push(now());
|
|
57
81
|
if (outcome === "deny") {
|
|
58
|
-
|
|
82
|
+
consecutiveDenials += 1;
|
|
59
83
|
}
|
|
60
84
|
else if (outcome === "allow") {
|
|
61
|
-
|
|
85
|
+
consecutiveDenials = 0;
|
|
62
86
|
}
|
|
63
87
|
// "ask" leaves the denial streak UNCHANGED: it is neither a denial nor
|
|
64
88
|
// an exoneration. If it reset the streak, deny/ask/deny/ask would never
|
|
65
89
|
// trip the breaker (round-2 review blocker).
|
|
66
|
-
return
|
|
90
|
+
return snapshot();
|
|
67
91
|
},
|
|
68
92
|
resetTurn() {
|
|
69
|
-
|
|
93
|
+
consecutiveDenials = 0;
|
|
70
94
|
},
|
|
71
95
|
};
|
|
72
96
|
}
|
|
@@ -5,6 +5,7 @@ import { fetchMcpServers as defaultFetchMcpServers } from "./mcpTools.js";
|
|
|
5
5
|
import { type FlushOutcome, type SpoolClientOpts } from "./spool.js";
|
|
6
6
|
import { type TokenProvider } from "./tokenProvider.js";
|
|
7
7
|
import { type CatalogResult, type ContextBrief } from "./config.js";
|
|
8
|
+
import { maybeOfferMiningBeat as defaultMaybeOfferMiningBeat, type MineBeatMarkers } from "./mineBeat.js";
|
|
8
9
|
/**
|
|
9
10
|
* YAGNI Code extension entry point.
|
|
10
11
|
*
|
|
@@ -36,7 +37,21 @@ export interface RegisterYagniDeps {
|
|
|
36
37
|
baseUrl: string;
|
|
37
38
|
getToken: () => string | undefined;
|
|
38
39
|
fetchImpl?: typeof fetch;
|
|
40
|
+
repo?: string;
|
|
39
41
|
}) => Promise<ContextBrief | null>;
|
|
42
|
+
/**
|
|
43
|
+
* The session repo (`owner/name`), resolved from the origin remote before
|
|
44
|
+
* the boot brief fetch so `/context` can carry `repoDecisionCount` for the
|
|
45
|
+
* mining beat. Injectable (no git in tests); undefined outside a repo.
|
|
46
|
+
*/
|
|
47
|
+
resolveRepoFullName?: (cwd: string) => string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The opt-in seeding beat itself (Run 7). Injectable so tests assert it
|
|
50
|
+
* fires only on a startup with an explicitly-zero repo ledger.
|
|
51
|
+
*/
|
|
52
|
+
offerMiningBeat?: typeof defaultMaybeOfferMiningBeat;
|
|
53
|
+
/** The beat's once-per-repo marker store. Injectable (no disk in tests). */
|
|
54
|
+
mineBeatMarkers?: MineBeatMarkers;
|
|
40
55
|
/**
|
|
41
56
|
* Workspace MCP server list seam (YAG-446). The default hits
|
|
42
57
|
* `/api/yagni-code/mcp/servers` and is fail-soft: older backends and
|
|
@@ -122,7 +137,9 @@ export type { RecordDecisionParams } from "./recordDecisionTool.js";
|
|
|
122
137
|
export { runInitPass, runTeamSetup, registerTeamSetupCommand, isFreshWorkspace, readRepoIntake, draftEngineering, summarizeDraft, FRESH_BRIEF_MIN_CHARS, } from "./initPass.js";
|
|
123
138
|
export type { RunInitPassDeps, InitPassOutcome, RunTeamSetupDeps, TeamSetupOutcome, RepoIntake, RepoIntakeFs, EngineeringDraft, DraftTeam, } from "./initPass.js";
|
|
124
139
|
export { isInitDone, markInitDone, initDoneMarkerFile, _setInitDoneHomeForTest } from "./initDone.js";
|
|
125
|
-
export { brandSystemPrompt, YAGNI_IDENTITY, YAGNI_IDENTITY_DRIVER, BRAND_NAME } from "./branding.js";
|
|
140
|
+
export { brandSystemPrompt, YAGNI_IDENTITY, YAGNI_IDENTITY_DRIVER, YAGNI_IDENTITY_ULTRA, BRAND_NAME } from "./branding.js";
|
|
141
|
+
export { createUltraHolder, registerUltraCommand } from "./ultra.js";
|
|
142
|
+
export type { UltraHolder } from "./ultra.js";
|
|
126
143
|
export { attributionHeaders, isDriverCaller, fetchCatalog, getToken, getWorkspaceId, resolveBaseUrl, sanitizeCallerSegment, } from "./config.js";
|
|
127
144
|
export type { CatalogResult, FetchCatalogOptions, ModelEntry } from "./config.js";
|
|
128
145
|
export { buildYagniProvider } from "./provider.js";
|
|
@@ -139,9 +156,9 @@ export type { VerifyCommand, VerifyOutcome } from "./pipeline/verify.js";
|
|
|
139
156
|
export { blindStages, reportOnlyStages, makeGroundedVsBlindEval, formatComparisonReport } from "./pipeline/eval.js";
|
|
140
157
|
export type { ComparisonReport, LaneFit, LaneOutcome } from "./pipeline/eval.js";
|
|
141
158
|
export { registerGoCompareCommand } from "./pipeline/goCompareCommand.js";
|
|
142
|
-
export { registerSubagents, makeSubagentTool, discoverSubagents, parseAgentMarkdown, buildSubagentStage, formatAgentList, mapModelTier, SUBAGENT_TOOL_NAME, GENERAL_AGENT_NAME, MAX_PARALLEL_SUBAGENTS, DEFAULT_SUBAGENT_TOOLS, } from "./subagents.js";
|
|
159
|
+
export { registerSubagents, makeSubagentTool, discoverSubagents, parseAgentMarkdown, buildSubagentStage, formatAgentList, mapModelTier, SUBAGENT_TOOL_NAME, GENERAL_AGENT_NAME, MAX_PARALLEL_SUBAGENTS, MAX_PARALLEL_SUBAGENTS_ULTRA, DEFAULT_SUBAGENT_TOOLS, } from "./subagents.js";
|
|
143
160
|
export type { SubagentDef, SubagentSource } from "./subagents.js";
|
|
144
|
-
export { registerTodos, makeTodoTool, normalizeTodos, reconstructTodos, renderTodoWidget, formatTodoList, todoSummary, TODO_TOOL_NAME, MAX_TODOS, } from "./todos.js";
|
|
161
|
+
export { registerTodos, makeTodoTool, normalizeTodos, reconstructTodos, renderTodoWidget, formatTodoList, formatTodoReminder, shouldRemindTodos, todoSummary, TODO_TOOL_NAME, TODO_REMINDER_TURNS, MAX_TODOS, } from "./todos.js";
|
|
145
162
|
export type { TodoItem, TodoStatus, TodoTheme } from "./todos.js";
|
|
146
163
|
export { decideGate, registerPermissionGate, filterStaleModeContext, filterStalePlanContext, buildModeContextMessage, DEFAULT_PERMISSION_POLICY, MODE_CONTEXT_TYPE, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission.js";
|
|
147
164
|
export { classifyCommand, DEFAULT_EXEC_POLICY, } from "./execPolicy.js";
|
package/dist/extension/index.js
CHANGED
|
@@ -14,13 +14,13 @@ import { registerCmuxBridge } from "./cmux/index.js";
|
|
|
14
14
|
import { makeRecordEngineeringContextTool } from "./recordContextTool.js";
|
|
15
15
|
import { makeRecordDecisionTool } from "./recordDecisionTool.js";
|
|
16
16
|
import { makeSuggestNextWorkTool } from "./nextWorkTool.js";
|
|
17
|
-
import { BRAND_NAME, brandSystemPrompt, brandingDisabled, buildMastheadString, YAGNI_IDENTITY_DRIVER } from "./branding.js";
|
|
17
|
+
import { BRAND_NAME, brandSystemPrompt, brandingDisabled, buildMastheadString, YAGNI_IDENTITY_DRIVER, YAGNI_IDENTITY_ULTRA } from "./branding.js";
|
|
18
18
|
import { claudeRulesSection } from "./claudeRules.js";
|
|
19
19
|
import { registerCostCommand } from "./costHud.js";
|
|
20
20
|
import { isDebug } from "./diagnostics.js";
|
|
21
21
|
import { droppedSessionRuns, sessionRunIds } from "./sessionRuns.js";
|
|
22
22
|
import { codeStateHome } from "./stateHome.js";
|
|
23
|
-
import { createYagniFooterFactory, formatCwd } from "./footer.js";
|
|
23
|
+
import { createYagniFooterFactory, cyclePermissionMode, formatCwd, GIT_MUTATING_PATTERN, isShiftTab } from "./footer.js";
|
|
24
24
|
import { RerouteNotifier } from "./rerouteNotice.js";
|
|
25
25
|
import { isFreshWorkspace, registerTeamSetupCommand, runInitPass as defaultRunInitPass } from "./initPass.js";
|
|
26
26
|
import { isInitDone as defaultIsInitDone, markInitDone as defaultMarkInitDone } from "./initDone.js";
|
|
@@ -29,6 +29,7 @@ import { registerGoCommand } from "./pipeline/goCommand.js";
|
|
|
29
29
|
import { registerGoCompareCommand } from "./pipeline/goCompareCommand.js";
|
|
30
30
|
import { DEFAULT_PERMISSION_POLICY, createModeHolder, registerPermissionGate } from "./permission.js";
|
|
31
31
|
import { registerSubagents } from "./subagents.js";
|
|
32
|
+
import { createUltraHolder, registerUltraCommand } from "./ultra.js";
|
|
32
33
|
import { registerTodos } from "./todos.js";
|
|
33
34
|
import { registerDecisionCommands } from "./decisions.js";
|
|
34
35
|
import { makeDecisionCapture } from "./decisionCapture.js";
|
|
@@ -40,6 +41,8 @@ import { makeAuthedFetch, makeTokenProvider } from "./tokenProvider.js";
|
|
|
40
41
|
import { attributionHeaders, fetchCatalog as defaultFetchCatalog, fetchContextBrief as defaultFetchContextBrief, getToken, getTokenExpiresAt as defaultGetTokenExpiresAt, getWorkspaceId as defaultGetWorkspaceId, isDriverCaller, resolveBaseUrl, tokenExpiryNotice, } from "./config.js";
|
|
41
42
|
import { buildYagniProvider } from "./provider.js";
|
|
42
43
|
import { registerChipEditor } from "./chipEditor.js";
|
|
44
|
+
import { defaultMineBeatGit, fileMineBeatMarkers, maybeOfferMiningBeat as defaultMaybeOfferMiningBeat, } from "./mineBeat.js";
|
|
45
|
+
import { makeFlywheelState } from "./flywheel.js";
|
|
43
46
|
function isEvalMode(env = process.env) {
|
|
44
47
|
return env.YAGNI_CODE_EVAL_MODE === "1";
|
|
45
48
|
}
|
|
@@ -143,7 +146,11 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
143
146
|
// child, a subagent, or an advisor consult).
|
|
144
147
|
pi.registerProvider("yagni", buildYagniProvider(catalog, baseUrl, attributionHeaders(deps.env)));
|
|
145
148
|
const toolOpts = { baseUrl, getToken: getTokenFn, fetchImpl: authedFetch };
|
|
146
|
-
|
|
149
|
+
// One flywheel state per session, shared by ask_yagni (caps how many
|
|
150
|
+
// no-position record suggestions reach the model) and record_decision
|
|
151
|
+
// (flywheel-attributed records send dedupe: true). Run 7.
|
|
152
|
+
const flywheelState = makeFlywheelState();
|
|
153
|
+
pi.registerTool(makeAskYagniTool({ ...toolOpts, flywheel: flywheelState, getRepo: () => sessionRepo }));
|
|
147
154
|
// Ticket write-back (spec 2026-08-09): explicit user-intent writes to the
|
|
148
155
|
// workspace tracker, attributed to the developer via per-user credentials.
|
|
149
156
|
if (!evalMode) {
|
|
@@ -171,7 +178,7 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
171
178
|
pi.registerTool(makeRecordEngineeringContextTool(toolOpts));
|
|
172
179
|
// The capture half of the judgment loop: bank a product-intent decision so
|
|
173
180
|
// ask_yagni answers the same question next time instead of interrupting a human.
|
|
174
|
-
pi.registerTool(makeRecordDecisionTool(toolOpts));
|
|
181
|
+
pi.registerTool(makeRecordDecisionTool({ ...toolOpts, flywheel: flywheelState }));
|
|
175
182
|
}
|
|
176
183
|
// The visible checklist for multi-step work: the todo_write tool, its
|
|
177
184
|
// above-editor widget, and /todos. Branch-replayed, so forks and resumes
|
|
@@ -185,7 +192,12 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
185
192
|
// The general subagent tool: delegate self-contained tasks (optionally in
|
|
186
193
|
// parallel) to fresh-context agents defined in .claude/agents / .pi/agents,
|
|
187
194
|
// riding the /go pipeline's child runner. /agents lists what's available.
|
|
188
|
-
|
|
195
|
+
// Ultra mode (/ultra) is an explicit orchestration dial: it swaps the
|
|
196
|
+
// driver's delegation identity for the diamond directive (see the
|
|
197
|
+
// before_agent_start handler below) and widens the tool's fan-out ceiling.
|
|
198
|
+
const ultraHolder = createUltraHolder();
|
|
199
|
+
registerSubagents(pi, { isUltra: () => ultraHolder.get() });
|
|
200
|
+
registerUltraCommand(pi, ultraHolder);
|
|
189
201
|
// Shared fetch timeout for the small, interactive display-path reads below
|
|
190
202
|
// (/cost's spend + headroom, and Task 8's per-run spend for /go's summary):
|
|
191
203
|
// 5s, not the 10s other boot/grounding fetches use, because the user is
|
|
@@ -238,6 +250,7 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
238
250
|
// Mutating MCP tools join write/edit/bash in the gate policy: plan mode
|
|
239
251
|
// holds them, review mode confirms them.
|
|
240
252
|
const modeHolder = createModeHolder();
|
|
253
|
+
const footerInvalidateHandle = { invalidateGit: () => { }, requestRender: () => { } };
|
|
241
254
|
const guardianState = makeGuardianState();
|
|
242
255
|
// Disabled by the local env override OR the workspace kill switch
|
|
243
256
|
// (yagni_code.guardian, read from the catalog response at launch). The env
|
|
@@ -262,7 +275,9 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
262
275
|
catch { /* logging must never break the session */ }
|
|
263
276
|
};
|
|
264
277
|
// YAG-510: persisted "don't ask again" grants, per-repo keyed. Loaded once
|
|
265
|
-
// at startup (grants added by other concurrent sessions appear next launch
|
|
278
|
+
// at startup (grants added by other concurrent sessions appear next launch —
|
|
279
|
+
// the startup load is the trust boundary; live reload was reviewed and
|
|
280
|
+
// rejected as a same-session self-authorization path, PR #1698).
|
|
266
281
|
const sessionGrants = evalMode ? [] : loadGrants();
|
|
267
282
|
const GUARDIAN_EVENT_TIMEOUT_MS = 5_000;
|
|
268
283
|
registerPermissionGate(pi, {
|
|
@@ -452,11 +467,27 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
452
467
|
// must never block the agent), then inject it into the system prompt so the
|
|
453
468
|
// very first turn already knows how this company and codebase work.
|
|
454
469
|
const fetchBrief = deps.fetchContextBrief ?? defaultFetchContextBrief;
|
|
470
|
+
// Resolved BEFORE the brief fetch so /context can answer repoDecisionCount
|
|
471
|
+
// in the same round-trip (the mining beat's signal). Best-effort: undefined
|
|
472
|
+
// outside a git repo or without an origin remote.
|
|
473
|
+
const resolveRepoFn = deps.resolveRepoFullName ?? ((cwd) => defaultMineBeatGit.repoFullName(cwd));
|
|
474
|
+
let sessionRepo;
|
|
475
|
+
try {
|
|
476
|
+
sessionRepo = evalMode ? undefined : resolveRepoFn(process.cwd());
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
sessionRepo = undefined;
|
|
480
|
+
}
|
|
455
481
|
let contextBrief;
|
|
456
482
|
let briefResult = null;
|
|
457
483
|
if (!evalMode) {
|
|
458
484
|
try {
|
|
459
|
-
briefResult = await fetchBrief({
|
|
485
|
+
briefResult = await fetchBrief({
|
|
486
|
+
baseUrl,
|
|
487
|
+
getToken: getTokenFn,
|
|
488
|
+
fetchImpl: authedFetch,
|
|
489
|
+
repo: sessionRepo,
|
|
490
|
+
});
|
|
460
491
|
contextBrief = briefResult?.brief?.trim() ? briefResult.brief : undefined;
|
|
461
492
|
}
|
|
462
493
|
catch {
|
|
@@ -517,13 +548,19 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
517
548
|
// assembled prompt passes through byte-exact (no rewrite, no brief
|
|
518
549
|
// injection, no rules section, no delegation identity).
|
|
519
550
|
const noBranding = brandingDisabled(env);
|
|
520
|
-
|
|
551
|
+
// Resolved per turn (not once at activation): /ultra can flip the driver
|
|
552
|
+
// between the delegation and diamond identities mid-session.
|
|
553
|
+
const isDriver = isDriverCaller(env);
|
|
521
554
|
pi.on("before_agent_start", (event) => noBranding
|
|
522
555
|
? undefined
|
|
523
556
|
: {
|
|
524
557
|
systemPrompt: brandSystemPrompt(event.systemPrompt, {
|
|
525
558
|
contextBrief,
|
|
526
|
-
identity
|
|
559
|
+
identity: isDriver
|
|
560
|
+
? ultraHolder.get()
|
|
561
|
+
? YAGNI_IDENTITY_ULTRA
|
|
562
|
+
: YAGNI_IDENTITY_DRIVER
|
|
563
|
+
: undefined,
|
|
527
564
|
rulesSection,
|
|
528
565
|
}),
|
|
529
566
|
});
|
|
@@ -681,7 +718,15 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
681
718
|
// context % on line 2, and extension statuses (brand, todos, mode) on
|
|
682
719
|
// line 3. The factory captures ctx so the footer can read session data
|
|
683
720
|
// (token stats, context usage) that isn't on the footerData provider.
|
|
684
|
-
ctx.ui?.setFooter?.((tui, theme, footerData) => createYagniFooterFactory(ctx, modeHolder)(tui, theme, footerData));
|
|
721
|
+
ctx.ui?.setFooter?.((tui, theme, footerData) => createYagniFooterFactory(ctx, modeHolder, footerInvalidateHandle)(tui, theme, footerData));
|
|
722
|
+
ctx.ui?.onTerminalInput?.((data) => {
|
|
723
|
+
if (isShiftTab(data)) {
|
|
724
|
+
modeHolder.set(cyclePermissionMode(modeHolder.get()));
|
|
725
|
+
footerInvalidateHandle.requestRender();
|
|
726
|
+
return { consume: true };
|
|
727
|
+
}
|
|
728
|
+
return undefined;
|
|
729
|
+
});
|
|
685
730
|
// Label the collapsed chain-of-thought line so users know it is reasoning
|
|
686
731
|
// and how to reveal the full trace. Harmless when reasoning is expanded
|
|
687
732
|
// (the label only shows on hidden thinking blocks). Fails closed to pi's
|
|
@@ -755,6 +800,38 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
755
800
|
// The init pass is best-effort; a failure never blocks the session.
|
|
756
801
|
}
|
|
757
802
|
}
|
|
803
|
+
// Run 7: the opt-in repo seeding beat. Fires only on a genuine startup
|
|
804
|
+
// when /context reported an explicitly-empty repo-scoped ledger, once per
|
|
805
|
+
// repo (marker store). Fully fail-soft — a beat failure never blocks the
|
|
806
|
+
// session.
|
|
807
|
+
if (event.reason === "startup" && !evalMode) {
|
|
808
|
+
try {
|
|
809
|
+
const offerBeat = deps.offerMiningBeat ?? defaultMaybeOfferMiningBeat;
|
|
810
|
+
const markers = deps.mineBeatMarkers ?? fileMineBeatMarkers(codeStateHome(null, env));
|
|
811
|
+
await offerBeat(ctx, {
|
|
812
|
+
repoFullName: sessionRepo,
|
|
813
|
+
repoDecisionCount: briefResult?.repoDecisionCount,
|
|
814
|
+
cwd: process.cwd(),
|
|
815
|
+
baseUrl,
|
|
816
|
+
getToken: getTokenFn,
|
|
817
|
+
fetchImpl: authedFetch,
|
|
818
|
+
markers,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
catch {
|
|
822
|
+
// Seeding is best-effort; a failure never blocks the session.
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
pi.on("tool_result", (event) => {
|
|
827
|
+
if (event.toolName !== "bash")
|
|
828
|
+
return;
|
|
829
|
+
const command = typeof event.input?.command === "string"
|
|
830
|
+
? event.input.command
|
|
831
|
+
: "";
|
|
832
|
+
if (GIT_MUTATING_PATTERN.test(command)) {
|
|
833
|
+
footerInvalidateHandle.invalidateGit();
|
|
834
|
+
}
|
|
758
835
|
});
|
|
759
836
|
}
|
|
760
837
|
export default async function (pi) {
|
|
@@ -781,7 +858,9 @@ export { recordDecision } from "./recordDecisionTool.js";
|
|
|
781
858
|
export { runInitPass, runTeamSetup, registerTeamSetupCommand, isFreshWorkspace, readRepoIntake, draftEngineering, summarizeDraft, FRESH_BRIEF_MIN_CHARS, } from "./initPass.js";
|
|
782
859
|
// Onramp Door B (F2a): the one-time init-pass idempotency marker.
|
|
783
860
|
export { isInitDone, markInitDone, initDoneMarkerFile, _setInitDoneHomeForTest } from "./initDone.js";
|
|
784
|
-
export { brandSystemPrompt, YAGNI_IDENTITY, YAGNI_IDENTITY_DRIVER, BRAND_NAME } from "./branding.js";
|
|
861
|
+
export { brandSystemPrompt, YAGNI_IDENTITY, YAGNI_IDENTITY_DRIVER, YAGNI_IDENTITY_ULTRA, BRAND_NAME } from "./branding.js";
|
|
862
|
+
// Ultra mode (/ultra): the aggressive fan-out/verify/synthesize dial.
|
|
863
|
+
export { createUltraHolder, registerUltraCommand } from "./ultra.js";
|
|
785
864
|
export { attributionHeaders, isDriverCaller, fetchCatalog, getToken, getWorkspaceId, resolveBaseUrl, sanitizeCallerSegment, } from "./config.js";
|
|
786
865
|
export { buildYagniProvider } from "./provider.js";
|
|
787
866
|
export { registerGoCommand } from "./pipeline/goCommand.js";
|
|
@@ -796,9 +875,9 @@ export { buildVerifyEnv, detectVerifyCommand, makeRunVerify, parseVerifyFailures
|
|
|
796
875
|
export { blindStages, reportOnlyStages, makeGroundedVsBlindEval, formatComparisonReport } from "./pipeline/eval.js";
|
|
797
876
|
export { registerGoCompareCommand } from "./pipeline/goCompareCommand.js";
|
|
798
877
|
// The general subagent tool: Claude Code-format agent discovery + fan-out.
|
|
799
|
-
export { registerSubagents, makeSubagentTool, discoverSubagents, parseAgentMarkdown, buildSubagentStage, formatAgentList, mapModelTier, SUBAGENT_TOOL_NAME, GENERAL_AGENT_NAME, MAX_PARALLEL_SUBAGENTS, DEFAULT_SUBAGENT_TOOLS, } from "./subagents.js";
|
|
878
|
+
export { registerSubagents, makeSubagentTool, discoverSubagents, parseAgentMarkdown, buildSubagentStage, formatAgentList, mapModelTier, SUBAGENT_TOOL_NAME, GENERAL_AGENT_NAME, MAX_PARALLEL_SUBAGENTS, MAX_PARALLEL_SUBAGENTS_ULTRA, DEFAULT_SUBAGENT_TOOLS, } from "./subagents.js";
|
|
800
879
|
// The session todo checklist: todo_write tool, widget renderer, /todos.
|
|
801
|
-
export { registerTodos, makeTodoTool, normalizeTodos, reconstructTodos, renderTodoWidget, formatTodoList, todoSummary, TODO_TOOL_NAME, MAX_TODOS, } from "./todos.js";
|
|
880
|
+
export { registerTodos, makeTodoTool, normalizeTodos, reconstructTodos, renderTodoWidget, formatTodoList, formatTodoReminder, shouldRemindTodos, todoSummary, TODO_TOOL_NAME, TODO_REMINDER_TURNS, MAX_TODOS, } from "./todos.js";
|
|
802
881
|
// P3 + W4: the permission gate seam (decideGate is pure; policy injectable) plus
|
|
803
882
|
// the session bless-with-remember capture hook.
|
|
804
883
|
export { decideGate, registerPermissionGate, filterStaleModeContext, filterStalePlanContext, buildModeContextMessage, DEFAULT_PERMISSION_POLICY, MODE_CONTEXT_TYPE, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission.js";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The opt-in repo seeding beat (org plane, Run 7).
|
|
3
|
+
*
|
|
4
|
+
* On the first interactive session in a repo whose repo-scoped decision
|
|
5
|
+
* ledger is empty (`/context?repo=` reports `repoDecisionCount: 0`), YAGNI
|
|
6
|
+
* Code offers — once — to read the repo's own decision record and seed the
|
|
7
|
+
* ledger: "Read this repo's ADRs and docs and seed the decision ledger?".
|
|
8
|
+
* One keystroke, then the backend extracts, dedupes, and banks at most 12
|
|
9
|
+
* `repo_mined` asserted rows, and the beat prints the banked count plus the
|
|
10
|
+
* Library review deep link.
|
|
11
|
+
*
|
|
12
|
+
* Never automatic, never a nag: a decline writes a per-repo marker so the
|
|
13
|
+
* beat does not recur; an accepted pass marks the repo only after the server
|
|
14
|
+
* confirms (a transport failure leaves the offer available next session).
|
|
15
|
+
* The corpus is collected at git HEAD (same echo-chamber guard as
|
|
16
|
+
* repoDocs.ts) — ADR directories, agent-instruction root docs, top-level
|
|
17
|
+
* docs/*.md, and recent merge subjects — bounded client-side to the same
|
|
18
|
+
* limits the backend enforces.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Client-side bounds on the mining corpus — mirror of the backend's
|
|
22
|
+
* MINE_INPUT_LIMITS so nothing is silently truncated server-side.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MINE_BEAT_LIMITS: {
|
|
25
|
+
readonly maxDocs: 40;
|
|
26
|
+
readonly maxPathChars: 300;
|
|
27
|
+
readonly maxExcerptChars: 8000;
|
|
28
|
+
readonly maxCommitSubjects: 200;
|
|
29
|
+
readonly maxSubjectChars: 200;
|
|
30
|
+
};
|
|
31
|
+
/** One document offered to the mining pass. */
|
|
32
|
+
export interface MineBeatDoc {
|
|
33
|
+
path: string;
|
|
34
|
+
excerpt: string;
|
|
35
|
+
}
|
|
36
|
+
/** Injectable git seam (default: execFileSync; every call fail-soft). */
|
|
37
|
+
export interface MineBeatGit {
|
|
38
|
+
/** `owner/name` from the origin remote, or undefined outside a repo. */
|
|
39
|
+
repoFullName: (cwd: string) => string | undefined;
|
|
40
|
+
/** Tracked paths at HEAD, or null when cwd is not a usable git tree. */
|
|
41
|
+
listAtHead: (cwd: string) => string[] | null;
|
|
42
|
+
/** One file's HEAD content, or null when unreadable. */
|
|
43
|
+
readAtHead: (cwd: string, rel: string) => string | null;
|
|
44
|
+
/** Recent merge-commit subjects (fallback: plain subjects on squash repos). */
|
|
45
|
+
mergeSubjects: (cwd: string, max: number) => string[];
|
|
46
|
+
}
|
|
47
|
+
/** Parse `owner/name` from an origin remote URL (ssh or https). */
|
|
48
|
+
export declare function parseRepoFullName(remote: string | null | undefined): string | undefined;
|
|
49
|
+
export declare const defaultMineBeatGit: MineBeatGit;
|
|
50
|
+
/** Is this HEAD-tracked path part of the repo's decision record? */
|
|
51
|
+
export declare function isMineCandidate(rel: string): boolean;
|
|
52
|
+
/** Collect the HEAD-era mining corpus, bounded to MINE_BEAT_LIMITS. */
|
|
53
|
+
export declare function collectMineCorpus(cwd: string, gitImpl?: MineBeatGit): {
|
|
54
|
+
docs: MineBeatDoc[];
|
|
55
|
+
commitSubjects: string[];
|
|
56
|
+
};
|
|
57
|
+
/** Injectable marker-store seam (default: a JSON file per repo on disk). */
|
|
58
|
+
export interface MineBeatMarkers {
|
|
59
|
+
has: (repoFullName: string) => boolean;
|
|
60
|
+
write: (repoFullName: string, status: "declined" | "seeded") => void;
|
|
61
|
+
}
|
|
62
|
+
/** Filesystem path of one repo's beat marker under the state home. */
|
|
63
|
+
export declare function mineMarkerPath(stateHome: string, repoFullName: string): string;
|
|
64
|
+
export declare function fileMineBeatMarkers(stateHome: string): MineBeatMarkers;
|
|
65
|
+
/** The subset of the session-start ctx the beat needs. */
|
|
66
|
+
export interface MineBeatUi {
|
|
67
|
+
hasUI: boolean;
|
|
68
|
+
ui: {
|
|
69
|
+
confirm?: (title: string, message: string) => Promise<boolean>;
|
|
70
|
+
notify: (message: string, level: "info" | "error") => void;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export interface OfferMiningBeatOptions {
|
|
74
|
+
repoFullName: string | undefined;
|
|
75
|
+
/** From `/context?repo=`; the beat fires ONLY on an explicit 0. */
|
|
76
|
+
repoDecisionCount: number | undefined;
|
|
77
|
+
cwd: string;
|
|
78
|
+
baseUrl: string;
|
|
79
|
+
getToken: () => string | undefined;
|
|
80
|
+
fetchImpl?: typeof fetch;
|
|
81
|
+
markers: MineBeatMarkers;
|
|
82
|
+
gitImpl?: MineBeatGit;
|
|
83
|
+
collect?: typeof collectMineCorpus;
|
|
84
|
+
}
|
|
85
|
+
export interface MiningBeatOutcome {
|
|
86
|
+
offered: boolean;
|
|
87
|
+
accepted?: boolean;
|
|
88
|
+
banked?: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Offer (once per repo) and, on yes, run the seeding pass. Fail-soft
|
|
92
|
+
* throughout: any error surfaces as a notify at most, never a broken session.
|
|
93
|
+
*/
|
|
94
|
+
export declare function maybeOfferMiningBeat(ctx: MineBeatUi, opts: OfferMiningBeatOptions): Promise<MiningBeatOutcome>;
|
|
95
|
+
//# sourceMappingURL=mineBeat.d.ts.map
|