@creative-dswork/dscode 0.2.0 → 0.2.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.
- package/README.md +6 -10
- package/dist/dscode.mjs +436 -36
- package/dist/web/assets/{index-mH0fup1c.js → index-B2vbN7GC.js} +14 -14
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
package/dist/dscode.mjs
CHANGED
|
@@ -601,6 +601,123 @@ var init_theme = __esm({
|
|
|
601
601
|
}
|
|
602
602
|
});
|
|
603
603
|
|
|
604
|
+
// src/permissions/fuzzy.ts
|
|
605
|
+
function deriveFuzzyPattern(toolName) {
|
|
606
|
+
if (!toolName) return null;
|
|
607
|
+
const match = toolName.match(/^(mcp__[^_]+(?:_[^_]+)*?)__/);
|
|
608
|
+
if (match) {
|
|
609
|
+
return match[1] + "__*";
|
|
610
|
+
}
|
|
611
|
+
return toolName;
|
|
612
|
+
}
|
|
613
|
+
function deriveFuzzyArgPattern(toolName, args) {
|
|
614
|
+
if (args == null) return null;
|
|
615
|
+
if (toolName === "bash") {
|
|
616
|
+
const cmd = typeof args === "string" ? args : JSON.stringify(args);
|
|
617
|
+
const firstWord = cmd.match(/^"?(\w+)/);
|
|
618
|
+
if (firstWord) {
|
|
619
|
+
return `^"${firstWord[1]}`;
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
if (toolName === "read_file" || toolName === "write_file" || toolName === "edit" || toolName === "grep" || toolName === "glob" || toolName === "list_files") {
|
|
624
|
+
const a = args;
|
|
625
|
+
const path = typeof a.path === "string" ? a.path : null;
|
|
626
|
+
if (!path) return null;
|
|
627
|
+
const lastSlash = path.lastIndexOf("/");
|
|
628
|
+
const dir = lastSlash >= 0 ? path.slice(0, lastSlash + 1) : "";
|
|
629
|
+
if (!dir) return null;
|
|
630
|
+
const escaped = dir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
631
|
+
return `${escaped}[^"]*`;
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
function describeFuzzyArgPattern(toolName, args) {
|
|
636
|
+
if (toolName === "bash") {
|
|
637
|
+
const cmd = typeof args === "string" ? args : "";
|
|
638
|
+
const firstWord = cmd.match(/^"?(\w+)/);
|
|
639
|
+
return firstWord ? `${firstWord[1]}.*` : null;
|
|
640
|
+
}
|
|
641
|
+
if (toolName === "read_file" || toolName === "write_file" || toolName === "edit" || toolName === "grep" || toolName === "glob" || toolName === "list_files") {
|
|
642
|
+
const a = args;
|
|
643
|
+
const path = typeof a.path === "string" ? a.path : null;
|
|
644
|
+
if (!path) return null;
|
|
645
|
+
const lastSlash = path.lastIndexOf("/");
|
|
646
|
+
const dir = lastSlash >= 0 ? path.slice(0, lastSlash + 1) : "";
|
|
647
|
+
return dir ? `${dir}*` : null;
|
|
648
|
+
}
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
var init_fuzzy = __esm({
|
|
652
|
+
"src/permissions/fuzzy.ts"() {
|
|
653
|
+
"use strict";
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// src/permissions/fuzzy-llm.ts
|
|
658
|
+
import { complete } from "@mariozechner/pi-ai";
|
|
659
|
+
function cacheKey(toolName, args) {
|
|
660
|
+
const argsStr = typeof args === "string" ? args.slice(0, 80) : JSON.stringify(args).slice(0, 80);
|
|
661
|
+
return `${toolName}:${argsStr}`;
|
|
662
|
+
}
|
|
663
|
+
function prefetchLlmSuggestions(model, toolName, args, preview) {
|
|
664
|
+
const key = cacheKey(toolName, args);
|
|
665
|
+
if (suggestionCache.has(key)) return;
|
|
666
|
+
if (!model) return;
|
|
667
|
+
const prompt = buildPrompt(toolName, args, preview);
|
|
668
|
+
complete(model, {
|
|
669
|
+
systemPrompt: "Respond with JSON only. No explanation.",
|
|
670
|
+
messages: [{ role: "user", content: prompt, timestamp: Date.now() }]
|
|
671
|
+
}).then((msg) => {
|
|
672
|
+
const suggestions = parseSuggestions(msg);
|
|
673
|
+
if (suggestions.length > 0) {
|
|
674
|
+
suggestionCache.set(key, suggestions);
|
|
675
|
+
}
|
|
676
|
+
}).catch(() => {
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
function getLlmSuggestions(toolName, args) {
|
|
680
|
+
return suggestionCache.get(cacheKey(toolName, args)) ?? [];
|
|
681
|
+
}
|
|
682
|
+
function buildPrompt(toolName, args, preview) {
|
|
683
|
+
return `The user is granting permission for this tool call. Suggest 1-2 fuzzy patterns that would match similar future calls.
|
|
684
|
+
|
|
685
|
+
Tool: ${toolName}
|
|
686
|
+
Args: ${JSON.stringify(args)}
|
|
687
|
+
Preview: ${preview}
|
|
688
|
+
|
|
689
|
+
Respond with ONLY a JSON array. Each item: {"label":"<human description>","toolPattern":"<glob or null>","argPattern":"<regex or null>"}.
|
|
690
|
+
Example for "bash" with "git push origin main":
|
|
691
|
+
[{"label":"All git push","toolPattern":null,"argPattern":"^\\"git push"},{"label":"All git commands","toolPattern":null,"argPattern":"^\\"git"}]
|
|
692
|
+
|
|
693
|
+
Example for "read_file" with path "/src/components/Button.tsx":
|
|
694
|
+
[{"label":"All .tsx in components","toolPattern":null,"argPattern":"/src/components/[^\\"]*\\\\.tsx"},{"label":"All files in components","toolPattern":null,"argPattern":"/src/components/"}]
|
|
695
|
+
|
|
696
|
+
Rules:
|
|
697
|
+
- toolPattern: null means same tool. Use "mcp__server__*" only for MCP tools.
|
|
698
|
+
- argPattern: regex matching JSON-stringified args. null means no arg restriction.
|
|
699
|
+
- Max 2 suggestions. Prefer specific over broad.`;
|
|
700
|
+
}
|
|
701
|
+
function parseSuggestions(msg) {
|
|
702
|
+
try {
|
|
703
|
+
const text = typeof msg.content === "string" ? msg.content : Array.isArray(msg.content) ? msg.content.filter((b) => b.type === "text").map((b) => b.text).join("") : "";
|
|
704
|
+
const match = text.match(/\[[\s\S]*\]/);
|
|
705
|
+
if (!match) return [];
|
|
706
|
+
const parsed = JSON.parse(match[0]);
|
|
707
|
+
if (!Array.isArray(parsed)) return [];
|
|
708
|
+
return parsed.filter((s) => typeof s.label === "string" && s.label.length > 0 && s.label.length < 60).slice(0, 2);
|
|
709
|
+
} catch {
|
|
710
|
+
return [];
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
var suggestionCache;
|
|
714
|
+
var init_fuzzy_llm = __esm({
|
|
715
|
+
"src/permissions/fuzzy-llm.ts"() {
|
|
716
|
+
"use strict";
|
|
717
|
+
suggestionCache = /* @__PURE__ */ new Map();
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
|
|
604
721
|
// src/drivers/vision/reader.ts
|
|
605
722
|
import { readFileSync as readFileSync14 } from "node:fs";
|
|
606
723
|
import { exec, execSync as execSync3 } from "node:child_process";
|
|
@@ -3461,6 +3578,8 @@ var init_web_backend = __esm({
|
|
|
3461
3578
|
init_models();
|
|
3462
3579
|
init_config();
|
|
3463
3580
|
init_commands();
|
|
3581
|
+
init_fuzzy();
|
|
3582
|
+
init_fuzzy_llm();
|
|
3464
3583
|
init_mcp_browser();
|
|
3465
3584
|
init_at_file_resolver();
|
|
3466
3585
|
init_display();
|
|
@@ -3480,6 +3599,7 @@ var init_web_backend = __esm({
|
|
|
3480
3599
|
// Pending permission state
|
|
3481
3600
|
permissionResolve = null;
|
|
3482
3601
|
currentPermissionTool = "";
|
|
3602
|
+
currentPermissionArgs = null;
|
|
3483
3603
|
// Image state
|
|
3484
3604
|
pendingImages = [];
|
|
3485
3605
|
// Message accumulation for the current assistant turn
|
|
@@ -3602,11 +3722,17 @@ var init_web_backend = __esm({
|
|
|
3602
3722
|
}
|
|
3603
3723
|
// ── UiBackend Permission ──
|
|
3604
3724
|
getPromptPermission() {
|
|
3605
|
-
return (toolName, preview,
|
|
3725
|
+
return (toolName, preview, args) => {
|
|
3606
3726
|
return new Promise((resolve9) => {
|
|
3607
3727
|
this.currentPermissionTool = toolName;
|
|
3728
|
+
this.currentPermissionArgs = args;
|
|
3608
3729
|
this.permissionResolve = resolve9;
|
|
3609
|
-
|
|
3730
|
+
const fuzzy = deriveFuzzyPattern(toolName);
|
|
3731
|
+
const fuzzyArgDesc = describeFuzzyArgPattern(toolName, args);
|
|
3732
|
+
const llmSuggestions = getLlmSuggestions(toolName, args);
|
|
3733
|
+
this.broadcast({ type: "permission_prompt", toolName, preview, fuzzyPattern: fuzzy, fuzzyArgDesc, llmSuggestions: llmSuggestions.length > 0 ? llmSuggestions : void 0 });
|
|
3734
|
+
const model = resolveModel(this.config.provider, this.config.modelId);
|
|
3735
|
+
prefetchLlmSuggestions(model, toolName, args, preview);
|
|
3610
3736
|
});
|
|
3611
3737
|
};
|
|
3612
3738
|
}
|
|
@@ -3760,10 +3886,34 @@ var init_web_backend = __esm({
|
|
|
3760
3886
|
this.permissionResolve = null;
|
|
3761
3887
|
const isAlways = cmd.decision === "always_allow" || cmd.decision === "always_allow_save";
|
|
3762
3888
|
const isSave = cmd.decision === "always_allow_save";
|
|
3889
|
+
const isSessionGrant = cmd.decision === "always_allow" && !isSave || cmd.decision === "allow" && !!cmd.sessionGrantPattern;
|
|
3890
|
+
const sessionPattern = cmd.sessionGrantPattern;
|
|
3763
3891
|
resolve9({
|
|
3764
3892
|
decision: isAlways ? "allow" : cmd.decision,
|
|
3765
3893
|
rememberForSession: isAlways,
|
|
3766
|
-
|
|
3894
|
+
sessionGrantPattern: isSessionGrant ? sessionPattern : void 0,
|
|
3895
|
+
persistRule: isSave ? (() => {
|
|
3896
|
+
const mode = cmd.fuzzyMode ?? 0;
|
|
3897
|
+
if (mode === 2) {
|
|
3898
|
+
const fuzzyArg = deriveFuzzyArgPattern(this.currentPermissionTool, this.currentPermissionArgs);
|
|
3899
|
+
return fuzzyArg ? { tool: this.currentPermissionTool, argPattern: fuzzyArg, decision: "allow" } : { tool: this.currentPermissionTool, decision: "allow" };
|
|
3900
|
+
}
|
|
3901
|
+
if (mode >= 3) {
|
|
3902
|
+
try {
|
|
3903
|
+
const p = JSON.parse(cmd.toolNamePattern || "{}");
|
|
3904
|
+
return {
|
|
3905
|
+
tool: p.toolPattern ?? this.currentPermissionTool,
|
|
3906
|
+
argPattern: p.argPattern ?? void 0,
|
|
3907
|
+
decision: "allow"
|
|
3908
|
+
};
|
|
3909
|
+
} catch {
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
if (mode === 1) {
|
|
3913
|
+
return { tool: cmd.toolNamePattern ?? this.currentPermissionTool, decision: "allow" };
|
|
3914
|
+
}
|
|
3915
|
+
return { tool: this.currentPermissionTool, decision: "allow" };
|
|
3916
|
+
})() : void 0
|
|
3767
3917
|
});
|
|
3768
3918
|
}
|
|
3769
3919
|
break;
|
|
@@ -7383,7 +7533,7 @@ var ToolRegistry = class {
|
|
|
7383
7533
|
if (this.deferredToolNames.size === 0) return "";
|
|
7384
7534
|
const grouped = /* @__PURE__ */ new Map();
|
|
7385
7535
|
for (const name of this.deferredToolNames) {
|
|
7386
|
-
const prefix = name.startsWith("
|
|
7536
|
+
const prefix = name.startsWith("mcp__") ? name.split("__").slice(0, 2).join("__") : "other";
|
|
7387
7537
|
if (!grouped.has(prefix)) grouped.set(prefix, []);
|
|
7388
7538
|
grouped.get(prefix).push(name);
|
|
7389
7539
|
}
|
|
@@ -7834,6 +7984,7 @@ var PermissionManager = class {
|
|
|
7834
7984
|
rules;
|
|
7835
7985
|
denyRegexes;
|
|
7836
7986
|
sessionGrants = /* @__PURE__ */ new Set();
|
|
7987
|
+
sessionGrantPatterns = [];
|
|
7837
7988
|
promptUser;
|
|
7838
7989
|
defaultDecision;
|
|
7839
7990
|
onBeforePrompt;
|
|
@@ -7873,6 +8024,11 @@ var PermissionManager = class {
|
|
|
7873
8024
|
if (this.sessionGrants.has(toolName)) {
|
|
7874
8025
|
return void 0;
|
|
7875
8026
|
}
|
|
8027
|
+
for (const grant of this.sessionGrantPatterns) {
|
|
8028
|
+
if (this.matchesTool(grant.pattern, toolName)) {
|
|
8029
|
+
return void 0;
|
|
8030
|
+
}
|
|
8031
|
+
}
|
|
7876
8032
|
const decision = this.evaluate(toolName, argsStr);
|
|
7877
8033
|
switch (decision.decision) {
|
|
7878
8034
|
case "allow":
|
|
@@ -7895,7 +8051,14 @@ var PermissionManager = class {
|
|
|
7895
8051
|
this.rules.sort((a, b) => b.priority - a.priority);
|
|
7896
8052
|
}
|
|
7897
8053
|
if (result.rememberForSession) {
|
|
7898
|
-
|
|
8054
|
+
if (result.sessionGrantPattern) {
|
|
8055
|
+
this.sessionGrantPatterns.push({
|
|
8056
|
+
pattern: result.sessionGrantPattern,
|
|
8057
|
+
regex: result.sessionGrantPattern.includes("*") ? this.compileToolPattern(result.sessionGrantPattern) : null
|
|
8058
|
+
});
|
|
8059
|
+
} else {
|
|
8060
|
+
this.sessionGrants.add(toolName);
|
|
8061
|
+
}
|
|
7899
8062
|
}
|
|
7900
8063
|
if (result.decision === "deny") {
|
|
7901
8064
|
return { block: true, reason: result.denyReason ?? "Denied by user" };
|
|
@@ -7909,17 +8072,23 @@ var PermissionManager = class {
|
|
|
7909
8072
|
}
|
|
7910
8073
|
revokeGrant(toolName) {
|
|
7911
8074
|
this.sessionGrants.delete(toolName);
|
|
8075
|
+
this.sessionGrantPatterns = this.sessionGrantPatterns.filter(
|
|
8076
|
+
(g) => g.pattern !== toolName
|
|
8077
|
+
);
|
|
7912
8078
|
}
|
|
7913
8079
|
getSessionGrants() {
|
|
7914
|
-
|
|
8080
|
+
const exact = Array.from(this.sessionGrants);
|
|
8081
|
+
const patterns = this.sessionGrantPatterns.map((g) => g.pattern);
|
|
8082
|
+
return [...exact, ...patterns];
|
|
7915
8083
|
}
|
|
7916
|
-
persistRule(rule) {
|
|
8084
|
+
persistRule(rule, toolNamePattern) {
|
|
8085
|
+
const toolName = toolNamePattern ?? rule.tool;
|
|
7917
8086
|
const settings = loadScopedSettings(projectSettingsPath(this.projectPath));
|
|
7918
8087
|
const permissions = settings.permissions ?? {};
|
|
7919
8088
|
if (rule.decision === "allow") {
|
|
7920
8089
|
const allow = Array.isArray(permissions.allow) ? [...permissions.allow] : [];
|
|
7921
|
-
if (!allow.includes(
|
|
7922
|
-
allow.push(
|
|
8090
|
+
if (!allow.includes(toolName)) {
|
|
8091
|
+
allow.push(toolName);
|
|
7923
8092
|
}
|
|
7924
8093
|
saveProjectSettings(this.projectPath, {
|
|
7925
8094
|
...settings,
|
|
@@ -7930,8 +8099,8 @@ var PermissionManager = class {
|
|
|
7930
8099
|
});
|
|
7931
8100
|
} else if (rule.decision === "deny") {
|
|
7932
8101
|
const deny = Array.isArray(permissions.deny) ? [...permissions.deny] : [];
|
|
7933
|
-
if (!deny.includes(
|
|
7934
|
-
deny.push(
|
|
8102
|
+
if (!deny.includes(toolName)) {
|
|
8103
|
+
deny.push(toolName);
|
|
7935
8104
|
}
|
|
7936
8105
|
saveProjectSettings(this.projectPath, {
|
|
7937
8106
|
...settings,
|
|
@@ -8939,7 +9108,7 @@ var MCPManager = class {
|
|
|
8939
9108
|
for (const def of client.getAllToolDefs()) {
|
|
8940
9109
|
const visibility = def._meta?.ui?.visibility;
|
|
8941
9110
|
if (visibility && visibility.length === 1 && visibility[0] === "app") {
|
|
8942
|
-
names.push(`
|
|
9111
|
+
names.push(`mcp__${serverName}__${def.name}`);
|
|
8943
9112
|
}
|
|
8944
9113
|
}
|
|
8945
9114
|
}
|
|
@@ -9023,7 +9192,7 @@ var MCPManager = class {
|
|
|
9023
9192
|
this.clients.delete(name);
|
|
9024
9193
|
}
|
|
9025
9194
|
if (this.driverRegistry) {
|
|
9026
|
-
this.driverRegistry.unregister(`
|
|
9195
|
+
this.driverRegistry.unregister(`mcp__${name}`);
|
|
9027
9196
|
}
|
|
9028
9197
|
state.status = "connecting";
|
|
9029
9198
|
try {
|
|
@@ -9109,7 +9278,7 @@ var MCPManager = class {
|
|
|
9109
9278
|
}
|
|
9110
9279
|
this.clients.delete(name);
|
|
9111
9280
|
if (this.driverRegistry) {
|
|
9112
|
-
this.driverRegistry.unregister(`
|
|
9281
|
+
this.driverRegistry.unregister(`mcp__${name}`);
|
|
9113
9282
|
}
|
|
9114
9283
|
}
|
|
9115
9284
|
const cfg = this.configs.find((c2) => c2.name === name);
|
|
@@ -9150,7 +9319,7 @@ var MCPManager = class {
|
|
|
9150
9319
|
}
|
|
9151
9320
|
this.clients.delete(name);
|
|
9152
9321
|
if (this.driverRegistry) {
|
|
9153
|
-
this.driverRegistry.unregister(`
|
|
9322
|
+
this.driverRegistry.unregister(`mcp__${name}`);
|
|
9154
9323
|
}
|
|
9155
9324
|
state.status = "disconnected";
|
|
9156
9325
|
state.error = void 0;
|
|
@@ -9159,7 +9328,7 @@ var MCPManager = class {
|
|
|
9159
9328
|
state.refreshError = void 0;
|
|
9160
9329
|
}
|
|
9161
9330
|
buildAgentTool(serverName, def, client) {
|
|
9162
|
-
const toolName = `
|
|
9331
|
+
const toolName = `mcp__${serverName}__${def.name}`;
|
|
9163
9332
|
if (def.alwaysLoad) {
|
|
9164
9333
|
this.alwaysLoadToolNames.add(toolName);
|
|
9165
9334
|
}
|
|
@@ -9239,7 +9408,7 @@ var MCPManager = class {
|
|
|
9239
9408
|
const agentTools = tools.map((t) => this.buildAgentTool(serverName, t, client));
|
|
9240
9409
|
const description = this.configs.find((c2) => c2.name === serverName)?.description ?? "";
|
|
9241
9410
|
const driver = {
|
|
9242
|
-
name: `
|
|
9411
|
+
name: `mcp__${serverName}`,
|
|
9243
9412
|
description: `MCP: ${description || serverName}`,
|
|
9244
9413
|
tools: agentTools,
|
|
9245
9414
|
source: "mcp"
|
|
@@ -9667,6 +9836,8 @@ function escapeAttr(s) {
|
|
|
9667
9836
|
// src/ui/tui-app.ts
|
|
9668
9837
|
init_models();
|
|
9669
9838
|
init_theme();
|
|
9839
|
+
init_fuzzy();
|
|
9840
|
+
init_fuzzy_llm();
|
|
9670
9841
|
import {
|
|
9671
9842
|
ProcessTerminal,
|
|
9672
9843
|
TUI,
|
|
@@ -9742,8 +9913,13 @@ var ConversationView = class {
|
|
|
9742
9913
|
renderedToolCount = 0;
|
|
9743
9914
|
tui;
|
|
9744
9915
|
draftBlocks = /* @__PURE__ */ new Map();
|
|
9745
|
-
|
|
9916
|
+
_activePermission = null;
|
|
9746
9917
|
permSelected = 0;
|
|
9918
|
+
permSubMode = false;
|
|
9919
|
+
_permSubModeType = "save";
|
|
9920
|
+
permSubSelected = 0;
|
|
9921
|
+
lastSubNavAt = 0;
|
|
9922
|
+
lastCancelSubAt = 0;
|
|
9747
9923
|
imageTheme = {
|
|
9748
9924
|
fallbackColor: c.dim
|
|
9749
9925
|
};
|
|
@@ -9761,8 +9937,12 @@ var ConversationView = class {
|
|
|
9761
9937
|
this.currentAssistantText = "";
|
|
9762
9938
|
this.toolEntries = [];
|
|
9763
9939
|
this.renderedToolCount = 0;
|
|
9764
|
-
this.
|
|
9940
|
+
this._activePermission = null;
|
|
9765
9941
|
this.permSelected = 0;
|
|
9942
|
+
this.permSubMode = false;
|
|
9943
|
+
this._permSubModeType = "save";
|
|
9944
|
+
this.permSubSelected = 0;
|
|
9945
|
+
this.lastSubNavAt = 0;
|
|
9766
9946
|
this.draftBlocks.clear();
|
|
9767
9947
|
while (this.box.children.length > 0) {
|
|
9768
9948
|
this.box.removeChild(this.box.children[0]);
|
|
@@ -9967,9 +10147,12 @@ var ConversationView = class {
|
|
|
9967
10147
|
}
|
|
9968
10148
|
this.render();
|
|
9969
10149
|
}
|
|
9970
|
-
showPermissionPrompt(toolName, preview) {
|
|
9971
|
-
this.
|
|
10150
|
+
showPermissionPrompt(toolName, preview, fuzzyPattern, fuzzyArgDesc) {
|
|
10151
|
+
this._activePermission = { toolName, preview, fuzzyPattern: fuzzyPattern ?? null, fuzzyArgDesc: fuzzyArgDesc ?? null };
|
|
9972
10152
|
this.permSelected = 0;
|
|
10153
|
+
this.permSubMode = false;
|
|
10154
|
+
this._permSubModeType = "save";
|
|
10155
|
+
this.permSubSelected = 0;
|
|
9973
10156
|
this.render();
|
|
9974
10157
|
}
|
|
9975
10158
|
permNavigate(direction) {
|
|
@@ -9977,11 +10160,52 @@ var ConversationView = class {
|
|
|
9977
10160
|
this.render();
|
|
9978
10161
|
}
|
|
9979
10162
|
permSelect() {
|
|
9980
|
-
return this.
|
|
10163
|
+
return this._activePermission ? PERM_OPTIONS[this.permSelected] : null;
|
|
10164
|
+
}
|
|
10165
|
+
isInSubMode() {
|
|
10166
|
+
return this.permSubMode;
|
|
10167
|
+
}
|
|
10168
|
+
get activePermission() {
|
|
10169
|
+
return this._activePermission;
|
|
10170
|
+
}
|
|
10171
|
+
get permSubModeType() {
|
|
10172
|
+
return this._permSubModeType;
|
|
10173
|
+
}
|
|
10174
|
+
enterSubMode(type = "save", _fuzzy) {
|
|
10175
|
+
this.permSubMode = true;
|
|
10176
|
+
this._permSubModeType = type;
|
|
10177
|
+
this.permSubSelected = 0;
|
|
10178
|
+
this.lastSubNavAt = 0;
|
|
10179
|
+
this.render();
|
|
10180
|
+
}
|
|
10181
|
+
cancelSubMode() {
|
|
10182
|
+
this.permSubMode = false;
|
|
10183
|
+
this.permSubSelected = 0;
|
|
10184
|
+
this.lastCancelSubAt = Date.now();
|
|
10185
|
+
this.render();
|
|
10186
|
+
}
|
|
10187
|
+
/** True if sub-mode was cancelled within the last 200ms — used to debounce double-firing Escape. */
|
|
10188
|
+
get justCancelledSubMode() {
|
|
10189
|
+
return Date.now() - this.lastCancelSubAt < 200;
|
|
10190
|
+
}
|
|
10191
|
+
permSubNavigate(direction) {
|
|
10192
|
+
const now = Date.now();
|
|
10193
|
+
if (now - this.lastSubNavAt < 120) return;
|
|
10194
|
+
this.lastSubNavAt = now;
|
|
10195
|
+
const fa = this._activePermission?.fuzzyArgDesc;
|
|
10196
|
+
const llm = this._activePermission?.llmSuggestions;
|
|
10197
|
+
const count = (fa ? 3 : 2) + (llm ? llm.length : 0);
|
|
10198
|
+
this.permSubSelected = (this.permSubSelected + direction + count) % count;
|
|
10199
|
+
this.render();
|
|
10200
|
+
}
|
|
10201
|
+
permSubSelect() {
|
|
10202
|
+
return this.permSubSelected;
|
|
9981
10203
|
}
|
|
9982
10204
|
clearPermissionPrompt() {
|
|
9983
|
-
this.
|
|
10205
|
+
this._activePermission = null;
|
|
9984
10206
|
this.permSelected = 0;
|
|
10207
|
+
this.permSubMode = false;
|
|
10208
|
+
this.permSubSelected = 0;
|
|
9985
10209
|
}
|
|
9986
10210
|
pushText(content) {
|
|
9987
10211
|
this.blocks.push({ type: "text", content });
|
|
@@ -9989,12 +10213,15 @@ var ConversationView = class {
|
|
|
9989
10213
|
renderPermPrompt() {
|
|
9990
10214
|
const lines = [];
|
|
9991
10215
|
const maxLineLen = 60;
|
|
9992
|
-
if (!this.
|
|
10216
|
+
if (!this._activePermission) return lines;
|
|
10217
|
+
if (this.permSubMode) {
|
|
10218
|
+
return this.renderPermSubOptions(lines);
|
|
10219
|
+
}
|
|
9993
10220
|
lines.push("");
|
|
9994
10221
|
lines.push(c.yellow.bold(" Permissions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
9995
|
-
lines.push(c.yellow(` Tool: ${this.
|
|
9996
|
-
if (this.
|
|
9997
|
-
for (const pl of this.
|
|
10222
|
+
lines.push(c.yellow(` Tool: ${this._activePermission.toolName}`));
|
|
10223
|
+
if (this._activePermission.preview) {
|
|
10224
|
+
for (const pl of this._activePermission.preview.split("\n").slice(0, 6)) {
|
|
9998
10225
|
lines.push(c.dim(` ${pl.slice(0, maxLineLen)}`));
|
|
9999
10226
|
}
|
|
10000
10227
|
}
|
|
@@ -10008,7 +10235,64 @@ var ConversationView = class {
|
|
|
10008
10235
|
lines.push(`${prefix} ${label} ${hint}`);
|
|
10009
10236
|
}
|
|
10010
10237
|
lines.push(c.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10011
|
-
lines.push(c.dim(" \u2191\u2193 to navigate Enter to confirm A/I shortcuts Esc to deny"));
|
|
10238
|
+
lines.push(c.dim(" \u2191\u2193 to navigate Enter to confirm A/I/S shortcuts Esc to deny"));
|
|
10239
|
+
return lines;
|
|
10240
|
+
}
|
|
10241
|
+
renderPermSubOptions(lines) {
|
|
10242
|
+
const fp = this._activePermission?.fuzzyPattern;
|
|
10243
|
+
const tn = this._activePermission?.toolName ?? "";
|
|
10244
|
+
const fa = this._activePermission?.fuzzyArgDesc;
|
|
10245
|
+
const isMcp = tn.startsWith("mcp__");
|
|
10246
|
+
const isSession = this._permSubModeType === "session" || this._permSubModeType === "allow";
|
|
10247
|
+
if (isSession) {
|
|
10248
|
+
const fuzzyLabel2 = isMcp ? `fuzzy: ${fp}` : `fuzzy: ${tn} (all calls)`;
|
|
10249
|
+
const subOptions2 = [
|
|
10250
|
+
{ label: `exact: ${tn}`, key: "1" },
|
|
10251
|
+
{ label: fuzzyLabel2, key: "2" }
|
|
10252
|
+
];
|
|
10253
|
+
lines.push("");
|
|
10254
|
+
lines.push(c.yellow.bold(" Always Allow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10255
|
+
lines.push(c.dim(" Choose exact or fuzzy pattern:"));
|
|
10256
|
+
lines.push("");
|
|
10257
|
+
for (let i = 0; i < subOptions2.length; i++) {
|
|
10258
|
+
const opt = subOptions2[i];
|
|
10259
|
+
const selected = i === this.permSubSelected;
|
|
10260
|
+
const prefix = selected ? c.cyan(" \u25B6") : " ";
|
|
10261
|
+
const label = selected ? c.bold(c.magenta(opt.label)) : c.dim(opt.label);
|
|
10262
|
+
lines.push(`${prefix} ${label}`);
|
|
10263
|
+
}
|
|
10264
|
+
lines.push(c.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10265
|
+
lines.push(c.dim(" \u2191\u2193 to choose Enter to confirm Esc to cancel"));
|
|
10266
|
+
return lines;
|
|
10267
|
+
}
|
|
10268
|
+
const exactLabel = isMcp ? `exact: ${tn}` : `exact: ${tn} (this call)`;
|
|
10269
|
+
const fuzzyLabel = isMcp ? `fuzzy: ${fp ?? ""}` : `fuzzy: ${tn} (all calls)`;
|
|
10270
|
+
const subOptions = [
|
|
10271
|
+
{ label: exactLabel, key: "1" },
|
|
10272
|
+
{ label: fuzzyLabel, key: "2" }
|
|
10273
|
+
];
|
|
10274
|
+
if (fa) {
|
|
10275
|
+
subOptions.push({ label: `fuzzy args: ${tn} ${fa}`, key: "3" });
|
|
10276
|
+
}
|
|
10277
|
+
const llm = this._activePermission?.llmSuggestions;
|
|
10278
|
+
if (llm && llm.length > 0) {
|
|
10279
|
+
for (let i = 0; i < llm.length; i++) {
|
|
10280
|
+
subOptions.push({ label: `[AI] ${llm[i].label}`, key: `${3 + i}` });
|
|
10281
|
+
}
|
|
10282
|
+
}
|
|
10283
|
+
lines.push("");
|
|
10284
|
+
lines.push(c.yellow.bold(" Save Rule \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10285
|
+
lines.push(c.dim(" Choose exact or fuzzy pattern:"));
|
|
10286
|
+
lines.push("");
|
|
10287
|
+
for (let i = 0; i < subOptions.length; i++) {
|
|
10288
|
+
const opt = subOptions[i];
|
|
10289
|
+
const selected = i === this.permSubSelected;
|
|
10290
|
+
const prefix = selected ? c.cyan(" \u25B6") : " ";
|
|
10291
|
+
const label = selected ? c.bold(c.magenta(opt.label)) : c.dim(opt.label);
|
|
10292
|
+
lines.push(`${prefix} ${label}`);
|
|
10293
|
+
}
|
|
10294
|
+
lines.push(c.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10295
|
+
lines.push(c.dim(" \u2191\u2193 to choose Enter to confirm Esc to cancel"));
|
|
10012
10296
|
return lines;
|
|
10013
10297
|
}
|
|
10014
10298
|
render() {
|
|
@@ -10067,7 +10351,7 @@ var ConversationView = class {
|
|
|
10067
10351
|
this.box.addChild(liveText);
|
|
10068
10352
|
this.liveComponents.push(liveText);
|
|
10069
10353
|
}
|
|
10070
|
-
if (this.
|
|
10354
|
+
if (this._activePermission) {
|
|
10071
10355
|
const permText = new Text(this.renderPermPrompt().join("\n"));
|
|
10072
10356
|
this.box.addChild(permText);
|
|
10073
10357
|
this.liveComponents.push(permText);
|
|
@@ -10384,7 +10668,16 @@ var TuiApp = class {
|
|
|
10384
10668
|
async showPermissionPrompt(toolName, preview, args) {
|
|
10385
10669
|
this.pendingPermissionContext = { toolName, args };
|
|
10386
10670
|
this.permissionExplainMode = false;
|
|
10387
|
-
|
|
10671
|
+
const fuzzy = deriveFuzzyPattern(toolName);
|
|
10672
|
+
const fuzzyArgDesc = describeFuzzyArgPattern(toolName, args);
|
|
10673
|
+
const llmSuggestions = getLlmSuggestions(toolName, args);
|
|
10674
|
+
this.conversation.showPermissionPrompt(toolName, preview, fuzzy, fuzzyArgDesc);
|
|
10675
|
+
if (llmSuggestions.length > 0) {
|
|
10676
|
+
const ap = this.conversation.activePermission;
|
|
10677
|
+
if (ap) ap.llmSuggestions = llmSuggestions;
|
|
10678
|
+
}
|
|
10679
|
+
const model = resolveModel(this.deps.config.provider, this.deps.config.modelId);
|
|
10680
|
+
prefetchLlmSuggestions(model, toolName, args, preview);
|
|
10388
10681
|
return new Promise((resolve9) => {
|
|
10389
10682
|
this.resolvePermission = resolve9;
|
|
10390
10683
|
});
|
|
@@ -10428,13 +10721,21 @@ var TuiApp = class {
|
|
|
10428
10721
|
return;
|
|
10429
10722
|
}
|
|
10430
10723
|
if (option === "always_allow") {
|
|
10431
|
-
this.
|
|
10724
|
+
const fuzzy2 = deriveFuzzyPattern(this.pendingPermissionContext?.toolName ?? "");
|
|
10725
|
+
if (fuzzy2 && fuzzy2 !== this.pendingPermissionContext?.toolName) {
|
|
10726
|
+
this.conversation.enterSubMode("session", fuzzy2);
|
|
10727
|
+
} else {
|
|
10728
|
+
this.resolvePermissionChoice({ decision: "allow", rememberForSession: true });
|
|
10729
|
+
}
|
|
10432
10730
|
return;
|
|
10433
10731
|
}
|
|
10434
10732
|
if (option === "always_allow_save") {
|
|
10435
|
-
const
|
|
10436
|
-
|
|
10437
|
-
|
|
10733
|
+
const fuzzy2 = deriveFuzzyPattern(this.pendingPermissionContext?.toolName ?? "");
|
|
10734
|
+
if (fuzzy2) {
|
|
10735
|
+
this.conversation.enterSubMode("save", fuzzy2);
|
|
10736
|
+
} else {
|
|
10737
|
+
this.saveExactRule();
|
|
10738
|
+
}
|
|
10438
10739
|
return;
|
|
10439
10740
|
}
|
|
10440
10741
|
if (option === "explain") {
|
|
@@ -10445,7 +10746,65 @@ var TuiApp = class {
|
|
|
10445
10746
|
this.tui.requestRender(true);
|
|
10446
10747
|
return;
|
|
10447
10748
|
}
|
|
10448
|
-
this.
|
|
10749
|
+
const fuzzy = deriveFuzzyPattern(this.pendingPermissionContext?.toolName ?? "");
|
|
10750
|
+
if (fuzzy && fuzzy !== this.pendingPermissionContext?.toolName) {
|
|
10751
|
+
this.conversation.enterSubMode("allow", fuzzy);
|
|
10752
|
+
} else {
|
|
10753
|
+
this.resolvePermissionChoice({ decision: "allow" });
|
|
10754
|
+
}
|
|
10755
|
+
}
|
|
10756
|
+
saveExactRule() {
|
|
10757
|
+
const ctx = this.pendingPermissionContext;
|
|
10758
|
+
const persistRule = ctx ? this.buildPersistedRule(ctx.toolName, ctx.args, "saved from permission prompt") : void 0;
|
|
10759
|
+
this.resolvePermissionChoice({ decision: "allow", rememberForSession: true, persistRule });
|
|
10760
|
+
}
|
|
10761
|
+
applyFuzzySaveOption(subIdx) {
|
|
10762
|
+
const ctx = this.pendingPermissionContext;
|
|
10763
|
+
if (!ctx) {
|
|
10764
|
+
this.resolvePermissionChoice({ decision: "deny" });
|
|
10765
|
+
return;
|
|
10766
|
+
}
|
|
10767
|
+
if (subIdx === 0) {
|
|
10768
|
+
this.saveExactRule();
|
|
10769
|
+
} else if (subIdx === 1) {
|
|
10770
|
+
const fuzzy = deriveFuzzyPattern(ctx.toolName);
|
|
10771
|
+
this.resolvePermissionChoice({ decision: "allow", rememberForSession: true, persistRule: fuzzy ? { tool: fuzzy, decision: "allow" } : void 0 });
|
|
10772
|
+
} else if (subIdx === 2) {
|
|
10773
|
+
const fuzzyArg = deriveFuzzyArgPattern(ctx.toolName, ctx.args);
|
|
10774
|
+
this.resolvePermissionChoice({ decision: "allow", rememberForSession: true, persistRule: fuzzyArg ? { tool: ctx.toolName, argPattern: fuzzyArg, decision: "allow" } : void 0 });
|
|
10775
|
+
} else {
|
|
10776
|
+
const llm = this.conversation.activePermission?.llmSuggestions;
|
|
10777
|
+
const s = llm ? llm[subIdx - 3] : null;
|
|
10778
|
+
if (s) {
|
|
10779
|
+
this.resolvePermissionChoice({
|
|
10780
|
+
decision: "allow",
|
|
10781
|
+
rememberForSession: true,
|
|
10782
|
+
persistRule: {
|
|
10783
|
+
tool: s.toolPattern ?? ctx.toolName,
|
|
10784
|
+
argPattern: s.argPattern ?? void 0,
|
|
10785
|
+
decision: "allow"
|
|
10786
|
+
}
|
|
10787
|
+
});
|
|
10788
|
+
} else {
|
|
10789
|
+
this.saveExactRule();
|
|
10790
|
+
}
|
|
10791
|
+
}
|
|
10792
|
+
}
|
|
10793
|
+
applySessionGrantOption(subIdx) {
|
|
10794
|
+
if (subIdx === 0) {
|
|
10795
|
+
this.resolvePermissionChoice({ decision: "allow", rememberForSession: true });
|
|
10796
|
+
} else {
|
|
10797
|
+
const fuzzy = deriveFuzzyPattern(this.pendingPermissionContext?.toolName ?? "");
|
|
10798
|
+
this.resolvePermissionChoice({ decision: "allow", rememberForSession: true, sessionGrantPattern: fuzzy ?? void 0 });
|
|
10799
|
+
}
|
|
10800
|
+
}
|
|
10801
|
+
applyAllowOption(subIdx) {
|
|
10802
|
+
if (subIdx === 0) {
|
|
10803
|
+
this.resolvePermissionChoice({ decision: "allow" });
|
|
10804
|
+
} else {
|
|
10805
|
+
const fuzzy = deriveFuzzyPattern(this.pendingPermissionContext?.toolName ?? "");
|
|
10806
|
+
this.resolvePermissionChoice({ decision: "allow", rememberForSession: true, sessionGrantPattern: fuzzy ?? void 0 });
|
|
10807
|
+
}
|
|
10449
10808
|
}
|
|
10450
10809
|
handleInput(data) {
|
|
10451
10810
|
if (this.permissionExplainMode) {
|
|
@@ -10459,6 +10818,43 @@ var TuiApp = class {
|
|
|
10459
10818
|
return false;
|
|
10460
10819
|
}
|
|
10461
10820
|
if (this.resolvePermission) {
|
|
10821
|
+
if (this.conversation.isInSubMode()) {
|
|
10822
|
+
if (matchesKey(data, Key.up)) {
|
|
10823
|
+
this.conversation.permSubNavigate(-1);
|
|
10824
|
+
return true;
|
|
10825
|
+
}
|
|
10826
|
+
if (matchesKey(data, Key.down)) {
|
|
10827
|
+
this.conversation.permSubNavigate(1);
|
|
10828
|
+
return true;
|
|
10829
|
+
}
|
|
10830
|
+
if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
10831
|
+
const idx = this.conversation.permSubSelect();
|
|
10832
|
+
const t = this.conversation.permSubModeType;
|
|
10833
|
+
if (t === "session") this.applySessionGrantOption(idx);
|
|
10834
|
+
else if (t === "allow") this.applyAllowOption(idx);
|
|
10835
|
+
else this.applyFuzzySaveOption(idx);
|
|
10836
|
+
return true;
|
|
10837
|
+
}
|
|
10838
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, "ctrl+c") || data === "") {
|
|
10839
|
+
this.conversation.cancelSubMode();
|
|
10840
|
+
return true;
|
|
10841
|
+
}
|
|
10842
|
+
if (data === "1") {
|
|
10843
|
+
const t = this.conversation.permSubModeType;
|
|
10844
|
+
if (t === "session") this.applySessionGrantOption(0);
|
|
10845
|
+
else if (t === "allow") this.applyAllowOption(0);
|
|
10846
|
+
else this.applyFuzzySaveOption(0);
|
|
10847
|
+
return true;
|
|
10848
|
+
}
|
|
10849
|
+
if (data === "2") {
|
|
10850
|
+
const t = this.conversation.permSubModeType;
|
|
10851
|
+
if (t === "session") this.applySessionGrantOption(1);
|
|
10852
|
+
else if (t === "allow") this.applyAllowOption(1);
|
|
10853
|
+
else this.applyFuzzySaveOption(1);
|
|
10854
|
+
return true;
|
|
10855
|
+
}
|
|
10856
|
+
return true;
|
|
10857
|
+
}
|
|
10462
10858
|
if (matchesKey(data, Key.up)) {
|
|
10463
10859
|
if (this.canNavigateMenu("up")) {
|
|
10464
10860
|
this.conversation.permNavigate(-1);
|
|
@@ -10479,7 +10875,11 @@ var TuiApp = class {
|
|
|
10479
10875
|
return true;
|
|
10480
10876
|
}
|
|
10481
10877
|
if (matchesKey(data, Key.escape) || matchesKey(data, "ctrl+c") || data === "") {
|
|
10482
|
-
this.
|
|
10878
|
+
if (this.conversation.isInSubMode()) {
|
|
10879
|
+
this.conversation.cancelSubMode();
|
|
10880
|
+
} else if (!this.conversation.justCancelledSubMode) {
|
|
10881
|
+
this.resolvePermissionChoice({ decision: "deny" });
|
|
10882
|
+
}
|
|
10483
10883
|
return true;
|
|
10484
10884
|
}
|
|
10485
10885
|
const shortcut = findPermOptionByKey(data);
|