@code-yeongyu/senpi 2026.10.16-3 → 2026.10.16-4
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/CHANGELOG.md +28 -0
- package/dist/bundle/chunks/{chunk-I5ZJEXTM.js → chunk-2NO5ZOYE.js} +6 -6
- package/dist/bundle/chunks/{chunk-4R4PRTF5.js → chunk-CJRE5LLR.js} +1 -1
- package/dist/bundle/chunks/{chunk-X5VCHHXR.js → chunk-GY5O7NIQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-AVALXFDW.js → chunk-JD4CV3GH.js} +1 -1
- package/dist/bundle/chunks/{chunk-SSEGIXRL.js → chunk-KUQ3K33L.js} +1 -1
- package/dist/bundle/chunks/{chunk-FMYLLJBQ.js → chunk-KWC3MCLP.js} +1 -1
- package/dist/bundle/chunks/chunk-MLL7AGVM.js +3 -0
- package/dist/bundle/chunks/chunk-PAJKEG2L.js +8 -0
- package/dist/bundle/chunks/{chunk-DPRV3FR7.js → chunk-PWHAOWNH.js} +1 -1
- package/dist/bundle/chunks/{chunk-HJPX7PBL.js → chunk-XCRGEMY2.js} +125 -118
- package/dist/bundle/chunks/{cli-main-JVPFJGP5.js → cli-main-I6JF2GPS.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-SVCMHCYG.js → help-fast-path-APSHUDY6.js} +1 -1
- package/dist/bundle/chunks/{host-session-command-46PTZGIB.js → host-session-command-BUJUJHDA.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-BROZRY34.js → interactive-mode-4Q3TIXOZ.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-SMQR5SCJ.js → multi-session-host-XEZB2XVR.js} +5 -4
- package/dist/bundle/chunks/{package-manager-cli-2QFJY2AS.js → package-manager-cli-YWZHOZOY.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-QX7YSKDU.js → rotation-stream-HY3JPQCX.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-Z45T3NZO.js → rpc-mode-SLWXVXXE.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-2RHSWBQO.js → schedule-command-JRWZGEDU.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-H7FFWF2A.js → session-control-endpoint-E2ZSEWXW.js} +1 -1
- package/dist/bundle/chunks/session-picker-5IZY7E3G.js +2 -0
- package/dist/bundle/chunks/session-worker.js +243 -235
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session-runtime.d.ts +15 -0
- package/dist/core/agent-session-runtime.js +53 -2
- package/dist/core/agent-session.d.ts +12 -0
- package/dist/core/agent-session.js +42 -1
- package/dist/core/context-files.d.ts +21 -0
- package/dist/core/context-files.js +175 -0
- package/dist/core/diagnostics.d.ts +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/account-command.js +26 -3
- package/dist/core/extensions/builtin/anthropic-subscription/account-management.d.ts +5 -0
- package/dist/core/extensions/builtin/anthropic-subscription/account-management.js +40 -0
- package/dist/core/extensions/builtin/anthropic-subscription/accounts.d.ts +3 -0
- package/dist/core/extensions/builtin/anthropic-subscription/accounts.js +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/affinity.js +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/failover.d.ts +10 -5
- package/dist/core/extensions/builtin/anthropic-subscription/failover.js +158 -17
- package/dist/core/extensions/builtin/anthropic-subscription/usage-limit-block.d.ts +13 -0
- package/dist/core/extensions/builtin/anthropic-subscription/usage-limit-block.js +89 -0
- package/dist/core/extensions/builtin/permission-system/always-scope.d.ts +34 -0
- package/dist/core/extensions/builtin/permission-system/always-scope.js +77 -0
- package/dist/core/extensions/builtin/permission-system/index.js +3 -1
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.js +24 -3
- package/dist/core/extensions/builtin/permission-system/prompt.js +5 -1
- package/dist/core/extensions/builtin/permission-system/service.js +1 -1
- package/dist/core/extensions/builtin/permission-system/types.d.ts +7 -0
- package/dist/core/package-manager.js +4 -32
- package/dist/core/project-trust.d.ts +8 -0
- package/dist/core/project-trust.js +12 -0
- package/dist/core/resource-loader.d.ts +5 -7
- package/dist/core/resource-loader.js +11 -84
- package/dist/core/retry-fallback/circuit-probes.d.ts +1 -0
- package/dist/core/retry-fallback/circuit-probes.js +3 -0
- package/dist/core/retry-fallback/circuit.d.ts +5 -0
- package/dist/core/retry-fallback/circuit.js +27 -4
- package/dist/core/retry-fallback/controller.d.ts +9 -4
- package/dist/core/retry-fallback/controller.js +21 -4
- package/dist/core/settings-manager.d.ts +15 -0
- package/dist/core/settings-manager.js +21 -1
- package/dist/core/skills-prompt.d.ts +18 -0
- package/dist/core/skills-prompt.js +131 -0
- package/dist/core/skills.d.ts +1 -9
- package/dist/core/skills.js +1 -59
- package/dist/core/trust-manager.d.ts +13 -4
- package/dist/core/trust-manager.js +52 -21
- package/dist/main.js +23 -4
- package/dist/modes/interactive/interactive-mode.js +10 -3
- package/dist/modes/rpc/abort-grace.d.ts +9 -0
- package/dist/modes/rpc/abort-grace.js +11 -0
- package/dist/modes/rpc/effect-commands.d.ts +0 -2
- package/dist/modes/rpc/effect-commands.js +2 -3
- package/dist/modes/rpc/host-handover-parked.d.ts +6 -0
- package/dist/modes/rpc/host-handover-parked.js +1 -0
- package/dist/modes/rpc/host-handover-reopen.js +11 -2
- package/dist/modes/rpc/rpc-client.d.ts +28 -2
- package/dist/modes/rpc/rpc-client.js +24 -11
- package/dist/modes/rpc/rpc-request-deadline.d.ts +12 -0
- package/dist/modes/rpc/rpc-request-deadline.js +15 -0
- package/dist/modes/rpc/session-command-router.js +7 -4
- package/dist/modes/rpc/session-registry-attach.js +6 -1
- package/dist/modes/rpc/session-registry-claims.d.ts +22 -2
- package/dist/modes/rpc/session-registry-claims.js +68 -0
- package/dist/modes/rpc/session-registry-opening.d.ts +23 -0
- package/dist/modes/rpc/session-registry-opening.js +57 -0
- package/dist/modes/rpc/session-registry.d.ts +1 -0
- package/dist/modes/rpc/session-registry.js +48 -60
- package/docs/rpc.md +6 -2
- package/docs/settings.md +13 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-6MEETY7N.js +0 -8
- package/dist/bundle/chunks/chunk-TEGEPQIA.js +0 -3
- package/dist/bundle/chunks/session-picker-2O2BNBVE.js +0 -2
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code's own verdict that the account's subscription is spent: its anchored usage-limit prose
|
|
3
|
+
* or its structured terminal reason, under no status other than 429 (senpi#3462, senpi#3466).
|
|
4
|
+
*/
|
|
5
|
+
export declare function isConfirmedAccountUsageLimit(text: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* How long a spent account stays blocked: until the reset its limit names, or the reset its spent
|
|
8
|
+
* window reported, never past the window the limit belongs to. Undefined when the error is not
|
|
9
|
+
* Claude Code's account usage limit or names no reset, so the caller keeps its short default
|
|
10
|
+
* (senpi#3462).
|
|
11
|
+
*/
|
|
12
|
+
export declare function usageLimitBlockMs(text: string, accountName: string, now: number): number | undefined;
|
|
13
|
+
//# sourceMappingURL=usage-limit-block.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { usageLimitResetMs } from "../../../credential-pool/reset-time.js";
|
|
2
|
+
import { subscriptionLimits } from "../../../subscription-limits/limit-store.js";
|
|
3
|
+
// The subscription-limit store keys accounts by this provider id (auth-attempt.ts).
|
|
4
|
+
const PROVIDER = "anthropic-subscription";
|
|
5
|
+
const HOUR_MS = 60 * 60 * 1_000;
|
|
6
|
+
/**
|
|
7
|
+
* Claude Code's own account usage-limit errors, anchored at the start of the message: "You've hit
|
|
8
|
+
* your session limit · resets 3pm", "You've reached your 5-hour limit", "Claude AI usage limit
|
|
9
|
+
* reached". Only a window word Claude Code uses may stand before "limit", so a context or rate
|
|
10
|
+
* limit, a warning ("approaching your usage limit") or ordinary prose never takes a long block.
|
|
11
|
+
*/
|
|
12
|
+
const CLAUDE_USAGE_LIMIT = /^(?:You['\u2019]ve|You have) (?:hit|reached) your (?:(session|weekly|daily|monthly|\d+-hour|usage) )?limit(?=$|\s*[\u00b7\u2219.,{(]|\s+resets?\b)|^Claude AI usage limit reached\b/i;
|
|
13
|
+
/**
|
|
14
|
+
* Claude Code's structured terminal reason for a spent subscription. `rapid_refill_breaker` is a short burst
|
|
15
|
+
* breaker, not a spent account, so it keeps the short, session-scoped block (senpi#3466).
|
|
16
|
+
*/
|
|
17
|
+
const TERMINAL_LIMIT_REASON = /\bblocking_limit\b/;
|
|
18
|
+
/** A status the error names; a usage limit is a 429, so any other status is not one. */
|
|
19
|
+
const NAMED_STATUS = /\b(?:HTTP|status(?: code)?)\s*(\d{3})\b/i;
|
|
20
|
+
/** The longest a block may last for each window word: a reset read past it is a misread. */
|
|
21
|
+
const WINDOW_MS = {
|
|
22
|
+
session: 5 * HOUR_MS,
|
|
23
|
+
daily: 24 * HOUR_MS,
|
|
24
|
+
weekly: 7 * 24 * HOUR_MS,
|
|
25
|
+
monthly: 31 * 24 * HOUR_MS,
|
|
26
|
+
};
|
|
27
|
+
/** A limit that names no window is capped at the shortest subscription window. */
|
|
28
|
+
const UNNAMED_WINDOW_MS = 5 * HOUR_MS;
|
|
29
|
+
function namedWindowMs(word) {
|
|
30
|
+
if (word === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
const lower = word.toLowerCase();
|
|
33
|
+
const hours = /^(\d+)-hour$/.exec(lower);
|
|
34
|
+
if (hours)
|
|
35
|
+
return Number(hours[1]) * HOUR_MS;
|
|
36
|
+
return WINDOW_MS[lower];
|
|
37
|
+
}
|
|
38
|
+
/** The latest-resetting fully used window the SDK last reported for this account. */
|
|
39
|
+
function spentWindow(accountName, now) {
|
|
40
|
+
const limits = subscriptionLimits
|
|
41
|
+
.snapshot()
|
|
42
|
+
.find((entry) => entry.provider === PROVIDER && entry.account === accountName);
|
|
43
|
+
let spent;
|
|
44
|
+
for (const window of limits?.windows ?? []) {
|
|
45
|
+
if (window.usedPercent < 100 || window.resetsAt === undefined)
|
|
46
|
+
continue;
|
|
47
|
+
const resetsAt = Date.parse(window.resetsAt);
|
|
48
|
+
if (!Number.isFinite(resetsAt) || resetsAt <= now)
|
|
49
|
+
continue;
|
|
50
|
+
if (spent === undefined || resetsAt - now > spent.resetMs) {
|
|
51
|
+
spent = {
|
|
52
|
+
resetMs: resetsAt - now,
|
|
53
|
+
windowMs: window.windowMinutes === undefined ? undefined : window.windowMinutes * 60_000,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return spent;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Claude Code's own verdict that the account's subscription is spent: its anchored usage-limit prose
|
|
61
|
+
* or its structured terminal reason, under no status other than 429 (senpi#3462, senpi#3466).
|
|
62
|
+
*/
|
|
63
|
+
export function isConfirmedAccountUsageLimit(text) {
|
|
64
|
+
const trimmed = text.trim();
|
|
65
|
+
if (!CLAUDE_USAGE_LIMIT.test(trimmed) && !TERMINAL_LIMIT_REASON.test(trimmed))
|
|
66
|
+
return false;
|
|
67
|
+
const status = NAMED_STATUS.exec(trimmed)?.[1];
|
|
68
|
+
return status === undefined || status === "429";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* How long a spent account stays blocked: until the reset its limit names, or the reset its spent
|
|
72
|
+
* window reported, never past the window the limit belongs to. Undefined when the error is not
|
|
73
|
+
* Claude Code's account usage limit or names no reset, so the caller keeps its short default
|
|
74
|
+
* (senpi#3462).
|
|
75
|
+
*/
|
|
76
|
+
export function usageLimitBlockMs(text, accountName, now) {
|
|
77
|
+
const trimmed = text.trim();
|
|
78
|
+
if (!isConfirmedAccountUsageLimit(trimmed))
|
|
79
|
+
return undefined;
|
|
80
|
+
const shape = CLAUDE_USAGE_LIMIT.exec(trimmed);
|
|
81
|
+
const window = spentWindow(accountName, now);
|
|
82
|
+
const parsed = usageLimitResetMs(trimmed, now);
|
|
83
|
+
const resetMs = parsed !== undefined && parsed > 0 ? parsed : window?.resetMs;
|
|
84
|
+
if (resetMs === undefined)
|
|
85
|
+
return undefined;
|
|
86
|
+
const capMs = namedWindowMs(shape?.[1]) ?? window?.windowMs ?? UNNAMED_WINDOW_MS;
|
|
87
|
+
return Math.min(resetMs, capMs);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=usage-limit-block.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wider "Allow always" a prompt may offer for a tool WITHOUT its own permission parser, where
|
|
3
|
+
* plain "Allow always" remembers only the exact argument (one URL, one input) and so asks again for
|
|
4
|
+
* the next one (senpi#3403):
|
|
5
|
+
* - a URL argument: that URL's DOMAIN (scheme + canonical host + non-default port), stored as one
|
|
6
|
+
* literal and matched by equality, never the whole tool: a tool-wide allow for a browser or
|
|
7
|
+
* fetch tool would mean "go anywhere", close to Full access for the web;
|
|
8
|
+
* - a free `input`/`pattern` argument: the tool itself, for this project;
|
|
9
|
+
* - a command or a path: nothing wider (that would grant a shell or file access wholesale).
|
|
10
|
+
* Built-in tools (bash, eval, edit, write, read, ...) have parsers and never reach this.
|
|
11
|
+
*/
|
|
12
|
+
export interface WiderAlwaysScope {
|
|
13
|
+
/** The option label, stating the scope in plain words. */
|
|
14
|
+
readonly label: string;
|
|
15
|
+
/** The patterns an answer with this option approves for the permission. */
|
|
16
|
+
readonly patterns: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The one normalizer both sides of a domain grant go through (senpi#3403). A parser-less tool (an
|
|
20
|
+
* MCP server, a custom tool) parses the URL itself, and the permission layer cannot make it reuse
|
|
21
|
+
* this parse. So a domain origin is given only for raw text every parser reads as the same host:
|
|
22
|
+
* the strict shape above, no ambiguous character anywhere, and the same host and port as the WHATWG
|
|
23
|
+
* parser finds (#3416 review: `https://example.invalid\\@evil.test/` is example.invalid to WHATWG and
|
|
24
|
+
* evil.test to curl and Python). Anything else gets no alias and no domain option, so it asks.
|
|
25
|
+
*/
|
|
26
|
+
export declare function canonicalUrlOrigin(raw: string): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* The match target a domain grant is written for: a literal that never contains a wildcard
|
|
29
|
+
* character, so the grant matches by equality. The `origin=` prefix keeps it from colliding with a
|
|
30
|
+
* user's own URL rules.
|
|
31
|
+
*/
|
|
32
|
+
export declare function urlOriginAlias(raw: string): string | undefined;
|
|
33
|
+
export declare function widerAlwaysScope(toolName: string, argKey: string, arg: string): WiderAlwaysScope | undefined;
|
|
34
|
+
//# sourceMappingURL=always-scope.d.ts.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Never widened, even if a future change routes one of them through the fallback. */
|
|
2
|
+
const NEVER_WIDENED = new Set([
|
|
3
|
+
"bash",
|
|
4
|
+
"eval",
|
|
5
|
+
"edit",
|
|
6
|
+
"write",
|
|
7
|
+
"read",
|
|
8
|
+
"apply_patch",
|
|
9
|
+
"multiedit",
|
|
10
|
+
"glob",
|
|
11
|
+
"grep",
|
|
12
|
+
"ls",
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* The only raw URL shape that gets a domain alias: `http(s)://`, an ASCII DNS host (letters, digits,
|
|
16
|
+
* dots, dashes; no trailing dot), an optional port, then `/`, `?`, `#` or the end. No userinfo, no
|
|
17
|
+
* percent-escape, no IPv6 brackets.
|
|
18
|
+
*/
|
|
19
|
+
const UNAMBIGUOUS_URL = /^(https?):\/\/([A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?)(?::([0-9]{1,5}))?(?:[/?#]|$)/;
|
|
20
|
+
/** Characters that let one parser read another host than the next: backslash, `@`, `%`, whitespace, controls. */
|
|
21
|
+
const AMBIGUOUS_CHARACTER = /[\\@%\s\u0000-\u001f\u007f]/;
|
|
22
|
+
const DEFAULT_PORTS = { http: "80", https: "443" };
|
|
23
|
+
/**
|
|
24
|
+
* The one normalizer both sides of a domain grant go through (senpi#3403). A parser-less tool (an
|
|
25
|
+
* MCP server, a custom tool) parses the URL itself, and the permission layer cannot make it reuse
|
|
26
|
+
* this parse. So a domain origin is given only for raw text every parser reads as the same host:
|
|
27
|
+
* the strict shape above, no ambiguous character anywhere, and the same host and port as the WHATWG
|
|
28
|
+
* parser finds (#3416 review: `https://example.invalid\\@evil.test/` is example.invalid to WHATWG and
|
|
29
|
+
* evil.test to curl and Python). Anything else gets no alias and no domain option, so it asks.
|
|
30
|
+
*/
|
|
31
|
+
export function canonicalUrlOrigin(raw) {
|
|
32
|
+
if (AMBIGUOUS_CHARACTER.test(raw))
|
|
33
|
+
return undefined;
|
|
34
|
+
const match = UNAMBIGUOUS_URL.exec(raw);
|
|
35
|
+
if (match === null)
|
|
36
|
+
return undefined;
|
|
37
|
+
const scheme = match[1].toLowerCase();
|
|
38
|
+
const host = match[2].toLowerCase();
|
|
39
|
+
const port = match[3] === undefined || match[3] === DEFAULT_PORTS[scheme] ? "" : String(Number(match[3]));
|
|
40
|
+
let url;
|
|
41
|
+
try {
|
|
42
|
+
url = new URL(raw);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
if (url.protocol !== `${scheme}:` || url.hostname !== host || url.port !== port)
|
|
48
|
+
return undefined;
|
|
49
|
+
return `${scheme}://${host}${port === "" ? "" : `:${port}`}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The match target a domain grant is written for: a literal that never contains a wildcard
|
|
53
|
+
* character, so the grant matches by equality. The `origin=` prefix keeps it from colliding with a
|
|
54
|
+
* user's own URL rules.
|
|
55
|
+
*/
|
|
56
|
+
export function urlOriginAlias(raw) {
|
|
57
|
+
const origin = canonicalUrlOrigin(raw);
|
|
58
|
+
return origin === undefined ? undefined : `origin=${origin}`;
|
|
59
|
+
}
|
|
60
|
+
export function widerAlwaysScope(toolName, argKey, arg) {
|
|
61
|
+
if (NEVER_WIDENED.has(toolName))
|
|
62
|
+
return undefined;
|
|
63
|
+
if (argKey === "url") {
|
|
64
|
+
const origin = canonicalUrlOrigin(arg);
|
|
65
|
+
if (origin === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
return {
|
|
68
|
+
label: `Allow ${toolName} on ${origin.slice(origin.indexOf("//") + 2)} always in this project`,
|
|
69
|
+
patterns: [`origin=${origin}`],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (argKey === "input" || argKey === "pattern") {
|
|
73
|
+
return { label: `Allow ${toolName} always in this project`, patterns: ["*"] };
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=always-scope.js.map
|
|
@@ -261,6 +261,7 @@ export default function permissionSystemExtension(pi) {
|
|
|
261
261
|
permission: permissionRequest.permission,
|
|
262
262
|
patterns: permissionRequest.patterns,
|
|
263
263
|
always: permissionRequest.always,
|
|
264
|
+
...(permissionRequest.widerAlways ? { widerAlways: permissionRequest.widerAlways } : {}),
|
|
264
265
|
metadata: createRequestMetadata(event.toolName, event.input),
|
|
265
266
|
tool: {
|
|
266
267
|
callID: event.toolCallId,
|
|
@@ -295,7 +296,8 @@ export default function permissionSystemExtension(pi) {
|
|
|
295
296
|
const reply = await showPermissionPrompt(ctx, request, signal);
|
|
296
297
|
activeService.reply(reply);
|
|
297
298
|
if (reply.reply === "always") {
|
|
298
|
-
|
|
299
|
+
const answered = reply.always ? { ...request, always: reply.always } : request;
|
|
300
|
+
persistAlways(ctx.agentDir, ctx.cwd, answered, persistedAtAnswer);
|
|
299
301
|
}
|
|
300
302
|
}
|
|
301
303
|
else {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ToolInfo } from "../../types.ts";
|
|
2
2
|
import type { Request } from "../permission-system/types.ts";
|
|
3
3
|
/** Simplified permission request without ID/session metadata */
|
|
4
|
-
export type PermissionRequest = Pick<Request, "permission" | "patterns" | "always"> & {
|
|
4
|
+
export type PermissionRequest = Pick<Request, "permission" | "patterns" | "always" | "widerAlways"> & {
|
|
5
5
|
readonly autoApproveAsk?: true;
|
|
6
6
|
readonly ruleAliases?: readonly string[];
|
|
7
7
|
};
|
|
@@ -4,6 +4,7 @@ import { extractPatchedPaths } from "../gpt-apply-patch/index.js";
|
|
|
4
4
|
import { BashArity } from "../permission-system/arity.js";
|
|
5
5
|
import { extractExternalPaths, isExternalPath, toParentDirectoryPattern, } from "../permission-system/external-dir.js";
|
|
6
6
|
import { setApprovedMonitorParent } from "../terminal/monitor-permission.js";
|
|
7
|
+
import { urlOriginAlias, widerAlwaysScope } from "./always-scope.js";
|
|
7
8
|
import { parseReadPermission } from "./read-permission.js";
|
|
8
9
|
function fallbackPermissionRequest(permission) {
|
|
9
10
|
return {
|
|
@@ -19,12 +20,32 @@ function fallbackPermissionRequest(permission) {
|
|
|
19
20
|
* meaningful arg — path, command, url, or input — and keeps `*` only for a truly arg-less call.
|
|
20
21
|
*/
|
|
21
22
|
function fallbackPermissionRequestFor(toolName, input) {
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
23
|
+
const found = getStringEntry(input, "path", "file_path", "command", "url", "input", "pattern", "filePath");
|
|
24
|
+
if (found === undefined)
|
|
24
25
|
return fallbackPermissionRequest(toolName);
|
|
26
|
+
const [argKey, arg] = found;
|
|
25
27
|
// A bare `*` alongside the concrete arg would still match an ask/all rule and widen past a
|
|
26
28
|
// pattern-specific deny, so the concrete arg is the only target; `*` is the arg-less fallback.
|
|
27
|
-
|
|
29
|
+
// "Allow always" keeps the exact arg; a wider scope is a separate, labelled choice (senpi#3403).
|
|
30
|
+
const widerAlways = widerAlwaysScope(toolName, argKey, arg);
|
|
31
|
+
// A URL is also matched as its canonical origin, so a domain grant (a literal origin) applies to
|
|
32
|
+
// every spelling of that origin; rules on the raw URL, a deny included, still match the raw URL.
|
|
33
|
+
const originAlias = argKey === "url" ? urlOriginAlias(arg) : undefined;
|
|
34
|
+
return {
|
|
35
|
+
permission: toolName,
|
|
36
|
+
patterns: [arg],
|
|
37
|
+
always: [arg],
|
|
38
|
+
...(originAlias === undefined ? {} : { ruleAliases: [arg, originAlias] }),
|
|
39
|
+
...(widerAlways ? { widerAlways } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function getStringEntry(input, ...keys) {
|
|
43
|
+
for (const key of keys) {
|
|
44
|
+
const value = input[key];
|
|
45
|
+
if (typeof value === "string")
|
|
46
|
+
return [key, value];
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
28
49
|
}
|
|
29
50
|
function getString(input, ...keys) {
|
|
30
51
|
for (const key of keys) {
|
|
@@ -2,7 +2,8 @@ export async function showPermissionPrompt(ctx, request, signal) {
|
|
|
2
2
|
const title = `Permission required: ${request.permission}`;
|
|
3
3
|
const message = formatRequestForDisplay(request);
|
|
4
4
|
const displayTitle = `${title}\n\n${message}`;
|
|
5
|
-
const
|
|
5
|
+
const wider = request.widerAlways;
|
|
6
|
+
const options = ["Allow once", "Allow always", ...(wider ? [wider.label] : []), "Deny", "Deny with feedback"];
|
|
6
7
|
// Clients bind the dialog to the call it approves by this id; with several calls of one tool in
|
|
7
8
|
// flight (the engine prepares a message's calls before running them), nothing else says which.
|
|
8
9
|
const call = request.tool
|
|
@@ -22,6 +23,9 @@ export async function showPermissionPrompt(ctx, request, signal) {
|
|
|
22
23
|
message: feedback || undefined,
|
|
23
24
|
};
|
|
24
25
|
}
|
|
26
|
+
if (wider !== undefined && choice === wider.label) {
|
|
27
|
+
return { requestID: request.id, reply: "always", always: [...wider.patterns] };
|
|
28
|
+
}
|
|
25
29
|
const replyMap = {
|
|
26
30
|
"Allow once": "once",
|
|
27
31
|
"Allow always": "always",
|
|
@@ -206,7 +206,7 @@ export class PermissionService {
|
|
|
206
206
|
if (input.reply === "once") {
|
|
207
207
|
return;
|
|
208
208
|
}
|
|
209
|
-
for (const pattern of existing.info.always) {
|
|
209
|
+
for (const pattern of input.always ?? existing.info.always) {
|
|
210
210
|
this.approved.push({
|
|
211
211
|
permission: existing.info.permission,
|
|
212
212
|
pattern,
|
|
@@ -22,6 +22,11 @@ export type Request = {
|
|
|
22
22
|
permission: string;
|
|
23
23
|
patterns: string[];
|
|
24
24
|
always: string[];
|
|
25
|
+
/** A wider "Allow always" scope the prompt may offer (tools without a parser, senpi#3403). */
|
|
26
|
+
widerAlways?: {
|
|
27
|
+
readonly label: string;
|
|
28
|
+
readonly patterns: string[];
|
|
29
|
+
};
|
|
25
30
|
metadata: Record<string, unknown>;
|
|
26
31
|
/** The tool call that raised this request (`parentCallID` when another tool issued it). */
|
|
27
32
|
tool?: {
|
|
@@ -35,6 +40,8 @@ export type ReplyInput = {
|
|
|
35
40
|
requestID: string;
|
|
36
41
|
reply: Reply;
|
|
37
42
|
message?: string;
|
|
43
|
+
/** With reply "always": the patterns to approve instead of the request's own (a wider scope). */
|
|
44
|
+
always?: string[];
|
|
38
45
|
};
|
|
39
46
|
/** Error thrown when user rejects a permission request */
|
|
40
47
|
export declare class RejectedError extends Error {
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { chmodSync, existsSync, globSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { stripBom } from "../utils/text.js";
|
|
5
|
+
import { projectAgentsSkillDirs } from "./trust-manager.js";
|
|
5
6
|
function getEnv() {
|
|
6
7
|
if (process.platform !== "linux" || Object.keys(process.env).length > 0) {
|
|
7
8
|
return process.env;
|
|
@@ -207,36 +208,9 @@ function collectFiles(dir, filePattern, skipNodeModules = true, ignoreMatcher, r
|
|
|
207
208
|
}
|
|
208
209
|
return files;
|
|
209
210
|
}
|
|
210
|
-
function findGitRepoRoot(startDir) {
|
|
211
|
-
let dir = resolve(startDir);
|
|
212
|
-
while (true) {
|
|
213
|
-
if (existsSync(join(dir, ".git"))) {
|
|
214
|
-
return dir;
|
|
215
|
-
}
|
|
216
|
-
const parent = dirname(dir);
|
|
217
|
-
if (parent === dir) {
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
|
-
dir = parent;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
211
|
function collectAncestorAgentsSkillDirs(startDir) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const gitRepoRoot = findGitRepoRoot(resolvedStartDir);
|
|
227
|
-
let dir = resolvedStartDir;
|
|
228
|
-
while (true) {
|
|
229
|
-
skillDirs.push(join(dir, ".agents", "skills"));
|
|
230
|
-
if (gitRepoRoot && dir === gitRepoRoot) {
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
const parent = dirname(dir);
|
|
234
|
-
if (parent === dir) {
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
dir = parent;
|
|
238
|
-
}
|
|
239
|
-
return skillDirs;
|
|
212
|
+
// The same walk the trust check uses: home and above are never project skills (#3420 review).
|
|
213
|
+
return projectAgentsSkillDirs(startDir);
|
|
240
214
|
}
|
|
241
215
|
function collectAutoPromptEntries(dir) {
|
|
242
216
|
const entries = [];
|
|
@@ -1964,9 +1938,7 @@ export class DefaultPackageManager {
|
|
|
1964
1938
|
};
|
|
1965
1939
|
const userAgentsSkillsDir = join(getHomeDir(), ".agents", "skills");
|
|
1966
1940
|
const projectTrusted = this.settingsManager.isProjectTrusted();
|
|
1967
|
-
const projectAgentsSkillDirs = projectTrusted
|
|
1968
|
-
? collectAncestorAgentsSkillDirs(this.cwd).filter((dir) => resolve(dir) !== resolve(userAgentsSkillsDir))
|
|
1969
|
-
: [];
|
|
1941
|
+
const projectAgentsSkillDirs = projectTrusted ? collectAncestorAgentsSkillDirs(this.cwd) : [];
|
|
1970
1942
|
const addResources = (resourceType, paths, metadata, overrides, baseDir) => {
|
|
1971
1943
|
const target = this.getTargetMap(accumulator, resourceType);
|
|
1972
1944
|
for (const path of paths) {
|
|
@@ -2,6 +2,14 @@ import type { LoadExtensionsResult, ProjectTrustContext } from "./extensions/typ
|
|
|
2
2
|
import type { DefaultProjectTrust } from "./settings-manager.ts";
|
|
3
3
|
import { type ProjectTrustStore } from "./trust-manager.ts";
|
|
4
4
|
export type AppMode = "interactive" | "print" | "json" | "rpc";
|
|
5
|
+
/**
|
|
6
|
+
* Full access means no prompt anywhere (sisyphuslabs/omo-desktop-app#2465): a session whose
|
|
7
|
+
* permission preset is explicitly `full-access` trusts its project for that session. Explicit
|
|
8
|
+
* means the session's `permission-preset` flag (CLI, or `open_session.permissionPreset` through
|
|
9
|
+
* the launch profile), else the user's global setting. Project settings never count: they are what
|
|
10
|
+
* trust gates. The implicit default never counts either, so a plain launch keeps asking.
|
|
11
|
+
*/
|
|
12
|
+
export declare function presetTrustsProject(presetFlag: boolean | string | undefined, globalPreset: unknown): boolean;
|
|
5
13
|
export interface ResolveProjectTrustedOptions {
|
|
6
14
|
cwd: string;
|
|
7
15
|
trustStore: ProjectTrustStore;
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { APP_NAME, CONFIG_DIR_NAME } from "../config.js";
|
|
2
2
|
import { emitProjectTrustEvent } from "./extensions/runner.js";
|
|
3
3
|
import { getProjectTrustOptions, hasTrustRequiringProjectResources, } from "./trust-manager.js";
|
|
4
|
+
/**
|
|
5
|
+
* Full access means no prompt anywhere (sisyphuslabs/omo-desktop-app#2465): a session whose
|
|
6
|
+
* permission preset is explicitly `full-access` trusts its project for that session. Explicit
|
|
7
|
+
* means the session's `permission-preset` flag (CLI, or `open_session.permissionPreset` through
|
|
8
|
+
* the launch profile), else the user's global setting. Project settings never count: they are what
|
|
9
|
+
* trust gates. The implicit default never counts either, so a plain launch keeps asking.
|
|
10
|
+
*/
|
|
11
|
+
export function presetTrustsProject(presetFlag, globalPreset) {
|
|
12
|
+
if (typeof presetFlag === "string")
|
|
13
|
+
return presetFlag === "full-access";
|
|
14
|
+
return globalPreset === "full-access";
|
|
15
|
+
}
|
|
4
16
|
function formatProjectTrustPrompt(cwd) {
|
|
5
17
|
return `Trust project folder?\n${cwd}\n\nThis allows ${APP_NAME} to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;
|
|
6
18
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Theme } from "../modes/interactive/theme/theme.ts";
|
|
2
2
|
import type { ResourceDiagnostic } from "./diagnostics.ts";
|
|
3
3
|
export type { ResourceCollision, ResourceDiagnostic } from "./diagnostics.ts";
|
|
4
|
+
export { loadProjectContextFiles };
|
|
5
|
+
import { loadProjectContextFiles } from "./context-files.ts";
|
|
4
6
|
import { type EventBus } from "./event-bus.ts";
|
|
5
7
|
import type { HostMcpRegistry } from "./extensions/builtin/mcp/host-registry.ts";
|
|
6
8
|
import type { ExtensionFactory, InlineExtension, LoadExtensionsResult, LoadedHookSources, SessionContext, SessionKind } from "./extensions/types.ts";
|
|
@@ -64,6 +66,7 @@ export interface ResourceLoader {
|
|
|
64
66
|
path: string;
|
|
65
67
|
content: string;
|
|
66
68
|
}>;
|
|
69
|
+
diagnostics?: ResourceDiagnostic[];
|
|
67
70
|
};
|
|
68
71
|
getSystemPrompt(): string | undefined;
|
|
69
72
|
getSystemPromptSource(): {
|
|
@@ -85,13 +88,6 @@ export interface ResourceLoader {
|
|
|
85
88
|
* session. Canonicalizing collapses both spellings to one path.
|
|
86
89
|
*/
|
|
87
90
|
export declare function canonicalizeGlobalDefaultExtensionModulePath(modulePath: string): string;
|
|
88
|
-
export declare function loadProjectContextFiles(options: {
|
|
89
|
-
cwd: string;
|
|
90
|
-
agentDir: string;
|
|
91
|
-
}): Array<{
|
|
92
|
-
path: string;
|
|
93
|
-
content: string;
|
|
94
|
-
}>;
|
|
95
91
|
export interface DefaultResourceLoaderOptions {
|
|
96
92
|
cwd: string;
|
|
97
93
|
agentDir: string;
|
|
@@ -185,6 +181,7 @@ export declare class DefaultResourceLoader implements ResourceLoader {
|
|
|
185
181
|
private themes;
|
|
186
182
|
private themeDiagnostics;
|
|
187
183
|
private agentsFiles;
|
|
184
|
+
private contextDiagnostics;
|
|
188
185
|
private systemPrompt?;
|
|
189
186
|
private systemPromptSourcePath?;
|
|
190
187
|
private appendSystemPrompt;
|
|
@@ -222,6 +219,7 @@ export declare class DefaultResourceLoader implements ResourceLoader {
|
|
|
222
219
|
path: string;
|
|
223
220
|
content: string;
|
|
224
221
|
}>;
|
|
222
|
+
diagnostics: ResourceDiagnostic[];
|
|
225
223
|
};
|
|
226
224
|
getSystemPrompt(): string | undefined;
|
|
227
225
|
getSystemPromptSource(): {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
4
|
import { detectCapabilities, getTerminalColorMode } from "@earendil-works/pi-tui";
|
|
5
5
|
import chalk from "chalk";
|
|
@@ -7,22 +7,24 @@ import { APP_NAME, CONFIG_DIR_NAME, findNodePackageDir, getAgentDir, getPackageD
|
|
|
7
7
|
import { loadThemeFromPath } from "../modes/interactive/theme/theme.js";
|
|
8
8
|
import { resolveInstallPath } from "../runtime-snapshot/marker.js";
|
|
9
9
|
import { ACCEPTED_SHIM_BANNERS, GENERATED_SHIM_BANNER } from "./generated-shim-banner.js";
|
|
10
|
+
export { loadProjectContextFiles };
|
|
10
11
|
import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.js";
|
|
11
12
|
import { stripBom } from "../utils/text.js";
|
|
12
13
|
import { bundledBuiltinExtensions } from "./bundled-resources.js";
|
|
14
|
+
import { loadProjectContextFiles } from "./context-files.js";
|
|
13
15
|
import { createEventBus } from "./event-bus.js";
|
|
14
16
|
import { builtinExtensions, globalDefaultExtensionFactories, globalDefaultExtensionIds, } from "./extensions/builtin/index.js";
|
|
15
17
|
import { createMcpExtension } from "./extensions/builtin/mcp/index.js";
|
|
16
18
|
import { McpService } from "./extensions/builtin/mcp/service.js";
|
|
17
19
|
import { createExtensionRuntime, loadExtensionFromFactory, loadExtensions, } from "./extensions/loader.js";
|
|
18
20
|
import { EMPTY_SESSION_CONTEXT } from "./extensions/types.js";
|
|
19
|
-
import { findGitPaths } from "./footer-data-provider.js";
|
|
20
21
|
import { dedupePathsByPackageIdentity, findNearestPackageIdentity } from "./package-identity.js";
|
|
21
22
|
import { DefaultPackageManager } from "./package-manager.js";
|
|
22
23
|
import { loadPromptTemplates } from "./prompt-templates.js";
|
|
23
24
|
import { memoizeResolvedPaths, resolvedPathsMemoKey } from "./resolved-paths-memo.js";
|
|
24
25
|
import { SettingsManager } from "./settings-manager.js";
|
|
25
26
|
import { loadSkills } from "./skills.js";
|
|
27
|
+
import { warnIfLargeSkillCatalog } from "./skills-prompt.js";
|
|
26
28
|
import { BUILTIN_PATH_PREFIX, createSourceInfo, getSyntheticPathSource, isSyntheticPath, } from "./source-info.js";
|
|
27
29
|
import { resetTimings, time } from "./timings.js";
|
|
28
30
|
const HOST_PROVIDED_EXTENSION_PACKAGES = new Set([
|
|
@@ -256,86 +258,9 @@ function resolveGeneratedGlobalDefaultExtensionFactory(extensionPath, agentDir)
|
|
|
256
258
|
return undefined;
|
|
257
259
|
}
|
|
258
260
|
}
|
|
259
|
-
function loadContextFileFromDir(dir) {
|
|
260
|
-
const candidates = ["AGENTS.override.md", "AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"];
|
|
261
|
-
for (const filename of candidates) {
|
|
262
|
-
const filePath = join(dir, filename);
|
|
263
|
-
if (existsSync(filePath)) {
|
|
264
|
-
try {
|
|
265
|
-
if (!statSync(filePath).isFile()) {
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
return {
|
|
269
|
-
path: filePath,
|
|
270
|
-
content: stripBom(readFileSync(filePath, "utf-8")),
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
catch (error) {
|
|
274
|
-
console.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* The main repo's context file that a nested linked worktree's own copy shadows: both
|
|
282
|
-
* occupy the same logical repository scope, so loading both applies that context twice. Returns
|
|
283
|
-
* undefined when nothing is shadowed, leaving normal ancestor inheritance alone.
|
|
284
|
-
*
|
|
285
|
-
* Returned canonicalized (realpath), because `git worktree add` writes the `.git`
|
|
286
|
-
* file's `gitdir:` target in realpath form while cwd may still be symlinked
|
|
287
|
-
* (macOS `/tmp` -> `/private/tmp`).
|
|
288
|
-
*/
|
|
289
|
-
function findShadowedContextFile(cwd) {
|
|
290
|
-
const gitPaths = findGitPaths(cwd);
|
|
291
|
-
if (!gitPaths)
|
|
292
|
-
return undefined;
|
|
293
|
-
const commonGitDir = canonicalizePath(gitPaths.commonGitDir);
|
|
294
|
-
const worktreeRoot = canonicalizePath(gitPaths.repoDir);
|
|
295
|
-
const mainRepoRoot = dirname(commonGitDir);
|
|
296
|
-
// False for an ordinary repo, where the two are the same dir, and for a sibling
|
|
297
|
-
// worktree (`git worktree add ../feat`), whose main repo is not an ancestor.
|
|
298
|
-
if (!worktreeRoot.startsWith(`${mainRepoRoot}${sep}`))
|
|
299
|
-
return undefined;
|
|
300
|
-
// dirname of the common git dir is the main worktree root only when that dir is
|
|
301
|
-
// itself checked out from the same repo. In a bare layout (`proj/.bare` +
|
|
302
|
-
// `proj/main`) it is just the directory holding `.bare`, which tracks nothing; a
|
|
303
|
-
// submodule's gitdir has no `commondir`, so it lands under `.git/modules`.
|
|
304
|
-
if (canonicalizePath(join(mainRepoRoot, ".git")) !== commonGitDir)
|
|
305
|
-
return undefined;
|
|
306
|
-
const worktreeContextFile = loadContextFileFromDir(worktreeRoot);
|
|
307
|
-
return worktreeContextFile ? join(mainRepoRoot, basename(worktreeContextFile.path)) : undefined;
|
|
308
|
-
}
|
|
309
|
-
export function loadProjectContextFiles(options) {
|
|
310
|
-
const resolvedCwd = resolvePath(options.cwd);
|
|
311
|
-
const resolvedAgentDir = resolvePath(options.agentDir);
|
|
312
|
-
const contextFiles = [];
|
|
313
|
-
const seenPaths = new Set();
|
|
314
|
-
const globalContext = loadContextFileFromDir(resolvedAgentDir);
|
|
315
|
-
if (globalContext) {
|
|
316
|
-
contextFiles.push(globalContext);
|
|
317
|
-
seenPaths.add(globalContext.path);
|
|
318
|
-
}
|
|
319
|
-
const ancestorContextFiles = [];
|
|
320
|
-
const shadowedContextFile = findShadowedContextFile(resolvedCwd);
|
|
321
|
-
let currentDir = resolvedCwd;
|
|
322
|
-
while (true) {
|
|
323
|
-
const contextFile = loadContextFileFromDir(currentDir);
|
|
324
|
-
const isShadowed = shadowedContextFile !== undefined && canonicalizePath(contextFile?.path ?? "") === shadowedContextFile;
|
|
325
|
-
if (contextFile && !isShadowed && !seenPaths.has(contextFile.path)) {
|
|
326
|
-
ancestorContextFiles.unshift(contextFile);
|
|
327
|
-
seenPaths.add(contextFile.path);
|
|
328
|
-
}
|
|
329
|
-
const parentDir = dirname(currentDir);
|
|
330
|
-
if (parentDir === currentDir)
|
|
331
|
-
break;
|
|
332
|
-
currentDir = parentDir;
|
|
333
|
-
}
|
|
334
|
-
contextFiles.push(...ancestorContextFiles);
|
|
335
|
-
return contextFiles;
|
|
336
|
-
}
|
|
337
261
|
export class DefaultResourceLoader {
|
|
338
262
|
constructor(options) {
|
|
263
|
+
this.contextDiagnostics = [];
|
|
339
264
|
this.cwd = resolvePath(options.cwd);
|
|
340
265
|
this.agentDir = resolvePath(options.agentDir);
|
|
341
266
|
this.settingsManager = options.settingsManager ?? SettingsManager.create(this.cwd, this.agentDir);
|
|
@@ -421,7 +346,7 @@ export class DefaultResourceLoader {
|
|
|
421
346
|
return { themes: this.themes, diagnostics: this.themeDiagnostics };
|
|
422
347
|
}
|
|
423
348
|
getAgentsFiles() {
|
|
424
|
-
return { agentsFiles: this.agentsFiles };
|
|
349
|
+
return { agentsFiles: this.agentsFiles, diagnostics: this.contextDiagnostics };
|
|
425
350
|
}
|
|
426
351
|
getSystemPrompt() {
|
|
427
352
|
return this.systemPrompt;
|
|
@@ -616,14 +541,16 @@ export class DefaultResourceLoader {
|
|
|
616
541
|
}
|
|
617
542
|
const agentsFiles = {
|
|
618
543
|
agentsFiles: this.noContextFiles
|
|
619
|
-
? []
|
|
544
|
+
? Object.assign([], { diagnostics: [] })
|
|
620
545
|
: loadProjectContextFiles({
|
|
621
546
|
cwd: this.cwd,
|
|
622
547
|
agentDir: this.agentDir,
|
|
548
|
+
...this.settingsManager.getSettings().contextFiles,
|
|
623
549
|
}),
|
|
624
550
|
};
|
|
625
551
|
const resolvedAgentsFiles = this.agentsFilesOverride ? this.agentsFilesOverride(agentsFiles) : agentsFiles;
|
|
626
|
-
this.agentsFiles = resolvedAgentsFiles.agentsFiles;
|
|
552
|
+
this.agentsFiles = [...resolvedAgentsFiles.agentsFiles];
|
|
553
|
+
this.contextDiagnostics = agentsFiles.agentsFiles.diagnostics;
|
|
627
554
|
time("contextFiles", "extensions");
|
|
628
555
|
// SYSTEM.md / APPEND_SYSTEM.md file discovery was intentionally removed; the explicit
|
|
629
556
|
// options are the only static prompt source (see packages/coding-agent/changes.md).
|
|
@@ -872,7 +799,7 @@ export class DefaultResourceLoader {
|
|
|
872
799
|
skill.sourceInfo ??
|
|
873
800
|
this.getDefaultSourceInfoForPath(skill.filePath),
|
|
874
801
|
}));
|
|
875
|
-
this.skillDiagnostics = resolvedSkills.diagnostics;
|
|
802
|
+
this.skillDiagnostics = [...resolvedSkills.diagnostics, ...warnIfLargeSkillCatalog(this.skills)];
|
|
876
803
|
}
|
|
877
804
|
updatePromptsFromPaths(promptPaths, metadataByPath) {
|
|
878
805
|
let promptsResult;
|
|
@@ -16,6 +16,7 @@ export declare class CircuitProbes {
|
|
|
16
16
|
constructor(circuits: FallbackCircuitAccess | undefined);
|
|
17
17
|
isOpen(selector: string): boolean;
|
|
18
18
|
governs(selector: string): boolean;
|
|
19
|
+
isExhausted(selector: string): boolean;
|
|
19
20
|
admit(selector: string): CircuitAdmission["kind"];
|
|
20
21
|
holds(selector: string): boolean;
|
|
21
22
|
get probing(): string | undefined;
|