@yagni-app/code-staging 0.3.4-staging.1145.1 → 0.3.4-staging.1148.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.
|
@@ -90,6 +90,14 @@ export interface CatalogResult {
|
|
|
90
90
|
* "off" (fail toward not sending).
|
|
91
91
|
*/
|
|
92
92
|
guardianStorage: GuardianStorageTier;
|
|
93
|
+
/**
|
|
94
|
+
* Backend-owned per-request Guardian wall ceiling (YAG-562). The client
|
|
95
|
+
* derives its whole-consult deadline from this instead of a hardcoded abort.
|
|
96
|
+
* Absent on older backends → the extension falls back to its default.
|
|
97
|
+
*/
|
|
98
|
+
guardianTimeoutMs?: number;
|
|
99
|
+
/** Number of attempts the backend makes (primary + one failover). */
|
|
100
|
+
guardianMaxAttempts?: number;
|
|
93
101
|
}
|
|
94
102
|
/**
|
|
95
103
|
* Fetch the YAGNI model catalog at startup.
|
package/dist/extension/config.js
CHANGED
|
@@ -91,6 +91,12 @@ export async function fetchCatalog(opts) {
|
|
|
91
91
|
guardianStorage: data.guardianStorage === "raw" || data.guardianStorage === "hash"
|
|
92
92
|
? data.guardianStorage
|
|
93
93
|
: "off",
|
|
94
|
+
...(typeof data.guardianTimeoutMs === "number" && Number.isFinite(data.guardianTimeoutMs) && data.guardianTimeoutMs > 0
|
|
95
|
+
? { guardianTimeoutMs: data.guardianTimeoutMs }
|
|
96
|
+
: {}),
|
|
97
|
+
...(typeof data.guardianMaxAttempts === "number" && Number.isSafeInteger(data.guardianMaxAttempts) && data.guardianMaxAttempts >= 1
|
|
98
|
+
? { guardianMaxAttempts: data.guardianMaxAttempts }
|
|
99
|
+
: {}),
|
|
94
100
|
};
|
|
95
101
|
}
|
|
96
102
|
/** Shape-check for a caller label: mirrors the model proxy's own validation regex. */
|
|
@@ -121,7 +121,7 @@ export { makeAskYagniTool } from "./askYagniTool.js";
|
|
|
121
121
|
export { makeFileTicketTool, makeUpdateTicketStatusTool } from "./ticketTools.js";
|
|
122
122
|
export { makeAskAdvisorTool, registerAdviseCommand } from "./askAdvisorTool.js";
|
|
123
123
|
export { ADVISOR_TIER, DEFAULT_ADVISOR_LIMITS, decideConsult, formatAdvisorSubtotal, makeAdvisorState, } from "./advisor.js";
|
|
124
|
-
export { DEFAULT_GUARDIAN_LIMITS, GUARDIAN_MODEL_TIER, formatGuardianSubtotal, makeGuardianState, resolveGuardianLimits, reviewCommand, } from "./permission/guardian.js";
|
|
124
|
+
export { DEFAULT_GUARDIAN_LIMITS, GUARDIAN_MODEL_TIER, formatGuardianSubtotal, makeGuardianState, resolveGuardianLimits, reviewCommand, deriveGuardianTimeoutMs, } from "./permission/guardian.js";
|
|
125
125
|
export type { GuardianOutcome, GuardianVerdict, GuardianState, GuardianStateHandle, GuardianLimits, ReviewResult, ReviewCommandDeps, } from "./permission/guardian.js";
|
|
126
126
|
export type { Citation, MakeAskYagniToolOptions } from "./askYagniTool.js";
|
|
127
127
|
export { makeReviewBusinessMatchTool } from "./reviewTool.js";
|
package/dist/extension/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
5
5
|
import { DEFAULT_ADVISOR_LIMITS, formatAdvisorSubtotal, makeAdvisorState } from "./advisor.js";
|
|
6
6
|
import { appendGrant, loadGrants, resolveRepoKey, storagePrefix } from "./permission/approvedPrefixes.js";
|
|
7
7
|
import { redactCommand } from "./redact.js";
|
|
8
|
-
import { formatGuardianSubtotal, GUARDIAN_MODEL_TIER, makeGuardianState, resolveGuardianLimits, reviewCommand } from "./permission/guardian.js";
|
|
8
|
+
import { formatGuardianSubtotal, GUARDIAN_MODEL_TIER, makeGuardianState, resolveGuardianLimits, reviewCommand, deriveGuardianTimeoutMs } from "./permission/guardian.js";
|
|
9
9
|
import { makeAskAdvisorTool, registerAdviseCommand } from "./askAdvisorTool.js";
|
|
10
10
|
import { makeAskYagniTool } from "./askYagniTool.js";
|
|
11
11
|
import { makeFileTicketTool, makeUpdateTicketStatusTool } from "./ticketTools.js";
|
|
@@ -135,7 +135,7 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
135
135
|
// after_provider_response event does NOT fire on a 401 (the OpenAI SDK throws
|
|
136
136
|
// before onResponse is reached), so message_end is the only seam.
|
|
137
137
|
let lastAuthRecovery = null;
|
|
138
|
-
const { models: fullCatalog, guardianEnabled: workspaceGuardianEnabled, guardianStorage: guardianStorageTier, } = await fetchCatalog({ baseUrl, getToken: getTokenFn, fetchImpl: authedFetch });
|
|
138
|
+
const { models: fullCatalog, guardianEnabled: workspaceGuardianEnabled, guardianStorage: guardianStorageTier, guardianTimeoutMs: guardianTimeoutAdvertisedMs, guardianMaxAttempts: guardianMaxAttemptsAdvertised, } = await fetchCatalog({ baseUrl, getToken: getTokenFn, fetchImpl: authedFetch });
|
|
139
139
|
// Lock the interactive session to the `advanced` tier only. The backend
|
|
140
140
|
// catalog returns all tiers, but only `advanced` is registered with the
|
|
141
141
|
// `yagni` provider, so /model and Ctrl+P show a single entry. Child
|
|
@@ -272,6 +272,16 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
272
272
|
!workspaceGuardianEnabled;
|
|
273
273
|
const guardianTier = env.YAGNI_GUARDIAN_TIER ?? GUARDIAN_MODEL_TIER;
|
|
274
274
|
const guardianLimits = resolveGuardianLimits(env);
|
|
275
|
+
// YAG-562: the backend owns the Guardian timeout and retries once. The client
|
|
276
|
+
// derives its whole-consult deadline from the catalog's advertised wall
|
|
277
|
+
// ceiling + attempt count (plus a boot/tool overhead margin) rather than
|
|
278
|
+
// hard-aborting at 15s and preempting the backend's retry. Absent an
|
|
279
|
+
// advertisement (older backend) this keeps the default.
|
|
280
|
+
const guardianTimeoutMs = deriveGuardianTimeoutMs(guardianLimits, {
|
|
281
|
+
...(guardianTimeoutAdvertisedMs !== undefined ? { timeoutMs: guardianTimeoutAdvertisedMs } : {}),
|
|
282
|
+
...(guardianMaxAttemptsAdvertised !== undefined ? { maxAttempts: guardianMaxAttemptsAdvertised } : {}),
|
|
283
|
+
});
|
|
284
|
+
guardianLimits.timeoutMs = guardianTimeoutMs;
|
|
275
285
|
// YAG-510: guardian.log stays the sanitized local debug sink (hash-only,
|
|
276
286
|
// never the command). The remote guardian-events stream below is the
|
|
277
287
|
// separate, opt-in, per-workspace analytics sink; the two are independent.
|
|
@@ -887,7 +897,7 @@ export { makeAskYagniTool } from "./askYagniTool.js";
|
|
|
887
897
|
export { makeFileTicketTool, makeUpdateTicketStatusTool } from "./ticketTools.js";
|
|
888
898
|
export { makeAskAdvisorTool, registerAdviseCommand } from "./askAdvisorTool.js";
|
|
889
899
|
export { ADVISOR_TIER, DEFAULT_ADVISOR_LIMITS, decideConsult, formatAdvisorSubtotal, makeAdvisorState, } from "./advisor.js";
|
|
890
|
-
export { DEFAULT_GUARDIAN_LIMITS, GUARDIAN_MODEL_TIER, formatGuardianSubtotal, makeGuardianState, resolveGuardianLimits, reviewCommand, } from "./permission/guardian.js";
|
|
900
|
+
export { DEFAULT_GUARDIAN_LIMITS, GUARDIAN_MODEL_TIER, formatGuardianSubtotal, makeGuardianState, resolveGuardianLimits, reviewCommand, deriveGuardianTimeoutMs, } from "./permission/guardian.js";
|
|
891
901
|
export { makeReviewBusinessMatchTool } from "./reviewTool.js";
|
|
892
902
|
export { makeRecordEngineeringContextTool } from "./recordContextTool.js";
|
|
893
903
|
export { makeRecordDecisionTool } from "./recordDecisionTool.js";
|
|
@@ -45,13 +45,32 @@ export interface GuardianLimits {
|
|
|
45
45
|
timeoutMs: number;
|
|
46
46
|
}
|
|
47
47
|
export declare const DEFAULT_GUARDIAN_LIMITS: GuardianLimits;
|
|
48
|
+
/**
|
|
49
|
+
* Client-side safety margin on top of the backend's advertised wall ceiling
|
|
50
|
+
* (YAG-562). The backend now owns the real timeout (first-output + wall) and
|
|
51
|
+
* retries once, so the client must NOT pre-empt it; it derives its own
|
|
52
|
+
* deadline from the catalog's `guardianTimeoutMs * attempts` plus this margin
|
|
53
|
+
* for child boot + read-tool round-trips + teardown, and treats that bound as
|
|
54
|
+
* a last-resort floor, not the normal path.
|
|
55
|
+
*/
|
|
56
|
+
export declare const GUARDIAN_CLIENT_OVERHEAD_MS = 10000;
|
|
48
57
|
/**
|
|
49
58
|
* Resolve Guardian limits from the environment. `YAGNI_GUARDIAN_MAX_REVIEWS`
|
|
50
59
|
* overrides the sliding-window review cap; anything non-numeric or < 1 falls
|
|
51
60
|
* back to the default (a bad value must never zero out the cap and lock the
|
|
52
|
-
* session).
|
|
61
|
+
* session). `YAGNI_GUARDIAN_TIMEOUT_MS` overrides the consult deadline for a
|
|
62
|
+
* single developer (previously documented but unwired).
|
|
53
63
|
*/
|
|
54
64
|
export declare function resolveGuardianLimits(env?: Record<string, string | undefined>): GuardianLimits;
|
|
65
|
+
/**
|
|
66
|
+
* Derive the client's whole-consult deadline from the backend's advertised
|
|
67
|
+
* wall ceiling and attempt count (YAG-562). Falls back to the resolved limit
|
|
68
|
+
* when the catalog omitted the fields (older backend).
|
|
69
|
+
*/
|
|
70
|
+
export declare function deriveGuardianTimeoutMs(base: GuardianLimits, advertised: {
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
maxAttempts?: number;
|
|
73
|
+
}): number;
|
|
55
74
|
/** The model tier the Guardian runs on. Configurable via YAGNI_GUARDIAN_TIER. */
|
|
56
75
|
export declare const GUARDIAN_MODEL_TIER = "efficient";
|
|
57
76
|
/** Read-only tools — the Guardian can read files for context but cannot write or execute. */
|
|
@@ -33,17 +33,41 @@ export const DEFAULT_GUARDIAN_LIMITS = {
|
|
|
33
33
|
maxConsecutiveDenials: 3,
|
|
34
34
|
timeoutMs: 15_000,
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Client-side safety margin on top of the backend's advertised wall ceiling
|
|
38
|
+
* (YAG-562). The backend now owns the real timeout (first-output + wall) and
|
|
39
|
+
* retries once, so the client must NOT pre-empt it; it derives its own
|
|
40
|
+
* deadline from the catalog's `guardianTimeoutMs * attempts` plus this margin
|
|
41
|
+
* for child boot + read-tool round-trips + teardown, and treats that bound as
|
|
42
|
+
* a last-resort floor, not the normal path.
|
|
43
|
+
*/
|
|
44
|
+
export const GUARDIAN_CLIENT_OVERHEAD_MS = 10_000;
|
|
36
45
|
/**
|
|
37
46
|
* Resolve Guardian limits from the environment. `YAGNI_GUARDIAN_MAX_REVIEWS`
|
|
38
47
|
* overrides the sliding-window review cap; anything non-numeric or < 1 falls
|
|
39
48
|
* back to the default (a bad value must never zero out the cap and lock the
|
|
40
|
-
* session).
|
|
49
|
+
* session). `YAGNI_GUARDIAN_TIMEOUT_MS` overrides the consult deadline for a
|
|
50
|
+
* single developer (previously documented but unwired).
|
|
41
51
|
*/
|
|
42
52
|
export function resolveGuardianLimits(env = process.env) {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const maxReviews = Number.isFinite(
|
|
46
|
-
|
|
53
|
+
const rawReviews = env.YAGNI_GUARDIAN_MAX_REVIEWS?.trim();
|
|
54
|
+
const parsedReviews = rawReviews ? Number.parseInt(rawReviews, 10) : NaN;
|
|
55
|
+
const maxReviews = Number.isFinite(parsedReviews) && parsedReviews >= 1 ? parsedReviews : DEFAULT_GUARDIAN_LIMITS.maxReviews;
|
|
56
|
+
const rawTimeout = env.YAGNI_GUARDIAN_TIMEOUT_MS?.trim();
|
|
57
|
+
const parsedTimeout = rawTimeout ? Number.parseInt(rawTimeout, 10) : NaN;
|
|
58
|
+
const timeoutMs = Number.isFinite(parsedTimeout) && parsedTimeout >= 1 ? parsedTimeout : DEFAULT_GUARDIAN_LIMITS.timeoutMs;
|
|
59
|
+
return { ...DEFAULT_GUARDIAN_LIMITS, maxReviews, timeoutMs };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Derive the client's whole-consult deadline from the backend's advertised
|
|
63
|
+
* wall ceiling and attempt count (YAG-562). Falls back to the resolved limit
|
|
64
|
+
* when the catalog omitted the fields (older backend).
|
|
65
|
+
*/
|
|
66
|
+
export function deriveGuardianTimeoutMs(base, advertised) {
|
|
67
|
+
if (advertised.timeoutMs !== undefined && advertised.maxAttempts !== undefined) {
|
|
68
|
+
return advertised.timeoutMs * advertised.maxAttempts + GUARDIAN_CLIENT_OVERHEAD_MS;
|
|
69
|
+
}
|
|
70
|
+
return base.timeoutMs;
|
|
47
71
|
}
|
|
48
72
|
/** The model tier the Guardian runs on. Configurable via YAGNI_GUARDIAN_TIER. */
|
|
49
73
|
export const GUARDIAN_MODEL_TIER = "efficient";
|
|
@@ -190,7 +214,19 @@ export async function reviewCommand(command, deps) {
|
|
|
190
214
|
if (deps.signal?.aborted) {
|
|
191
215
|
return { verdict: null, error: "aborted", cost };
|
|
192
216
|
}
|
|
217
|
+
// The backend now owns the timeout (YAG-562) and streams a terminal error
|
|
218
|
+
// frame when the consult fails; a result with a `stopReason: "error"` or an
|
|
219
|
+
// `errorMessage` is a real backend failure, NOT the silent-empty shape. Map
|
|
220
|
+
// it to a concrete error so the gate shows "review timed out" / "service
|
|
221
|
+
// unavailable" instead of the misleading "no response". A timeout-shaped
|
|
222
|
+
// message (stream_idle_timeout / "timed out") is distinguished so the gate
|
|
223
|
+
// can add its timeout-specific note.
|
|
193
224
|
if (!output) {
|
|
225
|
+
if (result.stopReason === "error" || result.errorMessage) {
|
|
226
|
+
const msg = (result.errorMessage ?? "").toLowerCase();
|
|
227
|
+
const isTimeout = /stream_idle_timeout|timed out|idle|wall|timeout/i.test(msg);
|
|
228
|
+
return { verdict: null, error: isTimeout ? "timeout" : "network", cost };
|
|
229
|
+
}
|
|
194
230
|
return { verdict: null, error: "empty", cost };
|
|
195
231
|
}
|
|
196
232
|
const verdict = parseVerdict(output);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "0.3.4-staging.
|
|
3
|
+
"version": "0.3.4-staging.1148.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)",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@earendil-works/pi-coding-agent": "0.84.1",
|
|
38
38
|
"@earendil-works/pi-tui": "0.84.1",
|
|
39
39
|
"smol-toml": "^1.8.0",
|
|
40
|
-
"typebox": "^1.3.
|
|
40
|
+
"typebox": "^1.3.15"
|
|
41
41
|
},
|
|
42
|
-
"yagniSourceSha": "
|
|
42
|
+
"yagniSourceSha": "eefde6e0c0646de0dd81cb9d6ed81df228909d54"
|
|
43
43
|
}
|