devez-vibe 1.3.20 → 1.3.22
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/LICENSE +33 -33
- package/bin/dvz.exe +0 -0
- package/bridge/claude-agent-sdk-bridge.mjs +396 -396
- package/package.json +41 -41
package/LICENSE
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
DevezCode source code license
|
|
2
|
-
|
|
3
|
-
The MIT License below applies only to the DevezCode source code and to files
|
|
4
|
-
that are not listed in the exception below.
|
|
5
|
-
|
|
6
|
-
Exceptions:
|
|
7
|
-
|
|
8
|
-
* `Resources/Images/FileTypes/` and `Resources/Licenses/` are Microsoft Visual
|
|
9
|
-
Studio Image Library materials, subject to the included Microsoft terms.
|
|
10
|
-
* `Resources/Images/ShellPresets/` contains third-party brand assets, subject
|
|
11
|
-
to their respective owners' terms.
|
|
12
|
-
|
|
13
|
-
MIT License
|
|
14
|
-
|
|
15
|
-
Copyright (c) 2026 DevezCode contributors
|
|
16
|
-
|
|
17
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
-
in the Software without restriction, including without limitation the rights
|
|
20
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
-
furnished to do so, subject to the following conditions:
|
|
23
|
-
|
|
24
|
-
The above copyright notice and this permission notice shall be included in all
|
|
25
|
-
copies or substantial portions of the Software.
|
|
26
|
-
|
|
27
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
-
SOFTWARE.
|
|
1
|
+
DevezCode source code license
|
|
2
|
+
|
|
3
|
+
The MIT License below applies only to the DevezCode source code and to files
|
|
4
|
+
that are not listed in the exception below.
|
|
5
|
+
|
|
6
|
+
Exceptions:
|
|
7
|
+
|
|
8
|
+
* `Resources/Images/FileTypes/` and `Resources/Licenses/` are Microsoft Visual
|
|
9
|
+
Studio Image Library materials, subject to the included Microsoft terms.
|
|
10
|
+
* `Resources/Images/ShellPresets/` contains third-party brand assets, subject
|
|
11
|
+
to their respective owners' terms.
|
|
12
|
+
|
|
13
|
+
MIT License
|
|
14
|
+
|
|
15
|
+
Copyright (c) 2026 DevezCode contributors
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
+
in the Software without restriction, including without limitation the rights
|
|
20
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
+
furnished to do so, subject to the following conditions:
|
|
23
|
+
|
|
24
|
+
The above copyright notice and this permission notice shall be included in all
|
|
25
|
+
copies or substantial portions of the Software.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
+
SOFTWARE.
|
package/bin/dvz.exe
CHANGED
|
Binary file
|
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { createReadStream } from "node:fs";
|
|
6
|
-
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
9
|
import { createInterface } from "node:readline";
|
|
10
10
|
import {
|
|
11
11
|
deleteSession,
|
|
12
12
|
forkSession,
|
|
13
|
-
getSessionInfo,
|
|
14
|
-
getSessionMessages,
|
|
15
|
-
filterEscalatingDefaultMode,
|
|
16
|
-
listSessions,
|
|
17
|
-
resolveSettings,
|
|
18
|
-
startup,
|
|
19
|
-
} from "@anthropic-ai/claude-agent-sdk";
|
|
13
|
+
getSessionInfo,
|
|
14
|
+
getSessionMessages,
|
|
15
|
+
filterEscalatingDefaultMode,
|
|
16
|
+
listSessions,
|
|
17
|
+
resolveSettings,
|
|
18
|
+
startup,
|
|
19
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
20
20
|
|
|
21
21
|
const VERSION = process.env.DEVEZ_VIBE_VERSION || "dev";
|
|
22
22
|
const sessions = new Map();
|
|
@@ -171,7 +171,7 @@ function compactClaudeModelName(model) {
|
|
|
171
171
|
return fallback || clean(model.displayName || model.resolvedModel || model.value);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
function catalogEntry(model, defaultResolvedModel) {
|
|
174
|
+
function catalogEntry(model, defaultResolvedModel) {
|
|
175
175
|
const value = String(model.value || "");
|
|
176
176
|
const resolved = String(model.resolvedModel || value);
|
|
177
177
|
const efforts = model.supportsEffort && Array.isArray(model.supportedEffortLevels)
|
|
@@ -183,9 +183,9 @@ function catalogEntry(model, defaultResolvedModel) {
|
|
|
183
183
|
model: visibleModel(value),
|
|
184
184
|
displayName: compactClaudeModelName(model),
|
|
185
185
|
defaultReasoningEffort: efforts.includes("high") ? "high" : efforts.at(-1) || "",
|
|
186
|
-
supportedReasoningEfforts: efforts.map((reasoningEffort) => ({ reasoningEffort })),
|
|
187
|
-
supportsAutoMode: Boolean(model.supportsAutoMode),
|
|
188
|
-
isDefault: Boolean(defaultResolvedModel) && resolved === defaultResolvedModel,
|
|
186
|
+
supportedReasoningEfforts: efforts.map((reasoningEffort) => ({ reasoningEffort })),
|
|
187
|
+
supportsAutoMode: Boolean(model.supportsAutoMode),
|
|
188
|
+
isDefault: Boolean(defaultResolvedModel) && resolved === defaultResolvedModel,
|
|
189
189
|
...(contextWindow > 0 ? { contextWindow } : {}),
|
|
190
190
|
};
|
|
191
191
|
}
|
|
@@ -298,14 +298,14 @@ function adoptSessionId(session, incoming) {
|
|
|
298
298
|
});
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
const PERMISSION_MODES = [
|
|
302
|
-
"default",
|
|
303
|
-
"acceptEdits",
|
|
304
|
-
"plan",
|
|
305
|
-
"auto",
|
|
306
|
-
"dontAsk",
|
|
307
|
-
"bypassPermissions",
|
|
308
|
-
];
|
|
301
|
+
const PERMISSION_MODES = [
|
|
302
|
+
"default",
|
|
303
|
+
"acceptEdits",
|
|
304
|
+
"plan",
|
|
305
|
+
"auto",
|
|
306
|
+
"dontAsk",
|
|
307
|
+
"bypassPermissions",
|
|
308
|
+
];
|
|
309
309
|
|
|
310
310
|
function permissionMode(requested, fallback = "default") {
|
|
311
311
|
const mode = String(requested || "");
|
|
@@ -314,173 +314,173 @@ function permissionMode(requested, fallback = "default") {
|
|
|
314
314
|
|
|
315
315
|
// Moves a live session onto a mode the badge picked. A rejected mode — policy
|
|
316
316
|
// disables bypass, say — leaves the session on the one it already had.
|
|
317
|
-
async function applyPermissionMode(session, requested) {
|
|
318
|
-
const mode = permissionMode(requested, session.permissionMode || "default");
|
|
319
|
-
if (mode === session.permissionMode) return null;
|
|
320
|
-
try {
|
|
321
|
-
await session.query.setPermissionMode(mode);
|
|
322
|
-
session.permissionMode = mode;
|
|
323
|
-
return null;
|
|
324
|
-
} catch (error) {
|
|
325
|
-
return error?.message || String(error);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
async function claudePermissionStatus(params) {
|
|
330
|
-
const resolved = await resolveSettings({
|
|
331
|
-
cwd: params.cwd || process.cwd(),
|
|
332
|
-
settingSources: ["user", "project", "local"],
|
|
333
|
-
});
|
|
334
|
-
const effective = filterEscalatingDefaultMode(resolved);
|
|
335
|
-
const permissions = effective.permissions || {};
|
|
336
|
-
const bypassAccepted = effective.skipDangerousModePermissionPrompt === true
|
|
337
|
-
|| effective.bypassPermissionsModeAccepted === true;
|
|
338
|
-
const bypassAvailable = permissions.disableBypassPermissionsMode !== "disable" && bypassAccepted;
|
|
339
|
-
const autoDisabled = effective.disableAutoMode === "disable";
|
|
340
|
-
let defaultMode = permissionMode(permissions.defaultMode);
|
|
341
|
-
if ((defaultMode === "bypassPermissions" && !bypassAvailable)
|
|
342
|
-
|| (defaultMode === "auto" && autoDisabled)) defaultMode = "default";
|
|
343
|
-
const rules = [];
|
|
344
|
-
const directories = [];
|
|
345
|
-
for (const source of resolved.sources) {
|
|
346
|
-
const sourcePermissions = source.settings?.permissions || {};
|
|
347
|
-
for (const behavior of ["allow", "ask", "deny"]) {
|
|
348
|
-
for (const rule of Array.isArray(sourcePermissions[behavior])
|
|
349
|
-
? sourcePermissions[behavior]
|
|
350
|
-
: []) {
|
|
351
|
-
rules.push({
|
|
352
|
-
behavior,
|
|
353
|
-
rule: String(rule),
|
|
354
|
-
source: source.source,
|
|
355
|
-
path: source.path || null,
|
|
356
|
-
mutable: ["user", "project", "local"].includes(source.source),
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
for (const directory of Array.isArray(sourcePermissions.additionalDirectories)
|
|
361
|
-
? sourcePermissions.additionalDirectories
|
|
362
|
-
: []) {
|
|
363
|
-
directories.push({
|
|
364
|
-
directory: String(directory),
|
|
365
|
-
source: source.source,
|
|
366
|
-
path: source.path || null,
|
|
367
|
-
mutable: ["user", "project", "local"].includes(source.source),
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
const denials = [...sessions.values()]
|
|
372
|
-
.filter((session) => session.cwd === (params.cwd || process.cwd()))
|
|
373
|
-
.flatMap((session) => session.permissionDenials || [])
|
|
374
|
-
.slice(-20)
|
|
375
|
-
.reverse();
|
|
376
|
-
return {
|
|
377
|
-
defaultMode,
|
|
378
|
-
bypassAvailable,
|
|
379
|
-
autoDisabled,
|
|
380
|
-
rulesLocked: effective.allowManagedPermissionRulesOnly === true,
|
|
381
|
-
rules,
|
|
382
|
-
directories,
|
|
383
|
-
denials,
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function permissionSettingsPath(cwd, destination) {
|
|
388
|
-
if (destination === "user") {
|
|
389
|
-
return join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"), "settings.json");
|
|
390
|
-
}
|
|
391
|
-
if (destination === "project") return join(cwd, ".claude", "settings.json");
|
|
392
|
-
if (destination === "local") return join(cwd, ".claude", "settings.local.json");
|
|
393
|
-
throw new Error(`지원하지 않는 Claude 권한 설정 범위: ${destination}`);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
async function updateClaudePermission(params) {
|
|
397
|
-
const cwd = params.cwd || process.cwd();
|
|
398
|
-
const status = await claudePermissionStatus({ cwd });
|
|
399
|
-
if (status.rulesLocked) {
|
|
400
|
-
throw new Error("관리 정책에서 사용자·프로젝트 권한 규칙 변경을 비활성화했습니다.");
|
|
401
|
-
}
|
|
402
|
-
const destination = String(params.destination || "project");
|
|
403
|
-
const path = permissionSettingsPath(cwd, destination);
|
|
404
|
-
let settings = {};
|
|
405
|
-
try {
|
|
406
|
-
settings = JSON.parse(await readFile(path, "utf8"));
|
|
407
|
-
} catch (error) {
|
|
408
|
-
if (error?.code !== "ENOENT") throw error;
|
|
409
|
-
}
|
|
410
|
-
if (!settings || typeof settings !== "object" || Array.isArray(settings)) settings = {};
|
|
411
|
-
if (!settings.permissions || typeof settings.permissions !== "object"
|
|
412
|
-
|| Array.isArray(settings.permissions)) settings.permissions = {};
|
|
413
|
-
|
|
414
|
-
const action = String(params.action || "");
|
|
415
|
-
const behavior = String(params.behavior || "");
|
|
416
|
-
const key = behavior === "directory" ? "additionalDirectories" : behavior;
|
|
417
|
-
if (!["allow", "ask", "deny", "additionalDirectories"].includes(key)) {
|
|
418
|
-
throw new Error(`지원하지 않는 Claude 권한 규칙 종류: ${behavior}`);
|
|
419
|
-
}
|
|
420
|
-
const value = String(params.value || "").trim();
|
|
421
|
-
if (!value) throw new Error("빈 Claude 권한 규칙은 저장할 수 없습니다.");
|
|
422
|
-
const values = Array.isArray(settings.permissions[key])
|
|
423
|
-
? settings.permissions[key].map(String)
|
|
424
|
-
: [];
|
|
425
|
-
if (action === "add" && !values.includes(value)) values.push(value);
|
|
426
|
-
else if (action === "remove") {
|
|
427
|
-
const index = values.indexOf(value);
|
|
428
|
-
if (index >= 0) values.splice(index, 1);
|
|
429
|
-
} else if (action !== "add") {
|
|
430
|
-
throw new Error(`지원하지 않는 Claude 권한 규칙 작업: ${action}`);
|
|
431
|
-
}
|
|
432
|
-
settings.permissions[key] = values;
|
|
433
|
-
await mkdir(dirname(path), { recursive: true });
|
|
434
|
-
await writeFile(path, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
435
|
-
return claudePermissionStatus({ cwd });
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
async function setClaudeAutoModeDisabled(params) {
|
|
439
|
-
const cwd = params.cwd || process.cwd();
|
|
440
|
-
const path = permissionSettingsPath(cwd, "user");
|
|
441
|
-
let settings = {};
|
|
442
|
-
try {
|
|
443
|
-
settings = JSON.parse(await readFile(path, "utf8"));
|
|
444
|
-
} catch (error) {
|
|
445
|
-
if (error?.code !== "ENOENT") throw error;
|
|
446
|
-
}
|
|
447
|
-
if (!settings || typeof settings !== "object" || Array.isArray(settings)) settings = {};
|
|
448
|
-
if (params.disabled === false) delete settings.disableAutoMode;
|
|
449
|
-
else settings.disableAutoMode = "disable";
|
|
450
|
-
await mkdir(dirname(path), { recursive: true });
|
|
451
|
-
await writeFile(path, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
452
|
-
return claudePermissionStatus({ cwd });
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
async function retryClaudePermission(params) {
|
|
456
|
-
const session = lookupSession(params.sessionId);
|
|
457
|
-
if (!session) throw new Error("재시도할 Claude 세션을 찾을 수 없습니다.");
|
|
458
|
-
const tool = String(params.tool || "tool");
|
|
459
|
-
const input = params.input && typeof params.input === "object" ? params.input : {};
|
|
460
|
-
const previousMode = session.permissionMode;
|
|
461
|
-
return startPrompt({
|
|
462
|
-
sessionId: session.id,
|
|
463
|
-
permissionMode: previousMode === "auto" ? "default" : previousMode,
|
|
464
|
-
restorePermissionMode: previousMode === "auto" ? "auto" : null,
|
|
465
|
-
input: [{
|
|
466
|
-
type: "text",
|
|
467
|
-
text: [
|
|
468
|
-
`The user selected the denied ${tool} action in /permissions and asked to retry it.`,
|
|
469
|
-
"Retry that action now. A manual permission prompt will be shown before it runs.",
|
|
470
|
-
`Original tool input: ${JSON.stringify(input)}`,
|
|
471
|
-
].join("\n"),
|
|
472
|
-
}],
|
|
473
|
-
});
|
|
474
|
-
}
|
|
317
|
+
async function applyPermissionMode(session, requested) {
|
|
318
|
+
const mode = permissionMode(requested, session.permissionMode || "default");
|
|
319
|
+
if (mode === session.permissionMode) return null;
|
|
320
|
+
try {
|
|
321
|
+
await session.query.setPermissionMode(mode);
|
|
322
|
+
session.permissionMode = mode;
|
|
323
|
+
return null;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return error?.message || String(error);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function claudePermissionStatus(params) {
|
|
330
|
+
const resolved = await resolveSettings({
|
|
331
|
+
cwd: params.cwd || process.cwd(),
|
|
332
|
+
settingSources: ["user", "project", "local"],
|
|
333
|
+
});
|
|
334
|
+
const effective = filterEscalatingDefaultMode(resolved);
|
|
335
|
+
const permissions = effective.permissions || {};
|
|
336
|
+
const bypassAccepted = effective.skipDangerousModePermissionPrompt === true
|
|
337
|
+
|| effective.bypassPermissionsModeAccepted === true;
|
|
338
|
+
const bypassAvailable = permissions.disableBypassPermissionsMode !== "disable" && bypassAccepted;
|
|
339
|
+
const autoDisabled = effective.disableAutoMode === "disable";
|
|
340
|
+
let defaultMode = permissionMode(permissions.defaultMode);
|
|
341
|
+
if ((defaultMode === "bypassPermissions" && !bypassAvailable)
|
|
342
|
+
|| (defaultMode === "auto" && autoDisabled)) defaultMode = "default";
|
|
343
|
+
const rules = [];
|
|
344
|
+
const directories = [];
|
|
345
|
+
for (const source of resolved.sources) {
|
|
346
|
+
const sourcePermissions = source.settings?.permissions || {};
|
|
347
|
+
for (const behavior of ["allow", "ask", "deny"]) {
|
|
348
|
+
for (const rule of Array.isArray(sourcePermissions[behavior])
|
|
349
|
+
? sourcePermissions[behavior]
|
|
350
|
+
: []) {
|
|
351
|
+
rules.push({
|
|
352
|
+
behavior,
|
|
353
|
+
rule: String(rule),
|
|
354
|
+
source: source.source,
|
|
355
|
+
path: source.path || null,
|
|
356
|
+
mutable: ["user", "project", "local"].includes(source.source),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const directory of Array.isArray(sourcePermissions.additionalDirectories)
|
|
361
|
+
? sourcePermissions.additionalDirectories
|
|
362
|
+
: []) {
|
|
363
|
+
directories.push({
|
|
364
|
+
directory: String(directory),
|
|
365
|
+
source: source.source,
|
|
366
|
+
path: source.path || null,
|
|
367
|
+
mutable: ["user", "project", "local"].includes(source.source),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const denials = [...sessions.values()]
|
|
372
|
+
.filter((session) => session.cwd === (params.cwd || process.cwd()))
|
|
373
|
+
.flatMap((session) => session.permissionDenials || [])
|
|
374
|
+
.slice(-20)
|
|
375
|
+
.reverse();
|
|
376
|
+
return {
|
|
377
|
+
defaultMode,
|
|
378
|
+
bypassAvailable,
|
|
379
|
+
autoDisabled,
|
|
380
|
+
rulesLocked: effective.allowManagedPermissionRulesOnly === true,
|
|
381
|
+
rules,
|
|
382
|
+
directories,
|
|
383
|
+
denials,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function permissionSettingsPath(cwd, destination) {
|
|
388
|
+
if (destination === "user") {
|
|
389
|
+
return join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"), "settings.json");
|
|
390
|
+
}
|
|
391
|
+
if (destination === "project") return join(cwd, ".claude", "settings.json");
|
|
392
|
+
if (destination === "local") return join(cwd, ".claude", "settings.local.json");
|
|
393
|
+
throw new Error(`지원하지 않는 Claude 권한 설정 범위: ${destination}`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function updateClaudePermission(params) {
|
|
397
|
+
const cwd = params.cwd || process.cwd();
|
|
398
|
+
const status = await claudePermissionStatus({ cwd });
|
|
399
|
+
if (status.rulesLocked) {
|
|
400
|
+
throw new Error("관리 정책에서 사용자·프로젝트 권한 규칙 변경을 비활성화했습니다.");
|
|
401
|
+
}
|
|
402
|
+
const destination = String(params.destination || "project");
|
|
403
|
+
const path = permissionSettingsPath(cwd, destination);
|
|
404
|
+
let settings = {};
|
|
405
|
+
try {
|
|
406
|
+
settings = JSON.parse(await readFile(path, "utf8"));
|
|
407
|
+
} catch (error) {
|
|
408
|
+
if (error?.code !== "ENOENT") throw error;
|
|
409
|
+
}
|
|
410
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) settings = {};
|
|
411
|
+
if (!settings.permissions || typeof settings.permissions !== "object"
|
|
412
|
+
|| Array.isArray(settings.permissions)) settings.permissions = {};
|
|
413
|
+
|
|
414
|
+
const action = String(params.action || "");
|
|
415
|
+
const behavior = String(params.behavior || "");
|
|
416
|
+
const key = behavior === "directory" ? "additionalDirectories" : behavior;
|
|
417
|
+
if (!["allow", "ask", "deny", "additionalDirectories"].includes(key)) {
|
|
418
|
+
throw new Error(`지원하지 않는 Claude 권한 규칙 종류: ${behavior}`);
|
|
419
|
+
}
|
|
420
|
+
const value = String(params.value || "").trim();
|
|
421
|
+
if (!value) throw new Error("빈 Claude 권한 규칙은 저장할 수 없습니다.");
|
|
422
|
+
const values = Array.isArray(settings.permissions[key])
|
|
423
|
+
? settings.permissions[key].map(String)
|
|
424
|
+
: [];
|
|
425
|
+
if (action === "add" && !values.includes(value)) values.push(value);
|
|
426
|
+
else if (action === "remove") {
|
|
427
|
+
const index = values.indexOf(value);
|
|
428
|
+
if (index >= 0) values.splice(index, 1);
|
|
429
|
+
} else if (action !== "add") {
|
|
430
|
+
throw new Error(`지원하지 않는 Claude 권한 규칙 작업: ${action}`);
|
|
431
|
+
}
|
|
432
|
+
settings.permissions[key] = values;
|
|
433
|
+
await mkdir(dirname(path), { recursive: true });
|
|
434
|
+
await writeFile(path, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
435
|
+
return claudePermissionStatus({ cwd });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function setClaudeAutoModeDisabled(params) {
|
|
439
|
+
const cwd = params.cwd || process.cwd();
|
|
440
|
+
const path = permissionSettingsPath(cwd, "user");
|
|
441
|
+
let settings = {};
|
|
442
|
+
try {
|
|
443
|
+
settings = JSON.parse(await readFile(path, "utf8"));
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (error?.code !== "ENOENT") throw error;
|
|
446
|
+
}
|
|
447
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) settings = {};
|
|
448
|
+
if (params.disabled === false) delete settings.disableAutoMode;
|
|
449
|
+
else settings.disableAutoMode = "disable";
|
|
450
|
+
await mkdir(dirname(path), { recursive: true });
|
|
451
|
+
await writeFile(path, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
452
|
+
return claudePermissionStatus({ cwd });
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function retryClaudePermission(params) {
|
|
456
|
+
const session = lookupSession(params.sessionId);
|
|
457
|
+
if (!session) throw new Error("재시도할 Claude 세션을 찾을 수 없습니다.");
|
|
458
|
+
const tool = String(params.tool || "tool");
|
|
459
|
+
const input = params.input && typeof params.input === "object" ? params.input : {};
|
|
460
|
+
const previousMode = session.permissionMode;
|
|
461
|
+
return startPrompt({
|
|
462
|
+
sessionId: session.id,
|
|
463
|
+
permissionMode: previousMode === "auto" ? "default" : previousMode,
|
|
464
|
+
restorePermissionMode: previousMode === "auto" ? "auto" : null,
|
|
465
|
+
input: [{
|
|
466
|
+
type: "text",
|
|
467
|
+
text: [
|
|
468
|
+
`The user selected the denied ${tool} action in /permissions and asked to retry it.`,
|
|
469
|
+
"Retry that action now. A manual permission prompt will be shown before it runs.",
|
|
470
|
+
`Original tool input: ${JSON.stringify(input)}`,
|
|
471
|
+
].join("\n"),
|
|
472
|
+
}],
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
475
|
|
|
476
476
|
function makeOptions(params, sessionId, resume) {
|
|
477
477
|
const options = {
|
|
478
478
|
cwd: params.cwd || process.cwd(),
|
|
479
479
|
includePartialMessages: true,
|
|
480
|
-
permissionMode: permissionMode(params.permissionMode),
|
|
481
|
-
// Not a mode, a capability: the SDK refuses `bypassPermissions` outright
|
|
482
|
-
// unless the session was started with this. The UI exposes it only after
|
|
483
|
-
// Claude's resolved settings and acknowledgement make the mode available.
|
|
480
|
+
permissionMode: permissionMode(params.permissionMode),
|
|
481
|
+
// Not a mode, a capability: the SDK refuses `bypassPermissions` outright
|
|
482
|
+
// unless the session was started with this. The UI exposes it only after
|
|
483
|
+
// Claude's resolved settings and acknowledgement make the mode available.
|
|
484
484
|
allowDangerouslySkipPermissions: true,
|
|
485
485
|
enableFileCheckpointing: true,
|
|
486
486
|
persistSession: true,
|
|
@@ -506,7 +506,7 @@ function makeOptions(params, sessionId, resume) {
|
|
|
506
506
|
return options;
|
|
507
507
|
}
|
|
508
508
|
|
|
509
|
-
async function startAgentQuery(prompt, options) {
|
|
509
|
+
async function startAgentQuery(prompt, options) {
|
|
510
510
|
const warm = await startup({ options });
|
|
511
511
|
try {
|
|
512
512
|
return warm.query(prompt);
|
|
@@ -514,29 +514,29 @@ async function startAgentQuery(prompt, options) {
|
|
|
514
514
|
warm.close();
|
|
515
515
|
throw error;
|
|
516
516
|
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function permissionSuggestionLabel(suggestions) {
|
|
520
|
-
if (!Array.isArray(suggestions) || !suggestions.length) return null;
|
|
521
|
-
const rules = suggestions.flatMap((suggestion) =>
|
|
522
|
-
Array.isArray(suggestion.rules)
|
|
523
|
-
? suggestion.rules.map((rule) => rule.ruleContent
|
|
524
|
-
? `${rule.toolName}(${rule.ruleContent})`
|
|
525
|
-
: String(rule.toolName || ""))
|
|
526
|
-
: []);
|
|
527
|
-
const directories = suggestions.flatMap((suggestion) =>
|
|
528
|
-
Array.isArray(suggestion.directories) ? suggestion.directories.map(String) : []);
|
|
529
|
-
const values = [...new Set([...rules, ...directories].filter(Boolean))];
|
|
530
|
-
const destinations = new Set(suggestions.map((suggestion) => suggestion.destination));
|
|
531
|
-
const scope = destinations.has("userSettings")
|
|
532
|
-
? "모든 프로젝트에서"
|
|
533
|
-
: destinations.has("projectSettings") || destinations.has("localSettings")
|
|
534
|
-
? "이 프로젝트에서"
|
|
535
|
-
: "이번 세션에서";
|
|
536
|
-
return values.length
|
|
537
|
-
? `${scope} 항상 허용: ${values.join(", ")}`
|
|
538
|
-
: `${scope} 다시 묻지 않기`;
|
|
539
|
-
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function permissionSuggestionLabel(suggestions) {
|
|
520
|
+
if (!Array.isArray(suggestions) || !suggestions.length) return null;
|
|
521
|
+
const rules = suggestions.flatMap((suggestion) =>
|
|
522
|
+
Array.isArray(suggestion.rules)
|
|
523
|
+
? suggestion.rules.map((rule) => rule.ruleContent
|
|
524
|
+
? `${rule.toolName}(${rule.ruleContent})`
|
|
525
|
+
: String(rule.toolName || ""))
|
|
526
|
+
: []);
|
|
527
|
+
const directories = suggestions.flatMap((suggestion) =>
|
|
528
|
+
Array.isArray(suggestion.directories) ? suggestion.directories.map(String) : []);
|
|
529
|
+
const values = [...new Set([...rules, ...directories].filter(Boolean))];
|
|
530
|
+
const destinations = new Set(suggestions.map((suggestion) => suggestion.destination));
|
|
531
|
+
const scope = destinations.has("userSettings")
|
|
532
|
+
? "모든 프로젝트에서"
|
|
533
|
+
: destinations.has("projectSettings") || destinations.has("localSettings")
|
|
534
|
+
? "이 프로젝트에서"
|
|
535
|
+
: "이번 세션에서";
|
|
536
|
+
return values.length
|
|
537
|
+
? `${scope} 항상 허용: ${values.join(", ")}`
|
|
538
|
+
: `${scope} 다시 묻지 않기`;
|
|
539
|
+
}
|
|
540
540
|
|
|
541
541
|
async function requestToolPermission(toolName, input, permission) {
|
|
542
542
|
if (toolName === "AskUserQuestion") {
|
|
@@ -586,43 +586,43 @@ async function requestToolPermission(toolName, input, permission) {
|
|
|
586
586
|
return { behavior: "allow", updatedInput: { ...input, answers } };
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
-
// Claude calls this callback only after its selected mode and permission rules
|
|
590
|
-
// decide that a human answer is needed. Forward every such request to the host;
|
|
591
|
-
// auto-allowing the ordinary cases here would silently defeat default/auto mode.
|
|
592
|
-
const planApproval = toolName === "ExitPlanMode";
|
|
593
|
-
const persistentApprovalLabel = permissionSuggestionLabel(permission.suggestions);
|
|
594
|
-
const common = {
|
|
595
|
-
claudePermission: true,
|
|
596
|
-
title: permission.title || permission.displayName,
|
|
597
|
-
persistentApprovalLabel,
|
|
598
|
-
};
|
|
599
|
-
|
|
600
|
-
let method = "item/permissions/requestApproval";
|
|
601
|
-
let params = {
|
|
602
|
-
...common,
|
|
603
|
-
reason: permission.decisionReason || permission.description || permission.title,
|
|
604
|
-
permissions: { tool: toolName, blockedPath: permission.blockedPath },
|
|
605
|
-
};
|
|
606
|
-
if (planApproval) {
|
|
607
|
-
params = {
|
|
608
|
-
...common,
|
|
609
|
-
reason: input.plan || permission.description || "계획대로 진행할까요?",
|
|
610
|
-
permissions: { tool: toolName },
|
|
611
|
-
};
|
|
612
|
-
} else if (toolName === "Bash") {
|
|
613
|
-
method = "item/commandExecution/requestApproval";
|
|
614
|
-
params = {
|
|
615
|
-
...common,
|
|
616
|
-
command: input.command || "command",
|
|
617
|
-
cwd: input.cwd,
|
|
618
|
-
reason: permission.decisionReason || permission.description,
|
|
589
|
+
// Claude calls this callback only after its selected mode and permission rules
|
|
590
|
+
// decide that a human answer is needed. Forward every such request to the host;
|
|
591
|
+
// auto-allowing the ordinary cases here would silently defeat default/auto mode.
|
|
592
|
+
const planApproval = toolName === "ExitPlanMode";
|
|
593
|
+
const persistentApprovalLabel = permissionSuggestionLabel(permission.suggestions);
|
|
594
|
+
const common = {
|
|
595
|
+
claudePermission: true,
|
|
596
|
+
title: permission.title || permission.displayName,
|
|
597
|
+
persistentApprovalLabel,
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
let method = "item/permissions/requestApproval";
|
|
601
|
+
let params = {
|
|
602
|
+
...common,
|
|
603
|
+
reason: permission.decisionReason || permission.description || permission.title,
|
|
604
|
+
permissions: { tool: toolName, blockedPath: permission.blockedPath },
|
|
605
|
+
};
|
|
606
|
+
if (planApproval) {
|
|
607
|
+
params = {
|
|
608
|
+
...common,
|
|
609
|
+
reason: input.plan || permission.description || "계획대로 진행할까요?",
|
|
610
|
+
permissions: { tool: toolName },
|
|
611
|
+
};
|
|
612
|
+
} else if (toolName === "Bash") {
|
|
613
|
+
method = "item/commandExecution/requestApproval";
|
|
614
|
+
params = {
|
|
615
|
+
...common,
|
|
616
|
+
command: input.command || "command",
|
|
617
|
+
cwd: input.cwd,
|
|
618
|
+
reason: permission.decisionReason || permission.description,
|
|
619
619
|
};
|
|
620
620
|
} else if (["Edit", "Write", "NotebookEdit"].includes(toolName)) {
|
|
621
|
-
method = "item/fileChange/requestApproval";
|
|
622
|
-
params = {
|
|
623
|
-
...common,
|
|
624
|
-
grantRoot: permission.blockedPath || input.file_path || input.notebook_path,
|
|
625
|
-
reason: permission.decisionReason || permission.description,
|
|
621
|
+
method = "item/fileChange/requestApproval";
|
|
622
|
+
params = {
|
|
623
|
+
...common,
|
|
624
|
+
grantRoot: permission.blockedPath || input.file_path || input.notebook_path,
|
|
625
|
+
reason: permission.decisionReason || permission.description,
|
|
626
626
|
};
|
|
627
627
|
}
|
|
628
628
|
const response = await hostRequest(method, params, permission.signal);
|
|
@@ -800,7 +800,7 @@ async function claudePluginDetail(params) {
|
|
|
800
800
|
};
|
|
801
801
|
}
|
|
802
802
|
|
|
803
|
-
async function claudeSkills(params) {
|
|
803
|
+
async function claudeSkills(params) {
|
|
804
804
|
const installed = await runClaudeJson(params, ["plugin", "list", "--json"]);
|
|
805
805
|
const skills = [];
|
|
806
806
|
for (const plugin of Array.isArray(installed) ? installed : []) {
|
|
@@ -817,48 +817,48 @@ async function claudeSkills(params) {
|
|
|
817
817
|
}
|
|
818
818
|
} catch { /* Plugins do not have to provide skills. */ }
|
|
819
819
|
}
|
|
820
|
-
return { data: [{ cwd: params.cwd || process.cwd(), skills }] };
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
function claudeMcpStatusValue(statuses) {
|
|
824
|
-
return {
|
|
825
|
-
data: (Array.isArray(statuses) ? statuses : []).map((server) => ({
|
|
826
|
-
name: String(server?.name || "MCP"),
|
|
827
|
-
serverInfo: server?.serverInfo
|
|
828
|
-
? {
|
|
829
|
-
title: String(server.serverInfo.name || server.name || "MCP"),
|
|
830
|
-
version: String(server.serverInfo.version || ""),
|
|
831
|
-
}
|
|
832
|
-
: null,
|
|
833
|
-
tools: Object.fromEntries(
|
|
834
|
-
(Array.isArray(server?.tools) ? server.tools : [])
|
|
835
|
-
.filter((tool) => tool?.name)
|
|
836
|
-
.map((tool) => [String(tool.name), tool]),
|
|
837
|
-
),
|
|
838
|
-
resources: [],
|
|
839
|
-
authStatus: server?.status === "needs-auth"
|
|
840
|
-
? "notLoggedIn"
|
|
841
|
-
: server?.status === "connected"
|
|
842
|
-
? "unsupported"
|
|
843
|
-
: String(server?.status || "unknown"),
|
|
844
|
-
status: String(server?.status || "unknown"),
|
|
845
|
-
...(server?.error ? { error: String(server.error) } : {}),
|
|
846
|
-
})),
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
async function claudeMcpStatus(params) {
|
|
851
|
-
const session = lookupSession(params.threadId || params.sessionId);
|
|
852
|
-
if (!session) {
|
|
853
|
-
return {
|
|
854
|
-
data: [],
|
|
855
|
-
unavailableReason: "Claude 세션이 아직 시작되지 않았습니다.",
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
return claudeMcpStatusValue(await session.query.mcpServerStatus());
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
function isQuestionDeliveryError(error) {
|
|
820
|
+
return { data: [{ cwd: params.cwd || process.cwd(), skills }] };
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function claudeMcpStatusValue(statuses) {
|
|
824
|
+
return {
|
|
825
|
+
data: (Array.isArray(statuses) ? statuses : []).map((server) => ({
|
|
826
|
+
name: String(server?.name || "MCP"),
|
|
827
|
+
serverInfo: server?.serverInfo
|
|
828
|
+
? {
|
|
829
|
+
title: String(server.serverInfo.name || server.name || "MCP"),
|
|
830
|
+
version: String(server.serverInfo.version || ""),
|
|
831
|
+
}
|
|
832
|
+
: null,
|
|
833
|
+
tools: Object.fromEntries(
|
|
834
|
+
(Array.isArray(server?.tools) ? server.tools : [])
|
|
835
|
+
.filter((tool) => tool?.name)
|
|
836
|
+
.map((tool) => [String(tool.name), tool]),
|
|
837
|
+
),
|
|
838
|
+
resources: [],
|
|
839
|
+
authStatus: server?.status === "needs-auth"
|
|
840
|
+
? "notLoggedIn"
|
|
841
|
+
: server?.status === "connected"
|
|
842
|
+
? "unsupported"
|
|
843
|
+
: String(server?.status || "unknown"),
|
|
844
|
+
status: String(server?.status || "unknown"),
|
|
845
|
+
...(server?.error ? { error: String(server.error) } : {}),
|
|
846
|
+
})),
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async function claudeMcpStatus(params) {
|
|
851
|
+
const session = lookupSession(params.threadId || params.sessionId);
|
|
852
|
+
if (!session) {
|
|
853
|
+
return {
|
|
854
|
+
data: [],
|
|
855
|
+
unavailableReason: "Claude 세션이 아직 시작되지 않았습니다.",
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
return claudeMcpStatusValue(await session.query.mcpServerStatus());
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function isQuestionDeliveryError(error) {
|
|
862
862
|
return error?.code === -32700
|
|
863
863
|
|| String(error?.message || error).includes("사용자 입력 화면에 전달하지 못했습니다");
|
|
864
864
|
}
|
|
@@ -895,8 +895,8 @@ async function createSession(params, resumeId) {
|
|
|
895
895
|
turnSequence: 1,
|
|
896
896
|
itemSequence: 1,
|
|
897
897
|
streamBlocks: new Map(),
|
|
898
|
-
tools: new Map(),
|
|
899
|
-
permissionDenials: [],
|
|
898
|
+
tools: new Map(),
|
|
899
|
+
permissionDenials: [],
|
|
900
900
|
tasks: new Map(),
|
|
901
901
|
planCreatePending: false,
|
|
902
902
|
subagents: new Map(),
|
|
@@ -1802,16 +1802,16 @@ function toolOutput(content, structured) {
|
|
|
1802
1802
|
return content == null ? "" : JSON.stringify(content, null, 2);
|
|
1803
1803
|
}
|
|
1804
1804
|
|
|
1805
|
-
async function processResult(session, message) {
|
|
1806
|
-
if (!session.turn) return;
|
|
1807
|
-
for (const denial of Array.isArray(message.permission_denials) ? message.permission_denials : []) {
|
|
1808
|
-
rememberPermissionDenial(session, {
|
|
1809
|
-
tool: denial.tool_name,
|
|
1810
|
-
toolUseId: denial.tool_use_id,
|
|
1811
|
-
input: denial.tool_input,
|
|
1812
|
-
});
|
|
1813
|
-
}
|
|
1814
|
-
const interrupted = session.turn.interruptRequested === true;
|
|
1805
|
+
async function processResult(session, message) {
|
|
1806
|
+
if (!session.turn) return;
|
|
1807
|
+
for (const denial of Array.isArray(message.permission_denials) ? message.permission_denials : []) {
|
|
1808
|
+
rememberPermissionDenial(session, {
|
|
1809
|
+
tool: denial.tool_name,
|
|
1810
|
+
toolUseId: denial.tool_use_id,
|
|
1811
|
+
input: denial.tool_input,
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
const interrupted = session.turn.interruptRequested === true;
|
|
1815
1815
|
const totals = [...Object.values(message.modelUsage || {})].reduce((sum, usage) => ({
|
|
1816
1816
|
inputTokens: sum.inputTokens + Number(usage.inputTokens || 0) + Number(usage.cacheReadInputTokens || 0) + Number(usage.cacheCreationInputTokens || 0),
|
|
1817
1817
|
cachedInputTokens: sum.cachedInputTokens + Number(usage.cacheReadInputTokens || 0),
|
|
@@ -1830,42 +1830,42 @@ async function processResult(session, message) {
|
|
|
1830
1830
|
modelContextWindow: totals.contextWindow || session.lastContextWindow || undefined,
|
|
1831
1831
|
},
|
|
1832
1832
|
});
|
|
1833
|
-
const error = message.is_error && !interrupted
|
|
1833
|
+
const error = message.is_error && !interrupted
|
|
1834
1834
|
? { message: message.errors?.join("\n") || message.stop_reason || "Claude 실행 실패" }
|
|
1835
1835
|
: null;
|
|
1836
|
-
finishTurn(session, error, message.duration_ms);
|
|
1837
|
-
if (session.restorePermissionMode) {
|
|
1838
|
-
const restore = session.restorePermissionMode;
|
|
1839
|
-
session.restorePermissionMode = null;
|
|
1840
|
-
await applyPermissionMode(session, restore);
|
|
1841
|
-
}
|
|
1836
|
+
finishTurn(session, error, message.duration_ms);
|
|
1837
|
+
if (session.restorePermissionMode) {
|
|
1838
|
+
const restore = session.restorePermissionMode;
|
|
1839
|
+
session.restorePermissionMode = null;
|
|
1840
|
+
await applyPermissionMode(session, restore);
|
|
1841
|
+
}
|
|
1842
1842
|
notify("claude/account/updated", {
|
|
1843
1843
|
threadId: session.id,
|
|
1844
1844
|
account: await safeAccount(session.query),
|
|
1845
1845
|
usage: await safeUsage(session.query),
|
|
1846
1846
|
});
|
|
1847
|
-
await runPendingPrompt(session);
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
function rememberPermissionDenial(session, denial) {
|
|
1851
|
-
const toolUseId = String(denial.toolUseId || "");
|
|
1852
|
-
const existing = toolUseId
|
|
1853
|
-
? session.permissionDenials.find((candidate) => candidate.toolUseId === toolUseId)
|
|
1854
|
-
: null;
|
|
1855
|
-
if (existing) {
|
|
1856
|
-
existing.tool = String(denial.tool || existing.tool || "Tool");
|
|
1857
|
-
existing.reason = String(denial.reason || existing.reason || "");
|
|
1858
|
-
if (denial.input && typeof denial.input === "object") existing.input = denial.input;
|
|
1859
|
-
return;
|
|
1860
|
-
}
|
|
1861
|
-
session.permissionDenials.push({
|
|
1862
|
-
tool: String(denial.tool || "Tool"),
|
|
1863
|
-
toolUseId,
|
|
1864
|
-
reason: String(denial.reason || ""),
|
|
1865
|
-
input: denial.input && typeof denial.input === "object" ? denial.input : {},
|
|
1866
|
-
});
|
|
1867
|
-
if (session.permissionDenials.length > 20) session.permissionDenials.shift();
|
|
1868
|
-
}
|
|
1847
|
+
await runPendingPrompt(session);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
function rememberPermissionDenial(session, denial) {
|
|
1851
|
+
const toolUseId = String(denial.toolUseId || "");
|
|
1852
|
+
const existing = toolUseId
|
|
1853
|
+
? session.permissionDenials.find((candidate) => candidate.toolUseId === toolUseId)
|
|
1854
|
+
: null;
|
|
1855
|
+
if (existing) {
|
|
1856
|
+
existing.tool = String(denial.tool || existing.tool || "Tool");
|
|
1857
|
+
existing.reason = String(denial.reason || existing.reason || "");
|
|
1858
|
+
if (denial.input && typeof denial.input === "object") existing.input = denial.input;
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
session.permissionDenials.push({
|
|
1862
|
+
tool: String(denial.tool || "Tool"),
|
|
1863
|
+
toolUseId,
|
|
1864
|
+
reason: String(denial.reason || ""),
|
|
1865
|
+
input: denial.input && typeof denial.input === "object" ? denial.input : {},
|
|
1866
|
+
});
|
|
1867
|
+
if (session.permissionDenials.length > 20) session.permissionDenials.shift();
|
|
1868
|
+
}
|
|
1869
1869
|
|
|
1870
1870
|
// The turn that was waiting starts on its own, so the host sees it exactly like a
|
|
1871
1871
|
// prompt sent the moment the previous turn ended.
|
|
@@ -1908,16 +1908,16 @@ async function consume(session) {
|
|
|
1908
1908
|
await processStreamEvent(session, message);
|
|
1909
1909
|
} else if (message.type === "assistant") processAssistant(session, message);
|
|
1910
1910
|
else if (message.type === "user") processUser(session, message);
|
|
1911
|
-
else if (message.type === "result") await processResult(session, message);
|
|
1912
|
-
else if (message.type === "system" && message.subtype === "compact_boundary") {
|
|
1913
|
-
notify("thread/compacted", { threadId: session.id });
|
|
1914
|
-
} else if (message.type === "system" && message.subtype === "permission_denied") {
|
|
1915
|
-
rememberPermissionDenial(session, {
|
|
1916
|
-
tool: message.tool_name,
|
|
1917
|
-
toolUseId: message.tool_use_id,
|
|
1918
|
-
reason: message.decision_reason || message.decision_reason_type,
|
|
1919
|
-
});
|
|
1920
|
-
} else if (message.type === "rate_limit_event") {
|
|
1911
|
+
else if (message.type === "result") await processResult(session, message);
|
|
1912
|
+
else if (message.type === "system" && message.subtype === "compact_boundary") {
|
|
1913
|
+
notify("thread/compacted", { threadId: session.id });
|
|
1914
|
+
} else if (message.type === "system" && message.subtype === "permission_denied") {
|
|
1915
|
+
rememberPermissionDenial(session, {
|
|
1916
|
+
tool: message.tool_name,
|
|
1917
|
+
toolUseId: message.tool_use_id,
|
|
1918
|
+
reason: message.decision_reason || message.decision_reason_type,
|
|
1919
|
+
});
|
|
1920
|
+
} else if (message.type === "rate_limit_event") {
|
|
1921
1921
|
notify("claude/account/updated", { threadId: session.id, rateLimitInfo: message.rate_limit_info });
|
|
1922
1922
|
} else if (message.type === "system" && message.subtype === "api_retry") {
|
|
1923
1923
|
notify("warning", { threadId: session.id, provider: "Claude", message: `Claude API 재시도 ${message.attempt}/${message.max_retries}` });
|
|
@@ -1993,19 +1993,19 @@ async function runPrompt(session, params) {
|
|
|
1993
1993
|
await session.query.applyFlagSettings({ effortLevel: effort });
|
|
1994
1994
|
}
|
|
1995
1995
|
session.effort = effort;
|
|
1996
|
-
const permissionRejection = await applyPermissionMode(session, params.permissionMode);
|
|
1997
|
-
if (permissionRejection) {
|
|
1998
|
-
notify("claude/permissionMode/rejected", {
|
|
1999
|
-
threadId: visibleSession(session.id),
|
|
2000
|
-
permissionMode: params.permissionMode,
|
|
2001
|
-
effectivePermissionMode: session.permissionMode,
|
|
2002
|
-
message: permissionRejection,
|
|
2003
|
-
});
|
|
2004
|
-
}
|
|
2005
|
-
if (params.restorePermissionMode) {
|
|
2006
|
-
session.restorePermissionMode = permissionMode(params.restorePermissionMode);
|
|
2007
|
-
}
|
|
2008
|
-
const content = await inputContent(params.input, params.handoffContext);
|
|
1996
|
+
const permissionRejection = await applyPermissionMode(session, params.permissionMode);
|
|
1997
|
+
if (permissionRejection) {
|
|
1998
|
+
notify("claude/permissionMode/rejected", {
|
|
1999
|
+
threadId: visibleSession(session.id),
|
|
2000
|
+
permissionMode: params.permissionMode,
|
|
2001
|
+
effectivePermissionMode: session.permissionMode,
|
|
2002
|
+
message: permissionRejection,
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
if (params.restorePermissionMode) {
|
|
2006
|
+
session.restorePermissionMode = permissionMode(params.restorePermissionMode);
|
|
2007
|
+
}
|
|
2008
|
+
const content = await inputContent(params.input, params.handoffContext);
|
|
2009
2009
|
const turnId = beginTurn(session, params.input);
|
|
2010
2010
|
session.queue.push({
|
|
2011
2011
|
type: "user",
|
|
@@ -2274,10 +2274,10 @@ async function readableCwd(id, cwd) {
|
|
|
2274
2274
|
return await transcriptCwd(id) || cwd;
|
|
2275
2275
|
}
|
|
2276
2276
|
|
|
2277
|
-
async function dispatch(method, params = {}) {
|
|
2278
|
-
if (method === "model/list") return loadModelCatalog(params);
|
|
2279
|
-
if (method === "mcpServerStatus/list") return claudeMcpStatus(params);
|
|
2280
|
-
if (method === "plugin/list") return claudePluginCatalog(params);
|
|
2277
|
+
async function dispatch(method, params = {}) {
|
|
2278
|
+
if (method === "model/list") return loadModelCatalog(params);
|
|
2279
|
+
if (method === "mcpServerStatus/list") return claudeMcpStatus(params);
|
|
2280
|
+
if (method === "plugin/list") return claudePluginCatalog(params);
|
|
2281
2281
|
if (method === "plugin/installed") return claudePluginCatalog(params, true);
|
|
2282
2282
|
if (method === "plugin/read") return claudePluginDetail(params);
|
|
2283
2283
|
if (method === "plugin/install") {
|
|
@@ -2327,24 +2327,24 @@ async function dispatch(method, params = {}) {
|
|
|
2327
2327
|
}
|
|
2328
2328
|
if (method === "skills/list") return claudeSkills(params);
|
|
2329
2329
|
if (method === "app/list") return { data: [] };
|
|
2330
|
-
if (method === "plugin/reload") {
|
|
2331
|
-
return { message: "Claude 플러그인 설정을 다시 읽었습니다. 새 대화부터 적용됩니다." };
|
|
2332
|
-
}
|
|
2333
|
-
if (method === "permissions/status") return claudePermissionStatus(params);
|
|
2334
|
-
if (method === "permissions/update") return updateClaudePermission(params);
|
|
2335
|
-
if (method === "permissions/auto-mode") return setClaudeAutoModeDisabled(params);
|
|
2336
|
-
if (method === "permissions/retry") return retryClaudePermission(params);
|
|
2337
|
-
if (method === "session/permissionMode") {
|
|
2338
|
-
const session = lookupSession(params.sessionId);
|
|
2339
|
-
// A session that has not started yet picks the mode up from its first turn.
|
|
2340
|
-
if (!session) return { permissionMode: permissionMode(params.permissionMode) };
|
|
2341
|
-
session.restorePermissionMode = null;
|
|
2342
|
-
const rejection = await applyPermissionMode(session, params.permissionMode);
|
|
2343
|
-
return {
|
|
2344
|
-
permissionMode: session.permissionMode,
|
|
2345
|
-
...(rejection ? { rejection } : {}),
|
|
2346
|
-
};
|
|
2347
|
-
}
|
|
2330
|
+
if (method === "plugin/reload") {
|
|
2331
|
+
return { message: "Claude 플러그인 설정을 다시 읽었습니다. 새 대화부터 적용됩니다." };
|
|
2332
|
+
}
|
|
2333
|
+
if (method === "permissions/status") return claudePermissionStatus(params);
|
|
2334
|
+
if (method === "permissions/update") return updateClaudePermission(params);
|
|
2335
|
+
if (method === "permissions/auto-mode") return setClaudeAutoModeDisabled(params);
|
|
2336
|
+
if (method === "permissions/retry") return retryClaudePermission(params);
|
|
2337
|
+
if (method === "session/permissionMode") {
|
|
2338
|
+
const session = lookupSession(params.sessionId);
|
|
2339
|
+
// A session that has not started yet picks the mode up from its first turn.
|
|
2340
|
+
if (!session) return { permissionMode: permissionMode(params.permissionMode) };
|
|
2341
|
+
session.restorePermissionMode = null;
|
|
2342
|
+
const rejection = await applyPermissionMode(session, params.permissionMode);
|
|
2343
|
+
return {
|
|
2344
|
+
permissionMode: session.permissionMode,
|
|
2345
|
+
...(rejection ? { rejection } : {}),
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
2348
|
if (method === "session/start") {
|
|
2349
2349
|
const { session, account, usage } = await createSession(params);
|
|
2350
2350
|
return {
|
|
@@ -2497,12 +2497,12 @@ async function dispatch(method, params = {}) {
|
|
|
2497
2497
|
throw new Error(`지원하지 않는 Claude 브리지 메서드: ${method}`);
|
|
2498
2498
|
}
|
|
2499
2499
|
|
|
2500
|
-
async function runSelfTest() {
|
|
2501
|
-
if (permissionMode("dontAsk") !== "dontAsk"
|
|
2502
|
-
|| permissionMode("not-a-mode", "auto") !== "auto") {
|
|
2503
|
-
throw new Error("Claude permission mode self-test failed");
|
|
2504
|
-
}
|
|
2505
|
-
const pluginCatalog = buildClaudePluginCatalog(
|
|
2500
|
+
async function runSelfTest() {
|
|
2501
|
+
if (permissionMode("dontAsk") !== "dontAsk"
|
|
2502
|
+
|| permissionMode("not-a-mode", "auto") !== "auto") {
|
|
2503
|
+
throw new Error("Claude permission mode self-test failed");
|
|
2504
|
+
}
|
|
2505
|
+
const pluginCatalog = buildClaudePluginCatalog(
|
|
2506
2506
|
[{ id: "cloudflare@official", enabled: true, scope: "user" }],
|
|
2507
2507
|
[{
|
|
2508
2508
|
pluginId: "cloudflare@official",
|
|
@@ -2513,29 +2513,29 @@ async function runSelfTest() {
|
|
|
2513
2513
|
[{ name: "official", installLocation: "/tmp/official" }],
|
|
2514
2514
|
);
|
|
2515
2515
|
const plugin = pluginCatalog.marketplaces[0]?.plugins[0];
|
|
2516
|
-
if (plugin?.id !== "cloudflare@official"
|
|
2516
|
+
if (plugin?.id !== "cloudflare@official"
|
|
2517
2517
|
|| plugin?.name !== "cloudflare@official"
|
|
2518
2518
|
|| plugin?.interface?.displayName !== "cloudflare"
|
|
2519
2519
|
|| plugin?.installed !== true
|
|
2520
2520
|
|| plugin?.enabled !== true) {
|
|
2521
|
-
throw new Error(`Claude plugin catalogue self-test failed: ${JSON.stringify(pluginCatalog)}`);
|
|
2522
|
-
}
|
|
2523
|
-
const mcpStatus = claudeMcpStatusValue([
|
|
2524
|
-
{
|
|
2525
|
-
name: "context7",
|
|
2526
|
-
status: "connected",
|
|
2527
|
-
serverInfo: { name: "Context7", version: "1.0.0" },
|
|
2528
|
-
tools: [{ name: "resolve" }, { name: "docs" }],
|
|
2529
|
-
},
|
|
2530
|
-
{ name: "figma", status: "needs-auth", tools: [] },
|
|
2531
|
-
]).data;
|
|
2532
|
-
if (mcpStatus[0]?.name !== "context7"
|
|
2533
|
-
|| Object.keys(mcpStatus[0]?.tools || {}).length !== 2
|
|
2534
|
-
|| mcpStatus[0]?.status !== "connected"
|
|
2535
|
-
|| mcpStatus[1]?.authStatus !== "notLoggedIn") {
|
|
2536
|
-
throw new Error(`Claude MCP status self-test failed: ${JSON.stringify(mcpStatus)}`);
|
|
2537
|
-
}
|
|
2538
|
-
const user = (uuid, text) => ({
|
|
2521
|
+
throw new Error(`Claude plugin catalogue self-test failed: ${JSON.stringify(pluginCatalog)}`);
|
|
2522
|
+
}
|
|
2523
|
+
const mcpStatus = claudeMcpStatusValue([
|
|
2524
|
+
{
|
|
2525
|
+
name: "context7",
|
|
2526
|
+
status: "connected",
|
|
2527
|
+
serverInfo: { name: "Context7", version: "1.0.0" },
|
|
2528
|
+
tools: [{ name: "resolve" }, { name: "docs" }],
|
|
2529
|
+
},
|
|
2530
|
+
{ name: "figma", status: "needs-auth", tools: [] },
|
|
2531
|
+
]).data;
|
|
2532
|
+
if (mcpStatus[0]?.name !== "context7"
|
|
2533
|
+
|| Object.keys(mcpStatus[0]?.tools || {}).length !== 2
|
|
2534
|
+
|| mcpStatus[0]?.status !== "connected"
|
|
2535
|
+
|| mcpStatus[1]?.authStatus !== "notLoggedIn") {
|
|
2536
|
+
throw new Error(`Claude MCP status self-test failed: ${JSON.stringify(mcpStatus)}`);
|
|
2537
|
+
}
|
|
2538
|
+
const user = (uuid, text) => ({
|
|
2539
2539
|
type: "user",
|
|
2540
2540
|
uuid,
|
|
2541
2541
|
message: { role: "user", content: [{ type: "text", text }] },
|
package/package.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "devez-vibe",
|
|
3
|
-
"version": "1.3.
|
|
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
|
-
"README.md",
|
|
27
|
-
"LICENSE"
|
|
28
|
-
],
|
|
29
|
-
"os": [
|
|
30
|
-
"win32"
|
|
31
|
-
],
|
|
32
|
-
"cpu": [
|
|
33
|
-
"x64"
|
|
34
|
-
],
|
|
35
|
-
"engines": {
|
|
36
|
-
"node": ">=18"
|
|
37
|
-
},
|
|
38
|
-
"dependencies": {
|
|
39
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.223"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "devez-vibe",
|
|
3
|
+
"version": "1.3.22",
|
|
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
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"os": [
|
|
30
|
+
"win32"
|
|
31
|
+
],
|
|
32
|
+
"cpu": [
|
|
33
|
+
"x64"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.223"
|
|
40
|
+
}
|
|
41
|
+
}
|