@yagni-app/code-staging 0.2.1-staging.1055.1 → 0.3.0-staging.1061.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.
@@ -39,6 +39,7 @@
39
39
  * — useful reference for what data to replicate and how to format it.
40
40
  */
41
41
  import type { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
42
+ import type { ModeHolder, PermissionMode } from "./permission.js";
42
43
  export declare const BRANCH_MAX_WIDTH = 60;
43
44
  /**
44
45
  * Resolve the status bar's left pad from the launcher's `YAGNI_PAD_X` env so it
@@ -90,6 +91,8 @@ export declare function detectGitInfo(cwd: string, home: string | undefined): Gi
90
91
  export declare function renderFooterLines(input: {
91
92
  git: GitInfo;
92
93
  model: string;
94
+ /** Current permission mode; shown on line 2 to the left of the model as "<mode> mode". */
95
+ mode?: PermissionMode | null;
93
96
  usage: UsageTotals;
94
97
  contextPercent: number | null;
95
98
  statuses: string[];
@@ -99,7 +102,7 @@ export declare function renderFooterLines(input: {
99
102
  * and returns the component `setFooter` expects. Called from the
100
103
  * `session_start` handler in index.ts.
101
104
  */
102
- export declare function createYagniFooterFactory(ctx: ExtensionContext): (_tui: unknown, theme: Theme, footerData: ReadonlyFooterDataProvider) => {
105
+ export declare function createYagniFooterFactory(ctx: ExtensionContext, modeHolder?: ModeHolder): (_tui: unknown, theme: Theme, footerData: ReadonlyFooterDataProvider) => {
103
106
  render(width: number): string[];
104
107
  invalidate(): void;
105
108
  dispose(): void;
@@ -221,7 +221,7 @@ export function renderFooterLines(input, theme, width, padX = 0) {
221
221
  line1Parts.push(theme.fg("border", truncateEnd(input.git.branch, BRANCH_MAX_WIDTH)));
222
222
  }
223
223
  const line1 = pad + truncateToWidth(line1Parts.join(sep), contentWidth, dim("…"));
224
- // Line 2: model · ↑in ↓out $cost · ctx%
224
+ // Line 2: [mode ·] model · ↑in ↓out $cost · ctx%
225
225
  const statParts = [];
226
226
  if (input.usage.input)
227
227
  statParts.push(`↑${formatTokens(input.usage.input)}`);
@@ -231,7 +231,10 @@ export function renderFooterLines(input, theme, width, padX = 0) {
231
231
  statParts.push(`$${input.usage.cost.toFixed(3)}`);
232
232
  const stats = statParts.join(" ");
233
233
  const percentText = input.contextPercent === null ? "?" : `${Math.round(input.contextPercent)}%`;
234
- const line2Parts = [dim(input.model)];
234
+ const line2Parts = [];
235
+ if (input.mode)
236
+ line2Parts.push(dim(`${input.mode} mode`));
237
+ line2Parts.push(dim(input.model));
235
238
  if (stats)
236
239
  line2Parts.push(dim(stats));
237
240
  line2Parts.push(theme.fg(contextColor(input.contextPercent), percentText));
@@ -249,7 +252,7 @@ export function renderFooterLines(input, theme, width, padX = 0) {
249
252
  * and returns the component `setFooter` expects. Called from the
250
253
  * `session_start` handler in index.ts.
251
254
  */
252
- export function createYagniFooterFactory(ctx) {
255
+ export function createYagniFooterFactory(ctx, modeHolder) {
253
256
  return (_tui, theme, footerData) => {
254
257
  // Recompute git/worktree info only when the branch actually changes. Optional-
255
258
  // chained: onBranchChange is typed on ReadonlyFooterDataProvider, but the runtime
@@ -273,6 +276,7 @@ export function createYagniFooterFactory(ctx) {
273
276
  return renderFooterLines({
274
277
  git: gitInfo(),
275
278
  model: ctx.model?.id ?? "no-model",
279
+ mode: modeHolder?.get() ?? null,
276
280
  usage: collectUsage(ctx.sessionManager),
277
281
  contextPercent: ctx.getContextUsage()?.percent ?? null,
278
282
  statuses,
@@ -23,7 +23,7 @@ import { isInitDone as defaultIsInitDone, markInitDone as defaultMarkInitDone }
23
23
  import { fetchMcpServers as defaultFetchMcpServers, registerMcpCommand, registerMcpTools, } from "./mcpTools.js";
24
24
  import { registerGoCommand } from "./pipeline/goCommand.js";
25
25
  import { registerGoCompareCommand } from "./pipeline/goCompareCommand.js";
26
- import { DEFAULT_PERMISSION_POLICY, registerPermissionGate } from "./permission.js";
26
+ import { DEFAULT_PERMISSION_POLICY, createModeHolder, registerPermissionGate } from "./permission.js";
27
27
  import { registerSubagents } from "./subagents.js";
28
28
  import { registerTodos } from "./todos.js";
29
29
  import { registerDecisionCommands } from "./decisions.js";
@@ -220,7 +220,9 @@ export async function registerYagni(pi, deps = {}) {
220
220
  // drafts a decision on "don't ask again". Default auto, so still additive.
221
221
  // Mutating MCP tools join write/edit/bash in the gate policy: plan mode
222
222
  // holds them, review mode confirms them.
223
+ const modeHolder = createModeHolder();
223
224
  registerPermissionGate(pi, {
225
+ modeHolder,
224
226
  ...(mcpMutatingTools.length > 0
225
227
  ? {
226
228
  policy: {
@@ -504,7 +506,7 @@ export async function registerYagni(pi, deps = {}) {
504
506
  // context % on line 2, and extension statuses (brand, todos, mode) on
505
507
  // line 3. The factory captures ctx so the footer can read session data
506
508
  // (token stats, context usage) that isn't on the footerData provider.
507
- ctx.ui?.setFooter?.((tui, theme, footerData) => createYagniFooterFactory(ctx)(tui, theme, footerData));
509
+ ctx.ui?.setFooter?.((tui, theme, footerData) => createYagniFooterFactory(ctx, modeHolder)(tui, theme, footerData));
508
510
  }
509
511
  // Best-effort, once at session start: if the token is at or near expiry, say
510
512
  // so via a single notice so a long session does not silently start 401-ing
@@ -29,6 +29,17 @@
29
29
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
30
30
  import { type BlessStore } from "./bless.js";
31
31
  export type PermissionMode = "auto" | "plan" | "review";
32
+ /**
33
+ * A shared, mutable holder for the current permission mode. Both
34
+ * `registerPermissionGate` (which writes on /mode) and the footer factory
35
+ * (which reads on every render) hold a reference, so mode changes appear
36
+ * immediately in the footer's line 2.
37
+ */
38
+ export interface ModeHolder {
39
+ get(): PermissionMode;
40
+ set(m: PermissionMode): void;
41
+ }
42
+ export declare function createModeHolder(initial?: PermissionMode): ModeHolder;
32
43
  /** Which tools each tier acts on, plus the optional grounding-bless predicate. */
33
44
  export interface PermissionPolicy {
34
45
  /** Tools blocked outright in plan mode (write/exec). */
@@ -76,6 +87,8 @@ export interface RegisterPermissionDeps {
76
87
  * never blocks the approved tool call.
77
88
  */
78
89
  onBlessRemember?: (ctx: ExtensionContext, info: BlessRememberInfo) => void | Promise<void>;
90
+ /** Shared holder so the footer can read the live mode on every render. */
91
+ modeHolder?: ModeHolder;
79
92
  }
80
93
  /** The customType tag on injected plan-mode context (filterable later). */
81
94
  export declare const PLAN_CONTEXT_TYPE = "yagni-plan-context";
@@ -27,6 +27,13 @@
27
27
  * the context so the model doesn't keep believing it is restricted.
28
28
  */
29
29
  import { makeBlessStore as defaultMakeBlessStore } from "./bless.js";
30
+ export function createModeHolder(initial = "auto") {
31
+ let current = initial;
32
+ return {
33
+ get: () => current,
34
+ set: (m) => { current = m; },
35
+ };
36
+ }
30
37
  export const DEFAULT_PERMISSION_POLICY = {
31
38
  planBlockTools: ["write", "edit", "bash", "file_ticket", "update_ticket_status"],
32
39
  reviewConfirmTools: ["write", "edit", "bash", "file_ticket", "update_ticket_status"],
@@ -232,6 +239,7 @@ export function registerPermissionGate(pi, deps = {}) {
232
239
  return { messages: filtered };
233
240
  });
234
241
  const paintMode = (ctx) => {
242
+ deps.modeHolder?.set(mode);
235
243
  try {
236
244
  if (ctx.hasUI)
237
245
  ctx.ui.setStatus?.("yagni-mode", MODE_STATUS[mode]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yagni-app/code-staging",
3
- "version": "0.2.1-staging.1055.1",
3
+ "version": "0.3.0-staging.1061.1",
4
4
  "description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
@@ -38,5 +38,5 @@
38
38
  "@earendil-works/pi-tui": "0.84.1",
39
39
  "typebox": "^1.3.11"
40
40
  },
41
- "yagniSourceSha": "154a3d40a32bd82da1a6f152100522efafabb7d1"
41
+ "yagniSourceSha": "44711af698e076c2e718fdd813f4e8ab4b875a0a"
42
42
  }