@yagni-app/code-staging 1.1.0-staging.1334.1 → 1.1.0-staging.1335.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.
@@ -541,6 +541,7 @@ export async function registerYagni(pi, deps = {}) {
541
541
  cwd: process.cwd(),
542
542
  env,
543
543
  hasUI: (ctx) => ctx.hasUI,
544
+ onShellResolutionRetry: (outcome) => telemetry.sandboxShellResolutionRetry(outcome),
544
545
  // Anchors project-protected paths (.yagni-code + its config.json in
545
546
  // denyWrite) and project-sourced permission rules to the repo the
546
547
  // session runs in — same root the gate uses for its rule anchoring.
@@ -61,8 +61,27 @@ export declare function makeSandboxSpawnHook(manager: YagniSandboxManager): Bash
61
61
  * Wrap a raw command for OS-sandboxed execution. Returns the original string
62
62
  * when the manager is not initialized (fail-open to plain execution — the
63
63
  * permission gate still ran; the sandbox simply is not active).
64
+ *
65
+ * Transient shell-resolution failures (isShellResolutionFailure — srt's 1s
66
+ * `which` spawn timing out under load) are retried once after a short
67
+ * backoff; a second failure surfaces the original error. Any other error
68
+ * propagates immediately, no retry.
64
69
  */
65
- export declare function preWrappedCommand(manager: YagniSandboxManager, command: string, binShell?: string): Promise<string>;
70
+ export interface PreWrapRetryOpts {
71
+ /** Abort the backoff sleep (the tool call's signal). */
72
+ signal?: AbortSignal;
73
+ /** Backoff before the single retry. Tests pass a tiny value to stay fast. */
74
+ backoffMs?: number;
75
+ /** Instrumentation: fired once, with the retry's terminal outcome. */
76
+ onRetry?: (outcome: "recovered" | "exhausted") => void;
77
+ }
78
+ export declare function preWrappedCommand(manager: YagniSandboxManager, command: string, binShell?: string, opts?: PreWrapRetryOpts): Promise<string>;
79
+ export declare function _setShellResolutionRetryBackoffForTest(ms: number | null): void;
80
+ /** srt's transient shell-resolution failure: `Shell '<name>' not found in
81
+ * PATH`, thrown from wrapCommandWithSandbox when which.js's 1s
82
+ * spawnSync('which') times out under load. Self-healing after ~15–30s;
83
+ * preWrappedCommand retries it once. */
84
+ export declare function isShellResolutionFailure(err: unknown): boolean;
66
85
  /**
67
86
  * Detect "Operation not permitted" style sandbox denials in bash output so
68
87
  * callers (tool_result) can annotate and the model can react. Returns the
@@ -21,6 +21,8 @@
21
21
  * lessons (process-group kill, stdio release).
22
22
  */
23
23
  import { existsSync } from "node:fs";
24
+ import { logEvent } from "../errorSink.js";
25
+ import { scrubSecrets } from "../pipeline/scrubSecrets.js";
24
26
  function stripLeadingSafeEnvVars(command) {
25
27
  // Best-effort normalization for excludedCommands matching (not a security
26
28
  // boundary — the permission gate is). Strips leading VAR=val pairs whose
@@ -112,15 +114,116 @@ export function makeSandboxSpawnHook(manager) {
112
114
  env: { ...ctx.env, ...extraEnv },
113
115
  });
114
116
  }
115
- /**
116
- * Wrap a raw command for OS-sandboxed execution. Returns the original string
117
- * when the manager is not initialized (fail-open to plain execution — the
118
- * permission gate still ran; the sandbox simply is not active).
119
- */
120
- export async function preWrappedCommand(manager, command, binShell) {
117
+ export async function preWrappedCommand(manager, command, binShell, opts = {}) {
121
118
  if (!manager.initialized)
122
119
  return command;
123
- return manager.wrapWithSandbox(command, binShell);
120
+ try {
121
+ return await manager.wrapWithSandbox(command, binShell);
122
+ }
123
+ catch (err) {
124
+ if (!isShellResolutionFailure(err))
125
+ throw err;
126
+ }
127
+ // The failed attempt may have left per-command state behind (the wrap can
128
+ // start helpers before it throws); clear it so the retry starts clean and
129
+ // the exec path's cleanupAfterCommand never sees double state. Best-effort:
130
+ // a cleanup throw must never swallow the original error or kill the retry.
131
+ try {
132
+ manager.cleanupAfterCommand();
133
+ }
134
+ catch (err) {
135
+ // Best-effort cleanup failed — the retry still runs (the exec path
136
+ // cleans up again); a fully silent swallow would hide a leaked-helper
137
+ // state, so the failure gets a visible trail. The message rides the
138
+ // line (the error class alone is always the literal "Error" for plain
139
+ // throws) and is run through scrubSecrets at WRITE time so the line
140
+ // is credential-scrubbed before any reader. That is pattern-scrub
141
+ // only, not a general redactor — readSessionTrail adds the same
142
+ // pattern pass at read time; the durable errors-*.jsonl stays
143
+ // unredacted by the sink's documented design (local file, never
144
+ // uploaded raw). Same posture as sandbox_persist_failed.
145
+ logEvent({
146
+ source: "sandbox",
147
+ level: "warn",
148
+ event: "shell_retry_cleanup_failed",
149
+ fields: {
150
+ error: scrubSecrets(err instanceof Error ? `${err.constructor.name}: ${err.message}` : String(err)),
151
+ },
152
+ });
153
+ }
154
+ await sleep(effectiveBackoffMs(opts.backoffMs), opts.signal);
155
+ try {
156
+ const wrapped = await manager.wrapWithSandbox(command, binShell);
157
+ opts.onRetry?.("recovered");
158
+ return wrapped;
159
+ }
160
+ catch (err) {
161
+ // `exhausted` is the shell-resolution health signal — report it only
162
+ // when the second failure is the SAME transient; an unrelated retry
163
+ // error propagates as-is without polluting the metric.
164
+ if (isShellResolutionFailure(err))
165
+ opts.onRetry?.("exhausted");
166
+ throw err;
167
+ }
168
+ }
169
+ /** Default backoff before the single shell-resolution retry. The observed
170
+ * upstream transient self-heals in ~15–30s; this is a bounded bridge, not
171
+ * a guarantee — a still-failing second attempt surfaces the original error. */
172
+ const SHELL_RESOLUTION_RETRY_BACKOFF_MS = 2000;
173
+ /** Test seam for the backoff: session-layer tests (composition, user_bash)
174
+ * drive the retry without a backoffMs opt, so they shrink the module-level
175
+ * default instead of sleeping a real 2s. Module-scoped (never a globalThis
176
+ * key — a leaked value there would silently shrink the production backoff);
177
+ * restore in the same describe that sets it. */
178
+ let shellRetryBackoffMsForTest = null;
179
+ export function _setShellResolutionRetryBackoffForTest(ms) {
180
+ shellRetryBackoffMsForTest = ms;
181
+ }
182
+ function effectiveBackoffMs(override) {
183
+ return override ?? shellRetryBackoffMsForTest ?? SHELL_RESOLUTION_RETRY_BACKOFF_MS;
184
+ }
185
+ function sleep(ms, signal) {
186
+ // The abort rejection carries the caller's own abort reason when the
187
+ // signal has a real one — a context-free "aborted" would replace the
188
+ // meaningful error the caller (and the model) should see. Node's DEFAULT
189
+ // reason is a generic DOMException ("This operation was aborted") with no
190
+ // retry context, so it is replaced with a named message too.
191
+ const abortError = () => {
192
+ const reason = signal?.reason;
193
+ if (reason instanceof Error && reason.name !== "AbortError")
194
+ return reason;
195
+ // A non-Error reason is spec-legal (controller.abort("user cancelled"))
196
+ // — stringify it rather than silently dropping it for the generic name.
197
+ // The DEFAULT reason (AbortError DOMException) stays on the named
198
+ // message: stringifying it would read "aborted: This operation was
199
+ // aborted".
200
+ if (reason !== undefined && reason !== null && !(reason instanceof Error)) {
201
+ return new Error(`sandbox shell-resolution retry aborted: ${String(reason)}`);
202
+ }
203
+ return new Error("sandbox shell-resolution retry aborted");
204
+ };
205
+ return new Promise((resolve, reject) => {
206
+ if (signal?.aborted) {
207
+ reject(abortError());
208
+ return;
209
+ }
210
+ const t = setTimeout(() => {
211
+ signal?.removeEventListener("abort", onAbort);
212
+ resolve();
213
+ }, ms);
214
+ const onAbort = () => {
215
+ clearTimeout(t);
216
+ reject(abortError());
217
+ };
218
+ signal?.addEventListener("abort", onAbort, { once: true });
219
+ });
220
+ }
221
+ /** srt's transient shell-resolution failure: `Shell '<name>' not found in
222
+ * PATH`, thrown from wrapCommandWithSandbox when which.js's 1s
223
+ * spawnSync('which') times out under load. Self-healing after ~15–30s;
224
+ * preWrappedCommand retries it once. */
225
+ export function isShellResolutionFailure(err) {
226
+ return err instanceof Error && /^Shell '[^']*' not found in PATH$/.test(err.message);
124
227
  }
125
228
  /**
126
229
  * Detect "Operation not permitted" style sandbox denials in bash output so
@@ -28,7 +28,7 @@ import type { PermissionRule } from "../permissionRules/loadConfig.js";
28
28
  * here would be overwritten and the sandbox would silently never reach
29
29
  * model-driven tool calls.
30
30
  */
31
- export declare function makeBashComposition(manager: YagniSandboxManager, settings: () => SandboxSettings, cwd: string): (def: ToolDefinition) => ToolDefinition;
31
+ export declare function makeBashComposition(manager: YagniSandboxManager, settings: () => SandboxSettings, cwd: string, onShellResolutionRetry?: (outcome: "recovered" | "exhausted") => void): (def: ToolDefinition) => ToolDefinition;
32
32
  export interface SandboxSessionHandle {
33
33
  manager: YagniSandboxManager;
34
34
  settings: () => SandboxSettings;
@@ -76,6 +76,10 @@ export interface RegisterSandboxOptions {
76
76
  * registration (the duplicate-name 400 + silent-unwrap lesson from M4).
77
77
  */
78
78
  registerOwnBash?: boolean;
79
+ /** Shell-resolution retry instrumentation (the OTel counter — index.ts
80
+ * wires the telemetry handle). Absent (eval mode, tests) ⇒ only the local
81
+ * sink line fires. */
82
+ onShellResolutionRetry?: (outcome: "recovered" | "exhausted") => void;
79
83
  }
80
84
  /**
81
85
  * Register the sandbox surfaces on the ExtensionAPI. Returns the session
@@ -39,7 +39,7 @@ import { effectiveRules } from "../permissionRules/loadConfig.js";
39
39
  * here would be overwritten and the sandbox would silently never reach
40
40
  * model-driven tool calls.
41
41
  */
42
- export function makeBashComposition(manager, settings, cwd) {
42
+ export function makeBashComposition(manager, settings, cwd, onShellResolutionRetry) {
43
43
  return (def) => {
44
44
  if (!settings().enabled)
45
45
  return def;
@@ -115,9 +115,13 @@ export function makeBashComposition(manager, settings, cwd) {
115
115
  if (!useSandbox) {
116
116
  return def.execute(id, params, signal, onUpdate, ctx);
117
117
  }
118
- const execParams = { ...input, command: await preWrappedCommand(manager, input.command, binShell) };
119
118
  let result;
120
119
  try {
120
+ const command = await preWrappedCommand(manager, input.command, binShell, {
121
+ signal: signal ?? undefined,
122
+ onRetry: onShellResolutionRetry,
123
+ });
124
+ const execParams = { ...input, command };
121
125
  result = await sandboxBash.execute(id, execParams, signal, onUpdate, ctx);
122
126
  }
123
127
  catch (err) {
@@ -309,7 +313,11 @@ export function registerSandbox(pi, opts) {
309
313
  // registration when condensed is inactive (eval mode, classic rows,
310
314
  // desktop). The composition is identity when the sandbox is disabled —
311
315
  // default-off sessions stay byte-identical.
312
- const composeBash = makeBashComposition(manager, () => currentSettings, opts.cwd);
316
+ const onShellResolutionRetry = (outcome) => {
317
+ logShellResolutionRetry(outcome);
318
+ opts.onShellResolutionRetry?.(outcome);
319
+ };
320
+ const composeBash = makeBashComposition(manager, () => currentSettings, opts.cwd, onShellResolutionRetry);
313
321
  const registerOwnBash = () => {
314
322
  if (!currentSettings.enabled)
315
323
  return;
@@ -345,7 +353,10 @@ export function registerSandbox(pi, opts) {
345
353
  exec: async (command, cwd, { onData, signal, timeout, env }) => {
346
354
  if (!existsSync(cwd))
347
355
  throw new Error(`Working directory does not exist: ${cwd}`);
348
- const wrapped = await manager.wrapWithSandbox(command, shell);
356
+ const wrapped = await preWrappedCommand(manager, command, shell, {
357
+ signal,
358
+ onRetry: onShellResolutionRetry,
359
+ });
349
360
  const child = spawn(shell, [...args, wrapped], {
350
361
  cwd,
351
362
  env: env ?? process.env,
@@ -781,6 +792,19 @@ export function registerSandbox(pi, opts) {
781
792
  function isPlainRecord(v) {
782
793
  return typeof v === "object" && v !== null && !Array.isArray(v);
783
794
  }
795
+ /** Sink line for the transient shell-resolution retry: outcome only (a
796
+ * closed enum, no command or shell content — scrub-safe, so it rides the
797
+ * default-on tier and /feedback). `recovered` = the retry healed a
798
+ * transient srt `which` timeout; `exhausted` = the failure surfaced to the
799
+ * model (warn, the break-glass signal). */
800
+ function logShellResolutionRetry(outcome) {
801
+ logEvent({
802
+ source: "sandbox",
803
+ level: outcome === "recovered" ? "info" : "warn",
804
+ event: "shell_resolution_retry",
805
+ fields: { outcome },
806
+ });
807
+ }
784
808
  /** Sink line when the network-posture classifier fires — the impact
785
809
  * signal (paired with tool_execute_decision's useSandbox it makes the
786
810
  * escape-rate before/after readable from the trail). Closed enum class,
@@ -85,6 +85,7 @@ export declare const METRIC_COST_USAGE = "yagni_code.cost.usage";
85
85
  export declare const METRIC_TOKEN_USAGE = "yagni_code.token.usage";
86
86
  export declare const METRIC_CODE_EDIT_DECISION = "yagni_code.code_edit_tool.decision";
87
87
  export declare const METRIC_ACTIVE_TIME = "yagni_code.active_time.total";
88
+ export declare const METRIC_SANDBOX_SHELL_RETRY = "yagni_code.sandbox.shell_resolution_retry.count";
88
89
  export declare const EVENT_USER_PROMPT = "user_prompt";
89
90
  export declare const EVENT_ASSISTANT_RESPONSE = "assistant_response";
90
91
  export declare const EVENT_TOOL_RESULT = "tool_result";
@@ -89,6 +89,7 @@ export const METRIC_COST_USAGE = `${PREFIX}.cost.usage`;
89
89
  export const METRIC_TOKEN_USAGE = `${PREFIX}.token.usage`;
90
90
  export const METRIC_CODE_EDIT_DECISION = `${PREFIX}.code_edit_tool.decision`;
91
91
  export const METRIC_ACTIVE_TIME = `${PREFIX}.active_time.total`;
92
+ export const METRIC_SANDBOX_SHELL_RETRY = `${PREFIX}.sandbox.shell_resolution_retry.count`;
92
93
  // ── Event names (Claude Code's log events, prefixed in the body) ────────────
93
94
  export const EVENT_USER_PROMPT = "user_prompt";
94
95
  export const EVENT_ASSISTANT_RESPONSE = "assistant_response";
@@ -34,6 +34,8 @@ export interface TelemetryHandle {
34
34
  * to the `includeAccountId` gate).
35
35
  */
36
36
  setUserEmail(email: string | undefined): void;
37
+ /** The sandbox's shell-resolution retry calls this with its outcome. */
38
+ sandboxShellResolutionRetry(outcome: "recovered" | "exhausted"): void;
37
39
  /** Test/introspection seam: the live tracker once the SDK is up. */
38
40
  readonly tracker: SessionTelemetry | null;
39
41
  }
@@ -28,6 +28,7 @@ const NOOP_HANDLE = (config) => ({
28
28
  toolDecision: () => { },
29
29
  permissionModeChanged: () => { },
30
30
  setUserEmail: () => { },
31
+ sandboxShellResolutionRetry: () => { },
31
32
  tracker: null,
32
33
  });
33
34
  export function registerTelemetry(pi, deps = {}) {
@@ -188,6 +189,7 @@ export function registerTelemetry(pi, deps = {}) {
188
189
  },
189
190
  toolDecision: guard("tool_decision", (input) => tracker?.toolDecision(input)),
190
191
  permissionModeChanged: guard("permission_mode_changed", (from, to) => tracker?.permissionModeChanged(from, to)),
192
+ sandboxShellResolutionRetry: guard("sandbox_shell_resolution_retry", (outcome) => tracker?.sandboxShellResolutionRetry(outcome)),
191
193
  // Precedence, lowest to highest: this call (the /context boot fetch) <
192
194
  // the launcher's YAGNI_USER_EMAIL, which resolveTelemetryConfig already
193
195
  // placed on `identity`. So an identity that is set is never overwritten,
@@ -110,6 +110,10 @@ export declare class SessionTelemetry {
110
110
  }): void;
111
111
  /** Lines added/removed by an edit or write, from pi's tool_result details. */
112
112
  linesOfCode(added: number, removed: number): void;
113
+ /** Transient sandbox shell-resolution failure retried by the wrap seam —
114
+ * `recovered` (retry healed it) vs `exhausted` (error surfaced to the
115
+ * model). The post-deploy health signal for the retry feature. */
116
+ sandboxShellResolutionRetry(outcome: "recovered" | "exhausted"): void;
113
117
  /** A successful bash command: count commits and PR creations. */
114
118
  bashSucceeded(command: string | undefined): void;
115
119
  permissionModeChanged(fromMode: string, toMode: string): void;
@@ -20,7 +20,7 @@
20
20
  import { randomUUID } from "node:crypto";
21
21
  import { context as otelContext, SpanStatusCode, trace, } from "@opentelemetry/api";
22
22
  import { SeverityNumber } from "@opentelemetry/api-logs";
23
- import { ATTR_APP_ENTRYPOINT, ATTR_APP_VERSION, ATTR_ERROR_TYPE, ATTR_GEN_AI_AGENT_NAME, ATTR_GEN_AI_CACHE_CREATION_TOKENS_LEGACY, ATTR_GEN_AI_CACHE_READ_TOKENS, ATTR_GEN_AI_CACHE_READ_TOKENS_LEGACY, ATTR_GEN_AI_CACHE_WRITE_TOKENS, ATTR_DD_LLMOBS_METADATA, ATTR_GEN_AI_CONVERSATION_ID, ATTR_GEN_AI_COST_ESTIMATED_TOTAL, ATTR_GEN_AI_FINISH_REASONS, ATTR_GEN_AI_INPUT_TOKENS, ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_OUTPUT_TOKENS, ATTR_GEN_AI_TOTAL_TOKENS, ATTR_GEN_AI_PROVIDER_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_ID, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_TOOL_TYPE, ATTR_HTTP_STATUS_CODE, ATTR_ORGANIZATION_ID, ATTR_SESSION_ID, ATTR_TERMINAL_TYPE, ATTR_USER_EMAIL, EVENT_API_ERROR, EVENT_API_REQUEST, EVENT_ASSISTANT_RESPONSE, EVENT_PERMISSION_MODE_CHANGED, EVENT_TOOL_DECISION, EVENT_TOOL_RESULT, EVENT_USER_PROMPT, GEN_AI_PROVIDER, languageFromPath, METRIC_ACTIVE_TIME, METRIC_CODE_EDIT_DECISION, METRIC_COMMIT_COUNT, METRIC_COST_USAGE, METRIC_LINES_OF_CODE, METRIC_PULL_REQUEST_COUNT, METRIC_SESSION_COUNT, METRIC_TOKEN_USAGE, PREFIX, SPAN_INTERACTION, SPAN_LLM_REQUEST, SPAN_TOOL, SPAN_TURN, } from "./attrs.js";
23
+ import { ATTR_APP_ENTRYPOINT, ATTR_APP_VERSION, ATTR_ERROR_TYPE, ATTR_GEN_AI_AGENT_NAME, ATTR_GEN_AI_CACHE_CREATION_TOKENS_LEGACY, ATTR_GEN_AI_CACHE_READ_TOKENS, ATTR_GEN_AI_CACHE_READ_TOKENS_LEGACY, ATTR_GEN_AI_CACHE_WRITE_TOKENS, ATTR_DD_LLMOBS_METADATA, ATTR_GEN_AI_CONVERSATION_ID, ATTR_GEN_AI_COST_ESTIMATED_TOTAL, ATTR_GEN_AI_FINISH_REASONS, ATTR_GEN_AI_INPUT_TOKENS, ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_OUTPUT_TOKENS, ATTR_GEN_AI_TOTAL_TOKENS, ATTR_GEN_AI_PROVIDER_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_ID, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_TOOL_TYPE, ATTR_HTTP_STATUS_CODE, ATTR_ORGANIZATION_ID, ATTR_SESSION_ID, ATTR_TERMINAL_TYPE, ATTR_USER_EMAIL, EVENT_API_ERROR, EVENT_API_REQUEST, EVENT_ASSISTANT_RESPONSE, EVENT_PERMISSION_MODE_CHANGED, EVENT_TOOL_DECISION, EVENT_TOOL_RESULT, EVENT_USER_PROMPT, GEN_AI_PROVIDER, languageFromPath, METRIC_ACTIVE_TIME, METRIC_CODE_EDIT_DECISION, METRIC_COMMIT_COUNT, METRIC_COST_USAGE, METRIC_LINES_OF_CODE, METRIC_PULL_REQUEST_COUNT, METRIC_SANDBOX_SHELL_RETRY, METRIC_SESSION_COUNT, METRIC_TOKEN_USAGE, PREFIX, SPAN_INTERACTION, SPAN_LLM_REQUEST, SPAN_TOOL, SPAN_TURN, } from "./attrs.js";
24
24
  /** Idle cutoff for user active time: gaps longer than this are not "active". */
25
25
  export const USER_ACTIVE_IDLE_CUTOFF_MS = 5 * 60 * 1000;
26
26
  const EDIT_TOOLS = new Set(["edit", "write", "multi_edit", "notebook_edit"]);
@@ -463,6 +463,12 @@ export class SessionTelemetry {
463
463
  this.add(METRIC_LINES_OF_CODE, "1", "Count of lines of code modified", added, { type: "added" });
464
464
  this.add(METRIC_LINES_OF_CODE, "1", "Count of lines of code modified", removed, { type: "removed" });
465
465
  }
466
+ /** Transient sandbox shell-resolution failure retried by the wrap seam —
467
+ * `recovered` (retry healed it) vs `exhausted` (error surfaced to the
468
+ * model). The post-deploy health signal for the retry feature. */
469
+ sandboxShellResolutionRetry(outcome) {
470
+ this.add(METRIC_SANDBOX_SHELL_RETRY, "1", "Sandbox shell-resolution transient failures, by retry outcome", 1, { outcome });
471
+ }
466
472
  /** A successful bash command: count commits and PR creations. */
467
473
  bashSucceeded(command) {
468
474
  if (typeof command !== "string")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yagni-app/code-staging",
3
- "version": "1.1.0-staging.1334.1",
3
+ "version": "1.1.0-staging.1335.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)",
@@ -58,5 +58,5 @@
58
58
  "turndown": "^7.2.4",
59
59
  "typebox": "^1.3.15"
60
60
  },
61
- "yagniSourceSha": "b7b91c0bf94f197fc16643cb9a5c5f580e896001"
61
+ "yagniSourceSha": "fe89db90856f43da8d645abeb16d448c83bb8e33"
62
62
  }