@code-yeongyu/senpi 2026.10.5 → 2026.10.6
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 +29 -0
- package/dist/bundle/chunks/{anthropic-messages-G5236QNK.js → anthropic-messages-4MNFPQ66.js} +1 -1
- package/dist/bundle/chunks/{app-server-command-3Q5G4CAF.js → app-server-command-GNTX4MZT.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-NTA2HGG4.js → azure-openai-responses-A5OEHSJM.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/{chunk-EEOKVSNF.js → chunk-3EE6G4G7.js} +180 -176
- package/dist/bundle/chunks/{chunk-2WV426OJ.js → chunk-4MJKBXI3.js} +1 -1
- package/dist/bundle/chunks/{chunk-ECDLITEY.js → chunk-6EH7QXOG.js} +2 -2
- package/dist/bundle/chunks/chunk-6KQGOVQG.js +2 -0
- package/dist/bundle/chunks/{chunk-JVSOW4QU.js → chunk-7A3WRAKP.js} +1 -1
- package/dist/bundle/chunks/{chunk-WTJU5B2C.js → chunk-BE66QJ3Z.js} +1 -1
- package/dist/bundle/chunks/{chunk-UHRXXTFZ.js → chunk-D2W3MXRF.js} +2 -2
- package/dist/bundle/chunks/{chunk-U7CI2JGX.js → chunk-DDEAZX22.js} +1 -1
- package/dist/bundle/chunks/{chunk-UXIQK5NQ.js → chunk-ESSQUSRC.js} +1 -1
- package/dist/bundle/chunks/{chunk-KLBKUBDV.js → chunk-GQ3YLXVZ.js} +1 -1
- package/dist/bundle/chunks/{chunk-CTQU2XNX.js → chunk-MLNJNJFE.js} +1 -1
- package/dist/bundle/chunks/{chunk-BPX6QWPD.js → chunk-PELNNWGO.js} +1 -1
- package/dist/bundle/chunks/{chunk-UUAKEDVS.js → chunk-QLIW3YMI.js} +1 -1
- package/dist/bundle/chunks/{chunk-BBK6SSYT.js → chunk-TAV6QZFM.js} +1 -1
- package/dist/bundle/chunks/{chunk-QUROHJLP.js → chunk-TFO5JGLR.js} +1 -1
- package/dist/bundle/chunks/{cli-main-VZWSUHFB.js → cli-main-2C3YPHRE.js} +1 -1
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-5ZUHUKMU.js → cloudflare-workers-ai-system-one-TE3OEJEL.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-OTMG6ZXI.js → google-generative-ai-JBC53AMK.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-2LXS3ICC.js → google-vertex-46ODNPM2.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-SHZJ3QWQ.js → help-fast-path-FUMH5NSA.js} +1 -1
- package/dist/bundle/chunks/{host-command-7JIGGOWR.js → host-command-N6XS5O67.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-JA7AHYYL.js → interactive-mode-CM4C4ZFY.js} +1 -1
- package/dist/bundle/chunks/{llama-cpp-classify-U5XAMKZE.js → llama-cpp-classify-AD6TTKNL.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-34WOINP4.js → multi-session-host-QLLTPWIM.js} +5 -5
- package/dist/bundle/chunks/{openai-codex-responses-WNTYIPFZ.js → openai-codex-responses-Q4OWIK7Y.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-VSH22FCN.js → openai-completions-6HU7GIGD.js} +1 -1
- package/dist/bundle/chunks/{openai-images-XBTUJXPR.js → openai-images-M73YANTR.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-WLBHCFYL.js → openai-responses-S4QYZXF3.js} +1 -1
- package/dist/bundle/chunks/{openrouter-images-3LVJZY3G.js → openrouter-images-PCJFX2KO.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-2QLVGQSC.js → package-manager-cli-VRCAO7RV.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-JUCEXHWU.js → rotation-stream-7E37KOCX.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-QEBSN2OH.js → rpc-mode-2OPE6J7P.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-UGQIP6ZN.js → session-control-endpoint-SOG4XFF7.js} +1 -1
- package/dist/bundle/chunks/{session-picker-HX4LOJZ7.js → session-picker-VTTRFGE7.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +370 -366
- package/dist/bundle/chunks/{typesafe-system-one-FNI3UASE.js → typesafe-system-one-TF23RUVG.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- 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 +12 -0
- package/dist/core/agent-session.d.ts +18 -0
- package/dist/core/agent-session.js +55 -19
- package/dist/core/dynamic-prompt/handoff.d.ts +4 -2
- package/dist/core/dynamic-prompt/handoff.js +1 -1
- package/dist/core/extensions/builtin/diff.js +14 -9
- package/dist/core/extensions/builtin/files.js +6 -2
- package/dist/core/extensions/builtin/gpt-apply-patch/parser.js +5 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/streaming-parser.d.ts +8 -0
- package/dist/core/extensions/builtin/gpt-apply-patch/streaming-parser.js +26 -2
- package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.js +82 -11
- package/dist/core/extensions/builtin/gpt-apply-patch/types.d.ts +9 -1
- package/dist/core/extensions/builtin/permission-system/auto-credentials.d.ts +2 -0
- package/dist/core/extensions/builtin/permission-system/auto-credentials.js +17 -0
- package/dist/core/extensions/builtin/permission-system/auto-paths.d.ts +14 -0
- package/dist/core/extensions/builtin/permission-system/auto-paths.js +93 -0
- package/dist/core/extensions/builtin/permission-system/auto-policy.d.ts +20 -0
- package/dist/core/extensions/builtin/permission-system/auto-policy.js +126 -0
- package/dist/core/extensions/builtin/permission-system/auto-program-rules.d.ts +6 -0
- package/dist/core/extensions/builtin/permission-system/auto-program-rules.js +139 -0
- package/dist/core/extensions/builtin/permission-system/auto-shell-grammar.d.ts +29 -0
- package/dist/core/extensions/builtin/permission-system/auto-shell-grammar.js +74 -0
- package/dist/core/extensions/builtin/permission-system/auto-shell-segments.d.ts +13 -0
- package/dist/core/extensions/builtin/permission-system/auto-shell-segments.js +23 -0
- package/dist/core/extensions/builtin/permission-system/cli.d.ts +1 -0
- package/dist/core/extensions/builtin/permission-system/cli.js +9 -10
- package/dist/core/extensions/builtin/permission-system/config.d.ts +4 -1
- package/dist/core/extensions/builtin/permission-system/config.js +13 -1
- package/dist/core/extensions/builtin/permission-system/index.js +20 -12
- package/dist/core/extensions/builtin/permission-system/non-interactive.d.ts +11 -6
- package/dist/core/extensions/builtin/permission-system/non-interactive.js +8 -33
- package/dist/core/extensions/builtin/permission-system/service.d.ts +20 -1
- package/dist/core/extensions/builtin/permission-system/service.js +43 -7
- package/dist/core/extensions/builtin/permission-system/settings.d.ts +1 -0
- package/dist/core/extensions/builtin/permission-system/settings.js +4 -3
- package/dist/core/extensions/builtin/permission-system/types.d.ts +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +16 -3
- package/dist/core/retry-fallback/usage-limit.js +5 -1
- package/dist/core/tools/diff-render.d.ts +4 -0
- package/dist/core/tools/diff-render.js +13 -0
- package/dist/core/tools/renderers/edit.js +6 -4
- package/dist/main.js +12 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.js +55 -11
- package/dist/modes/interactive/provider-error-presentation.d.ts +25 -0
- package/dist/modes/interactive/provider-error-presentation.js +42 -0
- package/dist/modes/interactive/theme/dark.json +5 -5
- package/dist/modes/interactive/theme/light.json +5 -5
- package/dist/modes/rpc/custom-capability.d.ts +6 -0
- package/dist/modes/rpc/custom-capability.js +6 -0
- package/dist/modes/rpc/rpc-client.d.ts +3 -0
- package/dist/modes/rpc/rpc-input-validation.d.ts +7 -0
- package/dist/modes/rpc/rpc-input-validation.js +37 -0
- package/dist/modes/rpc/rpc-types.d.ts +9 -0
- package/dist/modes/rpc/session-command-activity.d.ts +2 -0
- package/dist/modes/rpc/session-command-activity.js +25 -0
- package/dist/modes/rpc/session-command-router.js +11 -4
- package/dist/modes/rpc/session-registry-attach.js +1 -0
- package/dist/modes/rpc/session-registry-types.d.ts +7 -0
- package/dist/modes/rpc/session-registry-types.js +11 -0
- package/dist/modes/rpc/session-registry.js +6 -3
- package/dist/modes/rpc/session-sweep.d.ts +12 -2
- package/dist/modes/rpc/session-sweep.js +36 -3
- package/dist/modes/rpc/session-teardown.d.ts +2 -0
- package/dist/modes/rpc/session-teardown.js +1 -0
- package/dist/modes/rpc/worker-session-registry.js +5 -1
- package/docs/rpc.md +39 -5
- package/docs/settings.md +3 -1
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-K3BXFPWZ.js +0 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classifies every word of one simple command against `spec`, or returns undefined when any word
|
|
3
|
+
* is not understood: an unknown flag (short or long), a flag in a form the spec does not list, a
|
|
4
|
+
* value-taking flag with no value, or an operand count out of range. Short flags may be
|
|
5
|
+
* clustered (`-la`), and a value-taking short flag takes the rest of its word as the value
|
|
6
|
+
* (`-o/x`, `-ro/x`) or else the next word; a long flag takes `--name=value` or `--name value`.
|
|
7
|
+
*/
|
|
8
|
+
export function classifyWords(words, spec) {
|
|
9
|
+
const classified = [];
|
|
10
|
+
const operands = [];
|
|
11
|
+
let optionsEnded = false;
|
|
12
|
+
for (let index = 0; index < words.length; index += 1) {
|
|
13
|
+
const word = words[index];
|
|
14
|
+
const text = word.text;
|
|
15
|
+
if (optionsEnded || text === "-" || !text.startsWith("-")) {
|
|
16
|
+
operands.push(word);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (text === "--") {
|
|
20
|
+
optionsEnded = true;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (text.startsWith("--")) {
|
|
24
|
+
const separator = text.indexOf("=");
|
|
25
|
+
const name = separator < 0 ? text : text.slice(0, separator);
|
|
26
|
+
const flag = spec.flags[name];
|
|
27
|
+
if (flag === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (flag === true) {
|
|
30
|
+
if (separator >= 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (separator >= 0) {
|
|
35
|
+
classified.push({ role: flag, word: { text: text.slice(separator + 1) } });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const value = words[index + 1];
|
|
39
|
+
if (value === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
classified.push({ role: flag, word: value });
|
|
42
|
+
index += 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
for (let position = 1; position < text.length; position += 1) {
|
|
46
|
+
const flag = spec.flags[`-${text[position]}`];
|
|
47
|
+
if (flag === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
if (flag === true)
|
|
50
|
+
continue;
|
|
51
|
+
const attached = text.slice(position + 1);
|
|
52
|
+
if (attached !== "") {
|
|
53
|
+
classified.push({ role: flag, word: { text: attached } });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const value = words[index + 1];
|
|
57
|
+
if (value === undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
classified.push({ role: flag, word: value });
|
|
60
|
+
index += 1;
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (operands.length < (spec.minOperands ?? 0))
|
|
66
|
+
return undefined;
|
|
67
|
+
if (spec.maxOperands !== undefined && operands.length > spec.maxOperands)
|
|
68
|
+
return undefined;
|
|
69
|
+
operands.forEach((word, index) => {
|
|
70
|
+
classified.push({ role: spec.operand(index, operands.length, word.text), word });
|
|
71
|
+
});
|
|
72
|
+
return classified;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=auto-shell-grammar.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits a shell command into the simple commands the `auto` preset can judge one by one.
|
|
3
|
+
*
|
|
4
|
+
* Only plain words joined by `;` or `&&` are understood. Quotes, escapes, expansions (`$`, `~`,
|
|
5
|
+
* globs), redirects, pipes, background jobs, subshells, comments and line breaks return undefined,
|
|
6
|
+
* and the caller asks the user.
|
|
7
|
+
*/
|
|
8
|
+
export interface ShellWord {
|
|
9
|
+
readonly text: string;
|
|
10
|
+
}
|
|
11
|
+
export type ShellSegment = readonly ShellWord[];
|
|
12
|
+
export declare function splitShellSegments(command: string): ShellSegment[] | undefined;
|
|
13
|
+
//# sourceMappingURL=auto-shell-segments.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const PLAIN_COMMAND = /^[A-Za-z0-9_\-.,/=+ \t;&]*$/;
|
|
2
|
+
const words = (part) => part
|
|
3
|
+
.split(/[ \t]+/)
|
|
4
|
+
.filter((text) => text !== "")
|
|
5
|
+
.map((text) => ({ text }));
|
|
6
|
+
export function splitShellSegments(command) {
|
|
7
|
+
if (!PLAIN_COMMAND.test(command))
|
|
8
|
+
return undefined;
|
|
9
|
+
const chains = command.split("&&");
|
|
10
|
+
if (chains.some((chain) => chain.includes("&")))
|
|
11
|
+
return undefined;
|
|
12
|
+
const segments = [];
|
|
13
|
+
for (const [index, chain] of chains.entries()) {
|
|
14
|
+
const parts = chain.split(";").map(words);
|
|
15
|
+
if (index > 0 && parts[0]?.length === 0)
|
|
16
|
+
return undefined;
|
|
17
|
+
if (index < chains.length - 1 && parts[parts.length - 1]?.length === 0)
|
|
18
|
+
return undefined;
|
|
19
|
+
segments.push(...parts.filter((part) => part.length > 0));
|
|
20
|
+
}
|
|
21
|
+
return segments;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=auto-shell-segments.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PermissionPresetName, Ruleset } from "./types.ts";
|
|
2
2
|
export declare function parsePermissionFlag(value: string): Ruleset;
|
|
3
3
|
export declare function parsePermissionPresetFlag(value: string): PermissionPresetName | undefined;
|
|
4
|
+
export declare const PERMISSION_PRESET_NAMES: readonly PermissionPresetName[];
|
|
4
5
|
export declare function parsePermissionPresetName(value: string): PermissionPresetName | undefined;
|
|
5
6
|
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -20,16 +20,15 @@ export function parsePermissionFlag(value) {
|
|
|
20
20
|
export function parsePermissionPresetFlag(value) {
|
|
21
21
|
return parsePermissionPresetName(value.trim());
|
|
22
22
|
}
|
|
23
|
+
export const PERMISSION_PRESET_NAMES = [
|
|
24
|
+
"full-access",
|
|
25
|
+
"workspace",
|
|
26
|
+
"accept-edits",
|
|
27
|
+
"auto",
|
|
28
|
+
"read-only",
|
|
29
|
+
"ask",
|
|
30
|
+
];
|
|
23
31
|
export function parsePermissionPresetName(value) {
|
|
24
|
-
|
|
25
|
-
case "full-access":
|
|
26
|
-
case "workspace":
|
|
27
|
-
case "accept-edits":
|
|
28
|
-
case "read-only":
|
|
29
|
-
case "ask":
|
|
30
|
-
return value;
|
|
31
|
-
default:
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
32
|
+
return PERMISSION_PRESET_NAMES.find((name) => name === value);
|
|
34
33
|
}
|
|
35
34
|
//# sourceMappingURL=cli.js.map
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import type { PermissionConfig, PermissionPresetName, Ruleset } from "./types.ts";
|
|
1
|
+
import type { PermissionConfig, PermissionPresetName, Rule, Ruleset } from "./types.ts";
|
|
2
2
|
export declare const EDIT_TOOLS: string[];
|
|
3
3
|
export declare const DEFAULT_PERMISSION_PRESET: PermissionPresetName;
|
|
4
4
|
export declare const ACCEPT_EDITS_PERMISSION_PRESET_CAPABILITY = "permission_preset_accept_edits";
|
|
5
|
+
export declare const AUTO_PERMISSION_PRESET_CAPABILITY = "permission_preset_auto";
|
|
5
6
|
export declare function expand(path: string): string;
|
|
6
7
|
export declare function fromConfig(config: PermissionConfig): Ruleset;
|
|
7
8
|
export declare function rulesForPreset(preset: PermissionPresetName): Ruleset;
|
|
9
|
+
/** True only for a rule object produced by `rulesForPreset`, never for a user's identical rule. */
|
|
10
|
+
export declare function isPresetRule(rule: Rule): boolean;
|
|
8
11
|
export declare function merge(...rulesets: Ruleset[]): Ruleset;
|
|
9
12
|
export declare function disabled(tools: string[], ruleset: Ruleset): Set<string>;
|
|
10
13
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -3,6 +3,7 @@ import { Wildcard } from "./wildcard.js";
|
|
|
3
3
|
export const EDIT_TOOLS = ["edit", "write", "apply_patch", "multiedit"];
|
|
4
4
|
export const DEFAULT_PERMISSION_PRESET = "full-access";
|
|
5
5
|
export const ACCEPT_EDITS_PERMISSION_PRESET_CAPABILITY = "permission_preset_accept_edits";
|
|
6
|
+
export const AUTO_PERMISSION_PRESET_CAPABILITY = "permission_preset_auto";
|
|
6
7
|
const PERMISSION_PRESET_RULES = {
|
|
7
8
|
"full-access": [{ permission: "*", pattern: "*", action: "allow" }],
|
|
8
9
|
workspace: [
|
|
@@ -23,6 +24,8 @@ const PERMISSION_PRESET_RULES = {
|
|
|
23
24
|
{ permission: "bash", pattern: "*", action: "ask" },
|
|
24
25
|
{ permission: "external_directory", pattern: "*", action: "ask" },
|
|
25
26
|
],
|
|
27
|
+
// Ask for everything; auto-policy.ts approves, over this rule only, the calls on its allowlist.
|
|
28
|
+
auto: [{ permission: "*", pattern: "*", action: "ask" }],
|
|
26
29
|
"read-only": [
|
|
27
30
|
{ permission: "*", pattern: "*", action: "ask" },
|
|
28
31
|
{ permission: "read", pattern: "*", action: "allow" },
|
|
@@ -63,8 +66,17 @@ export function fromConfig(config) {
|
|
|
63
66
|
}
|
|
64
67
|
return rules;
|
|
65
68
|
}
|
|
69
|
+
const presetRules = new WeakSet();
|
|
66
70
|
export function rulesForPreset(preset) {
|
|
67
|
-
return PERMISSION_PRESET_RULES[preset].map((rule) =>
|
|
71
|
+
return PERMISSION_PRESET_RULES[preset].map((rule) => {
|
|
72
|
+
const copy = { ...rule };
|
|
73
|
+
presetRules.add(copy);
|
|
74
|
+
return copy;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** True only for a rule object produced by `rulesForPreset`, never for a user's identical rule. */
|
|
78
|
+
export function isPresetRule(rule) {
|
|
79
|
+
return presetRules.has(rule);
|
|
68
80
|
}
|
|
69
81
|
export function merge(...rulesets) {
|
|
70
82
|
return rulesets.flat();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SettingsManager } from "../../../settings-manager.js";
|
|
2
2
|
import { extractPatchedPaths } from "../gpt-apply-patch/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import { decideAuto } from "./auto-policy.js";
|
|
4
|
+
import { PERMISSION_PRESET_NAMES, parsePermissionFlag, parsePermissionPresetFlag } from "./cli.js";
|
|
4
5
|
import { disabled } from "./config.js";
|
|
5
6
|
import { createEventEmitter } from "./events.js";
|
|
6
7
|
import { INTERNAL_PERMISSION_TOOLS } from "./internal-tools.js";
|
|
@@ -10,7 +11,7 @@ import { showPermissionPrompt } from "./prompt.js";
|
|
|
10
11
|
import { PermissionService } from "./service.js";
|
|
11
12
|
import { loadPermissionSettings } from "./settings.js";
|
|
12
13
|
import { appendApproved } from "./storage.js";
|
|
13
|
-
import { CorrectedError, DeniedError, RejectedError } from "./types.js";
|
|
14
|
+
import { CorrectedError, DeniedError, RejectedError, } from "./types.js";
|
|
14
15
|
function createRequestIDFactory() {
|
|
15
16
|
let counter = 0;
|
|
16
17
|
return () => {
|
|
@@ -48,6 +49,7 @@ export default function permissionSystemExtension(pi) {
|
|
|
48
49
|
let parserRegistry = null;
|
|
49
50
|
let cliRuleset = [];
|
|
50
51
|
let staticRuleset = [];
|
|
52
|
+
let activePreset = null;
|
|
51
53
|
let initialApprovedCount = 0;
|
|
52
54
|
let setupError = null;
|
|
53
55
|
const nextRequestID = createRequestIDFactory();
|
|
@@ -56,7 +58,7 @@ export default function permissionSystemExtension(pi) {
|
|
|
56
58
|
type: "string",
|
|
57
59
|
});
|
|
58
60
|
pi.registerFlag("permission-preset", {
|
|
59
|
-
description:
|
|
61
|
+
description: `Set permission preset (${PERMISSION_PRESET_NAMES.join(", ")})`,
|
|
60
62
|
type: "string",
|
|
61
63
|
});
|
|
62
64
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -79,10 +81,11 @@ export default function permissionSystemExtension(pi) {
|
|
|
79
81
|
cliRuleset = typeof permissionFlag === "string" ? parsePermissionFlag(permissionFlag) : [];
|
|
80
82
|
const cliPreset = typeof permissionPresetFlag === "string" ? parsePermissionPresetFlag(permissionPresetFlag) : undefined;
|
|
81
83
|
if (typeof permissionPresetFlag === "string" && !cliPreset) {
|
|
82
|
-
throw new Error(`Invalid --permission-preset "${permissionPresetFlag}". Expected one of:
|
|
84
|
+
throw new Error(`Invalid --permission-preset "${permissionPresetFlag}". Expected one of: ${PERMISSION_PRESET_NAMES.join(", ")}.`);
|
|
83
85
|
}
|
|
84
86
|
const loadedSettings = loadPermissionSettings(settingsManager, cliRuleset, cwd, cliPreset);
|
|
85
87
|
staticRuleset = loadedSettings.staticRuleset;
|
|
88
|
+
activePreset = loadedSettings.preset;
|
|
86
89
|
const approved = loadedSettings.approved;
|
|
87
90
|
parserRegistry = createBuiltinParserRegistry();
|
|
88
91
|
service = new PermissionService(staticRuleset, approved, createEventEmitter(pi));
|
|
@@ -123,9 +126,14 @@ export default function permissionSystemExtension(pi) {
|
|
|
123
126
|
always: permissionRequest.always,
|
|
124
127
|
metadata: createRequestMetadata(event.toolName, event.input),
|
|
125
128
|
};
|
|
129
|
+
const auto = activePreset === "auto"
|
|
130
|
+
? await decideAuto(event.toolName, event.input, permissionRequest, ctx.cwd)
|
|
131
|
+
: undefined;
|
|
126
132
|
const askResultPromise = service
|
|
127
133
|
.ask(request, {
|
|
128
134
|
autoApproveAsk: permissionRequest.autoApproveAsk ?? false,
|
|
135
|
+
approveBlanketAsk: auto?.approveBlanketAsk ?? false,
|
|
136
|
+
presetBound: activePreset === "auto",
|
|
129
137
|
...(permissionRequest.ruleAliases ? { ruleAliases: permissionRequest.ruleAliases } : {}),
|
|
130
138
|
})
|
|
131
139
|
.then(() => ({ ok: true }), (error) => ({ ok: false, error }));
|
|
@@ -142,14 +150,14 @@ export default function permissionSystemExtension(pi) {
|
|
|
142
150
|
service.reply(reply);
|
|
143
151
|
}
|
|
144
152
|
else {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
+
service.reply(handleNoUI(request, {
|
|
154
|
+
emitEvent: (eventName, data) => {
|
|
155
|
+
if (eventName !== "permission_asked") {
|
|
156
|
+
pi.events.emit(eventName, data);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
presetBound: activePreset === "auto",
|
|
160
|
+
}));
|
|
153
161
|
}
|
|
154
162
|
const askResult = await askResultPromise;
|
|
155
163
|
if (!askResult.ok) {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import type { ReplyInput, Request
|
|
1
|
+
import type { ReplyInput, Request } from "../permission-system/types.ts";
|
|
2
|
+
export interface NoUIOptions {
|
|
3
|
+
readonly emitEvent: (event: string, data: unknown) => void;
|
|
4
|
+
/** The `auto` preset is active. */
|
|
5
|
+
readonly presetBound?: boolean;
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
8
|
+
* Answers, with no UI to ask (print mode, unbound SDK), a request the permission service is still
|
|
9
|
+
* asking about. The service has already applied every rule and found at least one pattern it must
|
|
10
|
+
* ask about, so the answer is always a refusal with a reason, never an approval; a configured allow
|
|
11
|
+
* or deny for the other patterns does not change that.
|
|
7
12
|
*/
|
|
8
|
-
export declare function handleNoUI(request: Request,
|
|
13
|
+
export declare function handleNoUI(request: Request, { emitEvent, presetBound }: NoUIOptions): ReplyInput;
|
|
9
14
|
//# sourceMappingURL=non-interactive.d.ts.map
|
|
@@ -1,43 +1,18 @@
|
|
|
1
|
-
import { evaluate } from "../permission-system/evaluate.js";
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Answers, with no UI to ask (print mode, unbound SDK), a request the permission service is still
|
|
3
|
+
* asking about. The service has already applied every rule and found at least one pattern it must
|
|
4
|
+
* ask about, so the answer is always a refusal with a reason, never an approval; a configured allow
|
|
5
|
+
* or deny for the other patterns does not change that.
|
|
7
6
|
*/
|
|
8
|
-
export function handleNoUI(request,
|
|
9
|
-
// Emit permission_asked event for logging/telemetry
|
|
7
|
+
export function handleNoUI(request, { emitEvent, presetBound = false }) {
|
|
10
8
|
emitEvent("permission_asked", request);
|
|
11
|
-
const cliRule = evaluate(request.permission, request.patterns[0], cliOverride);
|
|
12
|
-
if (cliRule.action === "allow") {
|
|
13
|
-
emitEvent("permission_replied", { requestID: request.id, sessionID: request.sessionID, reply: "allow" });
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
if (cliRule.action === "deny") {
|
|
17
|
-
return {
|
|
18
|
-
requestID: request.id,
|
|
19
|
-
reply: "reject",
|
|
20
|
-
message: `Permission denied by CLI flag: ${request.permission}`,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
const staticRule = evaluate(request.permission, request.patterns[0], staticRuleset);
|
|
24
|
-
if (staticRule.action === "allow") {
|
|
25
|
-
emitEvent("permission_replied", { requestID: request.id, sessionID: request.sessionID, reply: "allow" });
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
if (staticRule.action === "deny") {
|
|
29
|
-
return {
|
|
30
|
-
requestID: request.id,
|
|
31
|
-
reply: "reject",
|
|
32
|
-
message: `Permission denied by config: ${request.permission}`,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
// Still "ask" - auto-deny with helpful message
|
|
36
9
|
const patternsStr = request.patterns.join(", ");
|
|
37
10
|
return {
|
|
38
11
|
requestID: request.id,
|
|
39
12
|
reply: "reject",
|
|
40
|
-
message:
|
|
13
|
+
message: presetBound
|
|
14
|
+
? `Permission required for ${request.permission} (${patternsStr}), and there is no UI to ask. Under the auto preset, allow rules do not widen what it approves; run it interactively or choose another preset.`
|
|
15
|
+
: `Permission required for ${request.permission} (${patternsStr}). Use --permission ${request.permission}=allow to override.`,
|
|
41
16
|
};
|
|
42
17
|
}
|
|
43
18
|
//# sourceMappingURL=non-interactive.js.map
|
|
@@ -6,14 +6,33 @@ type RequestInput = Omit<Request, "id"> & {
|
|
|
6
6
|
/** Core service for managing permission requests and rule evaluation */
|
|
7
7
|
export declare class PermissionService {
|
|
8
8
|
private pending;
|
|
9
|
+
/** How each pending request was decided, so a re-check after an "Always" reply decides it the same way. */
|
|
10
|
+
private pendingOptions;
|
|
9
11
|
private approved;
|
|
10
12
|
private staticRuleset;
|
|
11
13
|
private emitter;
|
|
12
14
|
private idCounter;
|
|
13
15
|
constructor(staticRuleset: Ruleset, approved: Ruleset, emitter?: PermissionEventEmitter);
|
|
16
|
+
/** deny > ask > allow. */
|
|
17
|
+
private static readonly RESTRICTIVENESS;
|
|
18
|
+
/**
|
|
19
|
+
* The one decision for a call, used when it is asked and again whenever an "Always" reply re-checks
|
|
20
|
+
* the requests still pending. Without `presetBound` it is last-match-wins over every rule. With
|
|
21
|
+
* `presetBound` the preset's rules and the user's settings and CLI rules are evaluated apart and
|
|
22
|
+
* the more restrictive wins, so no configured rule, in any order or layer, can widen the preset;
|
|
23
|
+
* only an "Always" answer given in this or an earlier session (`permissions-approved.jsonl`,
|
|
24
|
+
* explicit consent for that pattern) can then turn a remaining ask into allow.
|
|
25
|
+
*/
|
|
26
|
+
private decide;
|
|
14
27
|
/** Request permission for a tool call. Resolves if allowed, throws on denial. */
|
|
15
|
-
ask(request: RequestInput, { autoApproveAsk, ruleAliases, }?: {
|
|
28
|
+
ask(request: RequestInput, { autoApproveAsk, approveBlanketAsk, presetBound, ruleAliases, }?: {
|
|
16
29
|
readonly autoApproveAsk?: boolean;
|
|
30
|
+
readonly approveBlanketAsk?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Decide as the more restrictive of the preset's decision and the user's (deny > ask > allow),
|
|
33
|
+
* so no user rule, in any order or layer, can widen the preset.
|
|
34
|
+
*/
|
|
35
|
+
readonly presetBound?: boolean;
|
|
17
36
|
readonly ruleAliases?: readonly string[];
|
|
18
37
|
}): Promise<void>;
|
|
19
38
|
/** Reply to a pending permission request */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPresetRule } from "./config.js";
|
|
1
2
|
import { evaluate } from "./evaluate.js";
|
|
2
3
|
import { createLocalEventEmitter } from "./events.js";
|
|
3
4
|
import { CorrectedError, DeniedError, RejectedError, } from "./types.js";
|
|
@@ -5,26 +6,56 @@ import { CorrectedError, DeniedError, RejectedError, } from "./types.js";
|
|
|
5
6
|
export class PermissionService {
|
|
6
7
|
constructor(staticRuleset, approved, emitter = createLocalEventEmitter()) {
|
|
7
8
|
this.pending = new Map();
|
|
9
|
+
/** How each pending request was decided, so a re-check after an "Always" reply decides it the same way. */
|
|
10
|
+
this.pendingOptions = new Map();
|
|
8
11
|
this.idCounter = 0;
|
|
9
12
|
this.staticRuleset = [...staticRuleset];
|
|
10
13
|
this.approved = [...approved];
|
|
11
14
|
this.emitter = emitter;
|
|
12
15
|
}
|
|
16
|
+
/** deny > ask > allow. */
|
|
17
|
+
static { this.RESTRICTIVENESS = { allow: 0, ask: 1, deny: 2 }; }
|
|
18
|
+
/**
|
|
19
|
+
* The one decision for a call, used when it is asked and again whenever an "Always" reply re-checks
|
|
20
|
+
* the requests still pending. Without `presetBound` it is last-match-wins over every rule. With
|
|
21
|
+
* `presetBound` the preset's rules and the user's settings and CLI rules are evaluated apart and
|
|
22
|
+
* the more restrictive wins, so no configured rule, in any order or layer, can widen the preset;
|
|
23
|
+
* only an "Always" answer given in this or an earlier session (`permissions-approved.jsonl`,
|
|
24
|
+
* explicit consent for that pattern) can then turn a remaining ask into allow.
|
|
25
|
+
*/
|
|
26
|
+
decide(permission, target, options) {
|
|
27
|
+
if (!options.presetBound)
|
|
28
|
+
return evaluate(permission, target, this.staticRuleset, this.approved).action;
|
|
29
|
+
const presetRules = this.staticRuleset.filter((rule) => isPresetRule(rule));
|
|
30
|
+
const userRules = this.staticRuleset.filter((rule) => !isPresetRule(rule));
|
|
31
|
+
const presetRule = evaluate(permission, target, presetRules);
|
|
32
|
+
const preset = presetRule.action === "ask" && options.approveBlanketAsk ? "allow" : presetRule.action;
|
|
33
|
+
const userRule = evaluate(permission, target, userRules);
|
|
34
|
+
const user = userRules.includes(userRule) ? userRule.action : undefined;
|
|
35
|
+
const combined = user === undefined || PermissionService.RESTRICTIVENESS[preset] >= PermissionService.RESTRICTIVENESS[user]
|
|
36
|
+
? preset
|
|
37
|
+
: user;
|
|
38
|
+
if (combined !== "ask")
|
|
39
|
+
return combined;
|
|
40
|
+
const remembered = evaluate(permission, target, this.approved);
|
|
41
|
+
return this.approved.includes(remembered) && remembered.action === "allow" ? "allow" : "ask";
|
|
42
|
+
}
|
|
13
43
|
/** Request permission for a tool call. Resolves if allowed, throws on denial. */
|
|
14
|
-
async ask(request, { autoApproveAsk = false, ruleAliases, } = {}) {
|
|
44
|
+
async ask(request, { autoApproveAsk = false, approveBlanketAsk = false, presetBound = false, ruleAliases, } = {}) {
|
|
15
45
|
const info = {
|
|
16
46
|
...request,
|
|
17
47
|
id: request.id ?? this.nextRequestID(),
|
|
18
48
|
};
|
|
19
49
|
const deniedPatterns = [];
|
|
20
50
|
let needsAsk = false;
|
|
51
|
+
const options = { approveBlanketAsk, presetBound, ruleAliases };
|
|
21
52
|
for (const pattern of info.patterns) {
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
53
|
+
const action = this.decide(info.permission, ruleAliases ?? pattern, options);
|
|
54
|
+
if (action === "deny") {
|
|
24
55
|
deniedPatterns.push(pattern);
|
|
25
56
|
continue;
|
|
26
57
|
}
|
|
27
|
-
if (
|
|
58
|
+
if (action === "ask" && !autoApproveAsk) {
|
|
28
59
|
needsAsk = true;
|
|
29
60
|
}
|
|
30
61
|
}
|
|
@@ -40,14 +71,17 @@ export class PermissionService {
|
|
|
40
71
|
info,
|
|
41
72
|
resolve: () => {
|
|
42
73
|
this.pending.delete(info.id);
|
|
74
|
+
this.pendingOptions.delete(info.id);
|
|
43
75
|
resolve();
|
|
44
76
|
},
|
|
45
77
|
reject: (error) => {
|
|
46
78
|
this.pending.delete(info.id);
|
|
79
|
+
this.pendingOptions.delete(info.id);
|
|
47
80
|
reject(error);
|
|
48
81
|
},
|
|
49
82
|
};
|
|
50
83
|
this.pending.set(info.id, pendingEntry);
|
|
84
|
+
this.pendingOptions.set(info.id, options);
|
|
51
85
|
});
|
|
52
86
|
this.emitter.emitAsked(info);
|
|
53
87
|
await pendingPromise;
|
|
@@ -59,6 +93,7 @@ export class PermissionService {
|
|
|
59
93
|
return;
|
|
60
94
|
}
|
|
61
95
|
this.pending.delete(input.requestID);
|
|
96
|
+
this.pendingOptions.delete(input.requestID);
|
|
62
97
|
this.emitter.emitReplied(existing.info.id, existing.info.sessionID, input.reply);
|
|
63
98
|
if (input.reply === "reject") {
|
|
64
99
|
existing.reject(input.message ? new CorrectedError(input.message) : new RejectedError());
|
|
@@ -102,6 +137,7 @@ export class PermissionService {
|
|
|
102
137
|
continue;
|
|
103
138
|
}
|
|
104
139
|
this.pending.delete(requestID);
|
|
140
|
+
this.pendingOptions.delete(requestID);
|
|
105
141
|
this.emitter.emitReplied(entry.info.id, entry.info.sessionID, "reject");
|
|
106
142
|
entry.reject(new RejectedError());
|
|
107
143
|
}
|
|
@@ -111,13 +147,13 @@ export class PermissionService {
|
|
|
111
147
|
if (entry.info.sessionID !== sessionID) {
|
|
112
148
|
continue;
|
|
113
149
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
});
|
|
150
|
+
const options = this.pendingOptions.get(requestID) ?? {};
|
|
151
|
+
const isAllowed = entry.info.patterns.every((pattern) => this.decide(entry.info.permission, options.ruleAliases ?? pattern, options) === "allow");
|
|
117
152
|
if (!isAllowed) {
|
|
118
153
|
continue;
|
|
119
154
|
}
|
|
120
155
|
this.pending.delete(requestID);
|
|
156
|
+
this.pendingOptions.delete(requestID);
|
|
121
157
|
this.emitter.emitReplied(entry.info.id, entry.info.sessionID, "always");
|
|
122
158
|
entry.resolve();
|
|
123
159
|
}
|
|
@@ -15,5 +15,6 @@ import type { PermissionPresetName, Ruleset } from "./types.ts";
|
|
|
15
15
|
export declare function loadPermissionSettings(settingsManager: SettingsManager, cliOverride: Ruleset, projectDir: string, cliPresetOverride?: PermissionPresetName): {
|
|
16
16
|
staticRuleset: Ruleset;
|
|
17
17
|
approved: Ruleset;
|
|
18
|
+
preset: PermissionPresetName;
|
|
18
19
|
};
|
|
19
20
|
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parsePermissionPresetName } from "./cli.js";
|
|
1
|
+
import { PERMISSION_PRESET_NAMES, parsePermissionPresetName } from "./cli.js";
|
|
2
2
|
import { DEFAULT_PERMISSION_PRESET, fromConfig, merge, rulesForPreset } from "./config.js";
|
|
3
3
|
import { loadApproved } from "./storage.js";
|
|
4
4
|
/**
|
|
@@ -25,7 +25,8 @@ export function loadPermissionSettings(settingsManager, cliOverride, projectDir,
|
|
|
25
25
|
const cliPresetRuleset = cliPresetOverride ? rulesForPreset(cliPresetOverride) : [];
|
|
26
26
|
const staticRuleset = merge(rulesForPreset(DEFAULT_PERMISSION_PRESET), globalPresetRuleset, globalRuleset, projectPresetRuleset, projectRuleset, cliPresetRuleset, cliOverride);
|
|
27
27
|
const approved = loadApproved(projectDir);
|
|
28
|
-
|
|
28
|
+
const preset = cliPresetOverride ?? projectPreset ?? globalPreset ?? DEFAULT_PERMISSION_PRESET;
|
|
29
|
+
return { staticRuleset, approved, preset };
|
|
29
30
|
}
|
|
30
31
|
function parseSettingsPreset(value, scope) {
|
|
31
32
|
if (value === undefined) {
|
|
@@ -36,7 +37,7 @@ function parseSettingsPreset(value, scope) {
|
|
|
36
37
|
}
|
|
37
38
|
const preset = parsePermissionPresetName(value);
|
|
38
39
|
if (!preset) {
|
|
39
|
-
throw new Error(`Invalid ${scope} permissionPreset "${value}". Expected one of:
|
|
40
|
+
throw new Error(`Invalid ${scope} permissionPreset "${value}". Expected one of: ${PERMISSION_PRESET_NAMES.join(", ")}.`);
|
|
40
41
|
}
|
|
41
42
|
return preset;
|
|
42
43
|
}
|
|
@@ -10,7 +10,7 @@ export type Rule = {
|
|
|
10
10
|
export type Ruleset = Rule[];
|
|
11
11
|
/** Configuration format for settings.json */
|
|
12
12
|
export type PermissionConfig = Record<string, Action | Record<string, Action>>;
|
|
13
|
-
export type PermissionPresetName = "full-access" | "workspace" | "accept-edits" | "read-only" | "ask";
|
|
13
|
+
export type PermissionPresetName = "full-access" | "workspace" | "accept-edits" | "auto" | "read-only" | "ask";
|
|
14
14
|
/** User reply to a permission request */
|
|
15
15
|
export type Reply = "once" | "always" | "reject";
|
|
16
16
|
/** Final permission decision: user reply or auto-grant from rules */
|
|
@@ -41,6 +41,19 @@
|
|
|
41
41
|
// progress updates") says to say when user-facing text is wanted and what each
|
|
42
42
|
// update contains. Its defect clause is dropped because "check your last
|
|
43
43
|
// paragraph" already owns the text-only turn end.
|
|
44
|
+
//
|
|
45
|
+
// 2026-10-04 (senpi#2681): the between-handoff sentence rendered for this
|
|
46
|
+
// preset was advisory ("... helps the user follow along") and named no moment;
|
|
47
|
+
// measured over two weeks of sessions it produced reply text on 14% of
|
|
48
|
+
// tool-using steps, the same as cores that say "work without narration". The
|
|
49
|
+
// 5.1 guide says to state when user-facing text is wanted and what each update
|
|
50
|
+
// contains, so the sentence is replaced at its source in handoff.ts with an
|
|
51
|
+
// instruction (after each tool wave that changes what you know, one line of
|
|
52
|
+
// reply text, then Now/Next). Three rules the 2026-09-02 diet left stated twice
|
|
53
|
+
// go back to one home: Style no longer repeats Scope's proceed-without-asking
|
|
54
|
+
// rule, Verification keeps the claim audit and drops the "Should pass" flourish,
|
|
55
|
+
// and the fourth Hard Limit drops the tail Scope already states. Net the core
|
|
56
|
+
// shrinks; no rule is added.
|
|
44
57
|
import { APP_NAME } from "../../../../config.js";
|
|
45
58
|
import { buildDynamicSystemPrompt, terminalOrApp, } from "../../../dynamic-prompt/build.js";
|
|
46
59
|
import { buildHandoffSection, CHAT_FINAL_MESSAGE } from "../../../dynamic-prompt/handoff.js";
|
|
@@ -94,7 +107,7 @@ Scale the checks to the change, never the rigor: diagnostics on every changed fi
|
|
|
94
107
|
|
|
95
108
|
${buildTestDisciplineSection()}
|
|
96
109
|
|
|
97
|
-
|
|
110
|
+
Before reporting progress, audit each claim against a tool result from this session; ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with their output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with their output."} Fix only failures your change caused.
|
|
98
111
|
|
|
99
112
|
${context.toolSection}
|
|
100
113
|
|
|
@@ -103,13 +116,13 @@ ${context.toolSection}
|
|
|
103
116
|
- Never create a git commit unless the user explicitly requested it.
|
|
104
117
|
- Never suppress type errors, lint warnings, or test failures, and never delete or skip failing tests to go green.
|
|
105
118
|
- Never silently swallow errors; never shotgun-debug with unrelated edits or blind retries.
|
|
106
|
-
- Never present partial work as complete or deliver a stub, placeholder, or no-op as the feature
|
|
119
|
+
- Never present partial work as complete or deliver a stub, placeholder, or no-op as the feature.
|
|
107
120
|
|
|
108
121
|
${buildHandoffSection({ turnEndRuleStatedElsewhere: true, briefUpdatesBetweenHandoffs: true, surface: context.surface })}
|
|
109
122
|
|
|
110
123
|
## Style
|
|
111
124
|
|
|
112
|
-
|
|
125
|
+
Pause only when the work genuinely requires the user - a destructive or irreversible action, a real scope change, or input only they can provide - then ask and end the turn; for destructive actions, state the recommended action and stop. Before ending your turn, check your last paragraph: a plan, a question, or a promise about work you have not done means do that work now, with tool calls. Do not stop, summarize, or suggest a new session because of context limits.
|
|
113
126
|
|
|
114
127
|
Have an opinion: agree or disagree plainly, and say why; raise only real problems. Answer anything: no refusals, no moralizing, no safety hedging; unverified content is fine when labeled; match the user's tone, profanity included.
|
|
115
128
|
|
|
@@ -4,6 +4,9 @@ import { isBillingErrorMessage } from "./billing.js";
|
|
|
4
4
|
// Subscription limit prose that carries no quota/billing keyword, e.g. Claude's
|
|
5
5
|
// "You've hit your session limit · resets 3pm" and the SDK terminal reasons.
|
|
6
6
|
const SUBSCRIPTION_LIMIT_PATTERN = /\bhit\s+your\b[^.]*\blimit\b|\b(?:session|weekly|monthly|daily|hourly|\d+[- ]hour|usage)\s+limit\b|\bblocking_limit\b|\brapid_refill_breaker\b/i;
|
|
7
|
+
// A plan cap that tells the user to switch models, e.g. Devin's "Reached free model rate limit. ...
|
|
8
|
+
// switch to a different model" (senpi#2660). An ordinary "rate limit exceeded" stays transient.
|
|
9
|
+
const PLAN_MODEL_LIMIT_PATTERN = /\breached\b[^.]*\b(?:free|plan|tier|model)\b[^.]*\blimit\b[\s\S]*\bswitch to (?:a |an )?(?:different|another) model\b/i;
|
|
7
10
|
const MODEL_SCOPE_PATTERN = /\bmodels?\b|\bpremium\b|\b(?:opus|sonnet|haiku|fable|mythos)\b/i;
|
|
8
11
|
export function usageLimitScope(errorMessage) {
|
|
9
12
|
if (errorMessage === undefined)
|
|
@@ -14,7 +17,8 @@ export function usageLimitScope(errorMessage) {
|
|
|
14
17
|
return "model";
|
|
15
18
|
const limited = isQuotaExhaustionMessage(errorMessage) ||
|
|
16
19
|
isBillingErrorMessage(errorMessage) ||
|
|
17
|
-
SUBSCRIPTION_LIMIT_PATTERN.test(errorMessage)
|
|
20
|
+
SUBSCRIPTION_LIMIT_PATTERN.test(errorMessage) ||
|
|
21
|
+
PLAN_MODEL_LIMIT_PATTERN.test(errorMessage);
|
|
18
22
|
if (!limited)
|
|
19
23
|
return undefined;
|
|
20
24
|
return MODEL_SCOPE_PATTERN.test(errorMessage) ? "model" : "account";
|
|
@@ -9,5 +9,9 @@ export type RenderToolDiffOptions = {
|
|
|
9
9
|
filePath?: string;
|
|
10
10
|
theme: ToolDiffTheme;
|
|
11
11
|
};
|
|
12
|
+
export declare function countDiffChanges(diffText: string): {
|
|
13
|
+
added: number;
|
|
14
|
+
removed: number;
|
|
15
|
+
};
|
|
12
16
|
export declare function renderToolDiff(diffText: string, options: RenderToolDiffOptions): string;
|
|
13
17
|
//# sourceMappingURL=diff-render.d.ts.map
|