devez-vibe 1.7.58 → 1.7.60
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/bin/dvz.exe +0 -0
- package/bridge/claude-agent-sdk-bridge.mjs +131 -1
- package/package.json +48 -48
package/bin/dvz.exe
CHANGED
|
Binary file
|
|
@@ -5,7 +5,7 @@ import { execFile, spawn } from "node:child_process";
|
|
|
5
5
|
import { createReadStream, existsSync, readFileSync, readdirSync } from "node:fs";
|
|
6
6
|
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
8
|
+
import { dirname, join, resolve as resolvePath, sep } from "node:path";
|
|
9
9
|
import { createInterface } from "node:readline";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import {
|
|
@@ -722,9 +722,85 @@ function makeOptions(params, sessionId, resume) {
|
|
|
722
722
|
else options.sessionId = sessionId;
|
|
723
723
|
options.canUseTool = (toolName, input, permission) =>
|
|
724
724
|
requestToolPermission(toolName, input, permission);
|
|
725
|
+
// Hooks run whatever the permission mode, so a read-only role turn holds even
|
|
726
|
+
// under bypass. The policy itself arrives with each prompt.
|
|
727
|
+
options.hooks = {
|
|
728
|
+
PreToolUse: [{ hooks: [(input) => toolPolicyHook(sessionId, input)] }],
|
|
729
|
+
};
|
|
725
730
|
return options;
|
|
726
731
|
}
|
|
727
732
|
|
|
733
|
+
// A turn under a read-only role (Planner, Reviewer) carries a tool policy:
|
|
734
|
+
// `{ readOnly: true, writableRoots: [...] }`. The role prompt states the same
|
|
735
|
+
// boundary; this keeps it when the model forgets. File tools are judged by the
|
|
736
|
+
// path they touch, shell tools by the shape of the command — a deny names the
|
|
737
|
+
// rule so the model can continue read-only instead of retrying.
|
|
738
|
+
const FILE_WRITING_TOOLS = ["Edit", "MultiEdit", "Write", "NotebookEdit"];
|
|
739
|
+
const SHELL_TOOLS = ["Bash", "PowerShell"];
|
|
740
|
+
const MUTATING_SHELL_PATTERNS = [
|
|
741
|
+
// Any git invocation naming a mutating subcommand, whatever global options
|
|
742
|
+
// (`-C .`, `-c key=value`) sit between `git` and the verb.
|
|
743
|
+
/\bgit\b[^|;&\n]*\b(?:commit|push|pull|merge|rebase|reset|checkout|switch|restore|stash|clean|add|rm|mv|cherry-pick|revert|tag|am|apply|notes|update-ref|symbolic-ref)\b/,
|
|
744
|
+
/\bgit\b[^|;&\n]*\bbranch\s+(?:-[dDmMcCf]|--delete|--move|--copy|--force)\b/,
|
|
745
|
+
/(?:^|[\s;&|(`])(?:rm|rmdir|mv|cp|mkdir|touch|tee|truncate|chmod|chown|ln|dd|patch|install|shred)\s/,
|
|
746
|
+
/\bsed\s+(?:-[a-zA-Z]*i|--in-place)/,
|
|
747
|
+
/\bperl\s+(?:-\S+\s+)*-\S*i\S*\s/,
|
|
748
|
+
/\b(?:npm|pnpm|yarn|bun)\s+(?:i|install|add|remove|rm|uninstall|publish|ci|link|update|up)\b/,
|
|
749
|
+
/\bcargo\s+(?:add|remove|install|publish|clean|fix|update|fmt)\b/,
|
|
750
|
+
/\bpip3?\s+(?:install|uninstall)\b/,
|
|
751
|
+
// A redirect into a file. `2>&1`, `>/dev/null`, `> nul`, `->` and `>=` pass.
|
|
752
|
+
/(?:^|[^<>=-])>{1,2}(?!\s*(?:&|=|\/dev\/null\b|nul\b))/i,
|
|
753
|
+
/\b(?:Set-Content|Add-Content|Out-File|Remove-Item|Move-Item|Copy-Item|New-Item|Rename-Item|Clear-Content|Set-ItemProperty|Export-Csv|Export-Clixml)\b/i,
|
|
754
|
+
];
|
|
755
|
+
|
|
756
|
+
function pathWithin(cwd, root, target) {
|
|
757
|
+
const normalize = (value) => {
|
|
758
|
+
const resolved = resolvePath(cwd, value).replace(/[\\/]+$/, "");
|
|
759
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
760
|
+
};
|
|
761
|
+
const base = normalize(root);
|
|
762
|
+
const candidate = normalize(target);
|
|
763
|
+
return candidate === base || candidate.startsWith(base + sep) || candidate.startsWith(`${base}/`);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// The reason a tool call is denied under the policy, or null when it may run.
|
|
767
|
+
function toolPolicyDecision(policy, cwd, toolName, input) {
|
|
768
|
+
if (!policy || policy.readOnly !== true) return null;
|
|
769
|
+
const roots = Array.isArray(policy.writableRoots) ? policy.writableRoots.filter(Boolean) : [];
|
|
770
|
+
if (FILE_WRITING_TOOLS.includes(toolName)) {
|
|
771
|
+
const target = input?.file_path ?? input?.notebook_path ?? input?.path;
|
|
772
|
+
if (typeof target === "string" && roots.some((root) => pathWithin(cwd, root, target))) return null;
|
|
773
|
+
return roots.length
|
|
774
|
+
? `이 역할은 읽기 전용입니다. 파일 쓰기는 ${roots.join(", ")} 아래에만 허용되며 요청한 경로는 ${target ?? "(없음)"}입니다. 다른 파일은 고치지 말고 계획에만 적습니다.`
|
|
775
|
+
: `이 역할은 읽기 전용입니다. ${toolName} 도구로 파일을 만들거나 고칠 수 없습니다. 발견한 내용은 보고에 남기고, 수정은 사용자가 역할을 바꾼 뒤 진행합니다.`;
|
|
776
|
+
}
|
|
777
|
+
if (SHELL_TOOLS.includes(toolName)) {
|
|
778
|
+
const command = String(input?.command ?? "");
|
|
779
|
+
if (MUTATING_SHELL_PATTERNS.some((pattern) => pattern.test(command))) {
|
|
780
|
+
return `이 역할은 읽기 전용입니다. 파일이나 저장소 상태를 바꾸는 명령은 실행할 수 없습니다: ${command.slice(0, 160)}. 읽기 명령과 Read·Grep·Glob 도구만 사용하고, 결과를 바꾸는 작업은 보고에 남깁니다.`;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return null;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async function toolPolicyHook(sessionId, input) {
|
|
787
|
+
const session = lookupSession(sessionId);
|
|
788
|
+
const reason = toolPolicyDecision(
|
|
789
|
+
session?.toolPolicy,
|
|
790
|
+
session?.cwd || process.cwd(),
|
|
791
|
+
input?.tool_name,
|
|
792
|
+
input?.tool_input,
|
|
793
|
+
);
|
|
794
|
+
if (!reason) return {};
|
|
795
|
+
return {
|
|
796
|
+
hookSpecificOutput: {
|
|
797
|
+
hookEventName: "PreToolUse",
|
|
798
|
+
permissionDecision: "deny",
|
|
799
|
+
permissionDecisionReason: reason,
|
|
800
|
+
},
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
728
804
|
async function startAgentQuery(prompt, options) {
|
|
729
805
|
const warm = await startup({ options });
|
|
730
806
|
try {
|
|
@@ -1258,6 +1334,7 @@ async function createSession(params, resumeId) {
|
|
|
1258
1334
|
permissionMode: BOOTSTRAP_PERMISSION_MODE,
|
|
1259
1335
|
permissionModeVerified: false,
|
|
1260
1336
|
bypassUnavailable: false,
|
|
1337
|
+
toolPolicy: null,
|
|
1261
1338
|
models: [],
|
|
1262
1339
|
queue,
|
|
1263
1340
|
query: null,
|
|
@@ -2711,6 +2788,11 @@ async function runPrompt(session, params) {
|
|
|
2711
2788
|
await session.query.applyFlagSettings({ effortLevel: effort });
|
|
2712
2789
|
}
|
|
2713
2790
|
session.effort = effort;
|
|
2791
|
+
// The role's read-only policy, or none: it binds this turn and the background
|
|
2792
|
+
// work it starts, until the next prompt replaces it.
|
|
2793
|
+
session.toolPolicy = params.toolPolicy && typeof params.toolPolicy === "object"
|
|
2794
|
+
? params.toolPolicy
|
|
2795
|
+
: null;
|
|
2714
2796
|
const content = await inputContent(params.input, params.handoffContext);
|
|
2715
2797
|
const turnId = beginTurn(session, params.input);
|
|
2716
2798
|
session.queue.push({
|
|
@@ -3453,8 +3535,56 @@ async function runPermissionModeSelfTest() {
|
|
|
3453
3535
|
}
|
|
3454
3536
|
}
|
|
3455
3537
|
|
|
3538
|
+
function runToolPolicySelfTest() {
|
|
3539
|
+
const cwd = process.platform === "win32" ? "C:\\repo" : "/repo";
|
|
3540
|
+
const reviewer = { readOnly: true, writableRoots: [] };
|
|
3541
|
+
const planner = { readOnly: true, writableRoots: ["docs/plans"] };
|
|
3542
|
+
const allowed = [
|
|
3543
|
+
[null, "Edit", { file_path: "src/main.rs" }],
|
|
3544
|
+
[{ readOnly: false }, "Bash", { command: "rm -rf target" }],
|
|
3545
|
+
[reviewer, "Read", { file_path: "src/main.rs" }],
|
|
3546
|
+
[reviewer, "Bash", { command: "git diff --stat HEAD~1" }],
|
|
3547
|
+
[reviewer, "Bash", { command: "git log --oneline -5 && git status --porcelain" }],
|
|
3548
|
+
[reviewer, "Bash", { command: "grep -rn \"->\" src | head -20" }],
|
|
3549
|
+
[reviewer, "Bash", { command: "cargo test reviewer 2>&1 | tail -20" }],
|
|
3550
|
+
[reviewer, "Bash", { command: "cargo test > /dev/null 2>&1; echo $?" }],
|
|
3551
|
+
[reviewer, "Bash", { command: "awk '$3 >= 10' report.txt" }],
|
|
3552
|
+
[reviewer, "PowerShell", { command: "Get-Content src/main.rs | Select-Object -First 5" }],
|
|
3553
|
+
[planner, "Write", { file_path: "docs/plans/2026-09-05-x.md" }],
|
|
3554
|
+
[planner, "Write", { file_path: `${cwd}${sep}docs${sep}plans${sep}x.md` }],
|
|
3555
|
+
];
|
|
3556
|
+
const denied = [
|
|
3557
|
+
[reviewer, "Edit", { file_path: "src/main.rs" }],
|
|
3558
|
+
[reviewer, "Write", { file_path: "docs/plans/x.md" }],
|
|
3559
|
+
[reviewer, "NotebookEdit", { notebook_path: "a.ipynb" }],
|
|
3560
|
+
[planner, "Write", { file_path: "src/main.rs" }],
|
|
3561
|
+
[planner, "Write", { file_path: "docs/plans/../../src/main.rs" }],
|
|
3562
|
+
[planner, "Write", { file_path: "docs/plans-old/x.md" }],
|
|
3563
|
+
[reviewer, "Bash", { command: "git commit -m 'x'" }],
|
|
3564
|
+
[reviewer, "Bash", { command: "git -C . checkout -- src" }],
|
|
3565
|
+
[reviewer, "Bash", { command: "echo hi > notes.txt" }],
|
|
3566
|
+
[reviewer, "Bash", { command: "cat a >> b.log" }],
|
|
3567
|
+
[reviewer, "Bash", { command: "sed -i 's/a/b/' src/main.rs" }],
|
|
3568
|
+
[reviewer, "Bash", { command: "rm -rf target" }],
|
|
3569
|
+
[reviewer, "Bash", { command: "cargo fmt" }],
|
|
3570
|
+
[reviewer, "PowerShell", { command: "Set-Content -Path a.txt -Value x" }],
|
|
3571
|
+
];
|
|
3572
|
+
for (const [policy, tool, input] of allowed) {
|
|
3573
|
+
const reason = toolPolicyDecision(policy, cwd, tool, input);
|
|
3574
|
+
if (reason !== null) {
|
|
3575
|
+
throw new Error(`Claude tool policy self-test denied ${tool} ${JSON.stringify(input)}: ${reason}`);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
for (const [policy, tool, input] of denied) {
|
|
3579
|
+
if (toolPolicyDecision(policy, cwd, tool, input) === null) {
|
|
3580
|
+
throw new Error(`Claude tool policy self-test allowed ${tool} ${JSON.stringify(input)}`);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3456
3585
|
async function runSelfTest() {
|
|
3457
3586
|
await runPermissionModeSelfTest();
|
|
3587
|
+
runToolPolicySelfTest();
|
|
3458
3588
|
const equivalentCwd = process.platform === "win32"
|
|
3459
3589
|
? sameCwd("D:\\Repo", "d:/repo/")
|
|
3460
3590
|
: sameCwd("/tmp/repo", "/tmp/repo/");
|
package/package.json
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "devez-vibe",
|
|
3
|
-
"version": "1.7.
|
|
4
|
-
"description": "Stable terminal UI for Codex and Claude Agent SDK",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"codex",
|
|
7
|
-
"cli",
|
|
8
|
-
"tui",
|
|
9
|
-
"terminal",
|
|
10
|
-
"app-server",
|
|
11
|
-
"claude-agent-sdk"
|
|
12
|
-
],
|
|
13
|
-
"homepage": "https://github.com/MrHoje/Devez-vibe#readme",
|
|
14
|
-
"bugs": "https://github.com/MrHoje/Devez-vibe/issues",
|
|
15
|
-
"repository": {
|
|
16
|
-
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/MrHoje/Devez-vibe.git"
|
|
18
|
-
},
|
|
19
|
-
"license": "MIT",
|
|
20
|
-
"bin": {
|
|
21
|
-
"dvz": "bin/dvz.exe"
|
|
22
|
-
},
|
|
23
|
-
"files": [
|
|
24
|
-
"bin/dvz.exe",
|
|
25
|
-
"bridge/claude-agent-sdk-bridge.mjs",
|
|
26
|
-
"install-skills.mjs",
|
|
27
|
-
"prepare-search-deps.mjs",
|
|
28
|
-
"skills/luna-loop/",
|
|
29
|
-
"skills/insane-search/",
|
|
30
|
-
"README.md",
|
|
31
|
-
"LICENSE"
|
|
32
|
-
],
|
|
33
|
-
"os": [
|
|
34
|
-
"win32"
|
|
35
|
-
],
|
|
36
|
-
"cpu": [
|
|
37
|
-
"x64"
|
|
38
|
-
],
|
|
39
|
-
"engines": {
|
|
40
|
-
"node": ">=18"
|
|
41
|
-
},
|
|
42
|
-
"scripts": {
|
|
43
|
-
"postinstall": "node install-skills.mjs && node prepare-search-deps.mjs"
|
|
44
|
-
},
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.260"
|
|
47
|
-
}
|
|
48
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "devez-vibe",
|
|
3
|
+
"version": "1.7.60",
|
|
4
|
+
"description": "Stable terminal UI for Codex and Claude Agent SDK",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"codex",
|
|
7
|
+
"cli",
|
|
8
|
+
"tui",
|
|
9
|
+
"terminal",
|
|
10
|
+
"app-server",
|
|
11
|
+
"claude-agent-sdk"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/MrHoje/Devez-vibe#readme",
|
|
14
|
+
"bugs": "https://github.com/MrHoje/Devez-vibe/issues",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/MrHoje/Devez-vibe.git"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"bin": {
|
|
21
|
+
"dvz": "bin/dvz.exe"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin/dvz.exe",
|
|
25
|
+
"bridge/claude-agent-sdk-bridge.mjs",
|
|
26
|
+
"install-skills.mjs",
|
|
27
|
+
"prepare-search-deps.mjs",
|
|
28
|
+
"skills/luna-loop/",
|
|
29
|
+
"skills/insane-search/",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"os": [
|
|
34
|
+
"win32"
|
|
35
|
+
],
|
|
36
|
+
"cpu": [
|
|
37
|
+
"x64"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"postinstall": "node install-skills.mjs && node prepare-search-deps.mjs"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.260"
|
|
47
|
+
}
|
|
48
|
+
}
|