devez-vibe 1.6.57 → 1.6.59

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 CHANGED
Binary file
@@ -357,34 +357,57 @@ function adoptSessionId(session, incoming) {
357
357
  });
358
358
  }
359
359
 
360
- const PERMISSION_MODES = [
361
- "default",
362
- "acceptEdits",
363
- "plan",
364
- "auto",
365
- "dontAsk",
366
- "bypassPermissions",
367
- ];
360
+ const BOOTSTRAP_PERMISSION_MODE = "default";
361
+ const PREFERRED_PERMISSION_MODE = "bypassPermissions";
362
+ const FALLBACK_PERMISSION_MODE = "auto";
368
363
 
369
- function permissionMode(requested, fallback = "default") {
370
- const mode = String(requested || "");
371
- return PERMISSION_MODES.includes(mode) ? mode : fallback;
364
+ function permissionError(error) {
365
+ return error?.message || String(error);
372
366
  }
373
367
 
374
- // Moves a live session onto a mode the badge picked. A rejected mode — policy
375
- // disables bypass, say — leaves the session on the one it already had.
376
- async function applyPermissionMode(session, requested) {
377
- const mode = permissionMode(requested, session.permissionMode || "default");
378
- if (mode === session.permissionMode) return null;
379
- try {
380
- await session.query.setPermissionMode(mode);
381
- session.permissionMode = mode;
368
+ // No tool-capable turn is admitted until the SDK has confirmed bypass mode or,
369
+ // when policy rejects it, confirmed auto mode. A double rejection fails the
370
+ // session instead of silently continuing in the bootstrap default mode.
371
+ async function applyPermissionMode(session) {
372
+ if (session.permissionModeVerified
373
+ && (session.permissionMode === PREFERRED_PERMISSION_MODE
374
+ || (session.bypassUnavailable && session.permissionMode === FALLBACK_PERMISSION_MODE))) {
382
375
  return null;
376
+ }
377
+ if (!session.bypassUnavailable) {
378
+ try {
379
+ await session.query.setPermissionMode(PREFERRED_PERMISSION_MODE);
380
+ session.permissionMode = PREFERRED_PERMISSION_MODE;
381
+ session.permissionModeVerified = true;
382
+ return null;
383
+ } catch (error) {
384
+ session.bypassUnavailable = true;
385
+ session.bypassRejection = permissionError(error);
386
+ }
387
+ }
388
+ try {
389
+ await session.query.setPermissionMode(FALLBACK_PERMISSION_MODE);
390
+ session.permissionMode = FALLBACK_PERMISSION_MODE;
391
+ session.permissionModeVerified = true;
392
+ return session.bypassRejection;
383
393
  } catch (error) {
384
- return error?.message || String(error);
394
+ session.permissionModeVerified = false;
395
+ throw new Error(
396
+ `Claude bypass 모드가 거부되었고 auto 폴백도 적용하지 못했습니다: ${permissionError(error)}`,
397
+ );
385
398
  }
386
399
  }
387
400
 
401
+ function notifyPermissionFallback(session, rejection) {
402
+ if (!rejection) return;
403
+ notify("claude/permissionMode/rejected", {
404
+ threadId: visibleSession(session.id),
405
+ permissionMode: PREFERRED_PERMISSION_MODE,
406
+ effectivePermissionMode: FALLBACK_PERMISSION_MODE,
407
+ message: `Bypass 모드를 사용할 수 없어 auto 모드로 실행합니다: ${rejection}`,
408
+ });
409
+ }
410
+
388
411
  async function claudePermissionStatus(params) {
389
412
  const resolved = await resolveSettings({
390
413
  cwd: params.cwd || process.cwd(),
@@ -396,9 +419,7 @@ async function claudePermissionStatus(params) {
396
419
  || effective.bypassPermissionsModeAccepted === true;
397
420
  const bypassAvailable = permissions.disableBypassPermissionsMode !== "disable" && bypassAccepted;
398
421
  const autoDisabled = effective.disableAutoMode === "disable";
399
- let defaultMode = permissionMode(permissions.defaultMode);
400
- if ((defaultMode === "bypassPermissions" && !bypassAvailable)
401
- || (defaultMode === "auto" && autoDisabled)) defaultMode = "default";
422
+ const defaultMode = bypassAvailable ? PREFERRED_PERMISSION_MODE : FALLBACK_PERMISSION_MODE;
402
423
  const rules = [];
403
424
  const directories = [];
404
425
  for (const source of resolved.sources) {
@@ -516,16 +537,14 @@ async function retryClaudePermission(params) {
516
537
  if (!session) throw new Error("재시도할 Claude 세션을 찾을 수 없습니다.");
517
538
  const tool = String(params.tool || "tool");
518
539
  const input = params.input && typeof params.input === "object" ? params.input : {};
519
- const previousMode = session.permissionMode;
520
540
  return startPrompt({
521
541
  sessionId: session.id,
522
- permissionMode: previousMode === "auto" ? "default" : previousMode,
523
- restorePermissionMode: previousMode === "auto" ? "auto" : null,
542
+ permissionMode: session.permissionMode,
524
543
  input: [{
525
544
  type: "text",
526
545
  text: [
527
546
  `The user selected the denied ${tool} action in /permissions and asked to retry it.`,
528
- "Retry that action now. A manual permission prompt will be shown before it runs.",
547
+ `Retry that action now under ${session.permissionMode} mode.`,
529
548
  `Original tool input: ${JSON.stringify(input)}`,
530
549
  ].join("\n"),
531
550
  }],
@@ -566,10 +585,11 @@ function makeOptions(params, sessionId, resume) {
566
585
  const options = {
567
586
  cwd: params.cwd || process.cwd(),
568
587
  includePartialMessages: true,
569
- permissionMode: permissionMode(params.permissionMode),
570
- // Not a mode, a capability: the SDK refuses `bypassPermissions` outright
571
- // unless the session was started with this. The UI exposes it only after
572
- // Claude's resolved settings and acknowledgement make the mode available.
588
+ // Initialization performs no tool work. The bridge verifies bypass or its
589
+ // auto fallback immediately afterwards and before exposing the session.
590
+ permissionMode: BOOTSTRAP_PERMISSION_MODE,
591
+ // Not a mode, a capability: the SDK refuses the preferred bypass transition
592
+ // outright unless the session was initialized with this allowance.
573
593
  allowDangerouslySkipPermissions: true,
574
594
  enableFileCheckpointing: true,
575
595
  persistSession: true,
@@ -1137,7 +1157,10 @@ async function createSession(params, resumeId) {
1137
1157
  cwd: params.cwd || process.cwd(),
1138
1158
  model: visibleModel(params.model),
1139
1159
  effort: params.effort || "",
1140
- permissionMode: permissionMode(params.permissionMode),
1160
+ permissionMode: BOOTSTRAP_PERMISSION_MODE,
1161
+ permissionModeVerified: false,
1162
+ bypassUnavailable: false,
1163
+ bypassRejection: null,
1141
1164
  models: [],
1142
1165
  queue,
1143
1166
  query: null,
@@ -1181,8 +1204,10 @@ async function createSession(params, resumeId) {
1181
1204
  });
1182
1205
  session.consumer = consumer;
1183
1206
  let initialization;
1207
+ let permissionRejection;
1184
1208
  try {
1185
1209
  initialization = await agentQuery.initializationResult();
1210
+ permissionRejection = await applyPermissionMode(session);
1186
1211
  } catch (error) {
1187
1212
  sessions.delete(id);
1188
1213
  queue.close();
@@ -1190,6 +1215,7 @@ async function createSession(params, resumeId) {
1190
1215
  await Promise.race([consumer, new Promise((resolve) => setTimeout(resolve, 1000))]);
1191
1216
  throw error;
1192
1217
  }
1218
+ notifyPermissionFallback(session, permissionRejection);
1193
1219
  session.models = Array.isArray(initialization.models) ? initialization.models : [];
1194
1220
  const capabilities = modelCapabilities(session.models, params.model);
1195
1221
  // A resumed session can only name its model as the resolved id the transcript
@@ -2332,11 +2358,6 @@ async function processResult(session, message) {
2332
2358
  ? { message: message.errors?.join("\n") || message.stop_reason || "Claude 실행 실패" }
2333
2359
  : null;
2334
2360
  finishTurn(session, error, message.duration_ms);
2335
- if (session.restorePermissionMode) {
2336
- const restore = session.restorePermissionMode;
2337
- session.restorePermissionMode = null;
2338
- await applyPermissionMode(session, restore);
2339
- }
2340
2361
  notify("claude/account/updated", {
2341
2362
  threadId: session.id,
2342
2363
  account: await safeAccount(session.query),
@@ -2569,6 +2590,9 @@ async function runPrompt(session, params) {
2569
2590
  const model = stripClaudeModel(params.model);
2570
2591
  await session.query.setModel(model);
2571
2592
  session.model = visibleModel(params.model);
2593
+ // Model switches can change which permission modes policy accepts. Recheck
2594
+ // the preferred/fallback pair before admitting the turn.
2595
+ session.permissionModeVerified = false;
2572
2596
  }
2573
2597
  session.steerPending = 0;
2574
2598
  const effort = supportedEffort(modelCapabilities(session.models, params.model || session.model), params.effort);
@@ -2576,18 +2600,8 @@ async function runPrompt(session, params) {
2576
2600
  await session.query.applyFlagSettings({ effortLevel: effort });
2577
2601
  }
2578
2602
  session.effort = effort;
2579
- const permissionRejection = await applyPermissionMode(session, params.permissionMode);
2580
- if (permissionRejection) {
2581
- notify("claude/permissionMode/rejected", {
2582
- threadId: visibleSession(session.id),
2583
- permissionMode: params.permissionMode,
2584
- effectivePermissionMode: session.permissionMode,
2585
- message: permissionRejection,
2586
- });
2587
- }
2588
- if (params.restorePermissionMode) {
2589
- session.restorePermissionMode = permissionMode(params.restorePermissionMode);
2590
- }
2603
+ const permissionRejection = await applyPermissionMode(session);
2604
+ notifyPermissionFallback(session, permissionRejection);
2591
2605
  const content = await inputContent(params.input, params.handoffContext);
2592
2606
  const turnId = beginTurn(session, params.input);
2593
2607
  session.queue.push({
@@ -3032,10 +3046,9 @@ async function dispatch(method, params = {}) {
3032
3046
  if (method === "permissions/retry") return retryClaudePermission(params);
3033
3047
  if (method === "session/permissionMode") {
3034
3048
  const session = lookupSession(params.sessionId);
3035
- // A session that has not started yet picks the mode up from its first turn.
3036
- if (!session) return { permissionMode: permissionMode(params.permissionMode) };
3037
- session.restorePermissionMode = null;
3038
- const rejection = await applyPermissionMode(session, params.permissionMode);
3049
+ if (!session) return { permissionMode: PREFERRED_PERMISSION_MODE };
3050
+ const rejection = await applyPermissionMode(session);
3051
+ notifyPermissionFallback(session, rejection);
3039
3052
  return {
3040
3053
  permissionMode: session.permissionMode,
3041
3054
  ...(rejection ? { rejection } : {}),
@@ -3203,7 +3216,54 @@ async function dispatch(method, params = {}) {
3203
3216
  throw new Error(`지원하지 않는 Claude 브리지 메서드: ${method}`);
3204
3217
  }
3205
3218
 
3219
+ async function runPermissionModeSelfTest() {
3220
+ const fakeSession = (setPermissionMode) => ({
3221
+ id: "permission-mode-self-test",
3222
+ permissionMode: BOOTSTRAP_PERMISSION_MODE,
3223
+ permissionModeVerified: false,
3224
+ bypassUnavailable: false,
3225
+ bypassRejection: null,
3226
+ query: { setPermissionMode },
3227
+ });
3228
+
3229
+ const preferredCalls = [];
3230
+ const preferred = fakeSession(async (mode) => preferredCalls.push(mode));
3231
+ const preferredRejection = await applyPermissionMode(preferred);
3232
+ if (preferredRejection !== null
3233
+ || preferred.permissionMode !== PREFERRED_PERMISSION_MODE
3234
+ || preferredCalls.join(",") !== PREFERRED_PERMISSION_MODE) {
3235
+ throw new Error(`Claude bypass permission self-test failed: ${JSON.stringify(preferredCalls)}`);
3236
+ }
3237
+
3238
+ const fallbackCalls = [];
3239
+ const fallback = fakeSession(async (mode) => {
3240
+ fallbackCalls.push(mode);
3241
+ if (mode === PREFERRED_PERMISSION_MODE) throw new Error("organization policy");
3242
+ });
3243
+ const fallbackRejection = await applyPermissionMode(fallback);
3244
+ await applyPermissionMode(fallback);
3245
+ if (fallbackRejection !== "organization policy"
3246
+ || fallback.permissionMode !== FALLBACK_PERMISSION_MODE
3247
+ || fallbackCalls.join(",") !== `${PREFERRED_PERMISSION_MODE},${FALLBACK_PERMISSION_MODE}`) {
3248
+ throw new Error(`Claude auto fallback self-test failed: ${JSON.stringify(fallbackCalls)}`);
3249
+ }
3250
+
3251
+ const rejected = fakeSession(async (mode) => {
3252
+ throw new Error(`${mode} rejected`);
3253
+ });
3254
+ let doubleRejection = "";
3255
+ try {
3256
+ await applyPermissionMode(rejected);
3257
+ } catch (error) {
3258
+ doubleRejection = permissionError(error);
3259
+ }
3260
+ if (!doubleRejection.includes("auto 폴백도 적용하지 못했습니다")) {
3261
+ throw new Error(`Claude permission double rejection self-test failed: ${doubleRejection}`);
3262
+ }
3263
+ }
3264
+
3206
3265
  async function runSelfTest() {
3266
+ await runPermissionModeSelfTest();
3207
3267
  const equivalentCwd = process.platform === "win32"
3208
3268
  ? sameCwd("D:\\Repo", "d:/repo/")
3209
3269
  : sameCwd("/tmp/repo", "/tmp/repo/");
@@ -3215,12 +3275,9 @@ async function runSelfTest() {
3215
3275
  if (!equivalentCwd || preview !== "세션 목록 질문") {
3216
3276
  throw new Error(`Claude session list self-test failed: ${JSON.stringify({ equivalentCwd, preview })}`);
3217
3277
  }
3218
- if (permissionMode("dontAsk") !== "dontAsk"
3219
- || permissionMode("not-a-mode", "auto") !== "auto") {
3220
- throw new Error("Claude permission mode self-test failed");
3221
- }
3222
- const latestOptions = makeOptions({ cwd: process.cwd() }, "00000000-0000-4000-8000-000000000000");
3223
- if (latestOptions.perTaskStopAffordance !== true
3278
+ const latestOptions = makeOptions({ cwd: process.cwd(), permissionMode: "plan" }, "00000000-0000-4000-8000-000000000000");
3279
+ if (latestOptions.permissionMode !== BOOTSTRAP_PERMISSION_MODE
3280
+ || latestOptions.perTaskStopAffordance !== true
3224
3281
  || CLAUDE_TASK_TOOLS.some((tool) => !latestOptions.allowedTools.includes(tool))) {
3225
3282
  throw new Error(`Claude latest SDK options self-test failed: ${JSON.stringify(latestOptions.allowedTools)}`);
3226
3283
  }
@@ -4231,6 +4288,12 @@ async function runSelfTest() {
4231
4288
  process.stdout.write("Claude bridge self-test passed\n");
4232
4289
  }
4233
4290
 
4291
+ if (process.argv.includes("--permission-mode-self-test")) {
4292
+ await runPermissionModeSelfTest();
4293
+ process.stdout.write("권한 모드 자체 점검 통과\n");
4294
+ process.exit(0);
4295
+ }
4296
+
4234
4297
  if (process.argv.includes("--self-test")) {
4235
4298
  await runSelfTest();
4236
4299
  process.exit(0);
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
- {
2
- "name": "devez-vibe",
3
- "version": "1.6.57",
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
- "skills/luna-loop/",
28
- "README.md",
29
- "LICENSE"
30
- ],
31
- "os": [
32
- "win32"
33
- ],
34
- "cpu": [
35
- "x64"
36
- ],
37
- "engines": {
38
- "node": ">=18"
39
- },
40
- "scripts": {
41
- "postinstall": "node install-skills.mjs"
42
- },
43
- "dependencies": {
44
- "@anthropic-ai/claude-agent-sdk": "0.3.247"
45
- }
46
- }
1
+ {
2
+ "name": "devez-vibe",
3
+ "version": "1.6.59",
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
+ "skills/luna-loop/",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "os": [
32
+ "win32"
33
+ ],
34
+ "cpu": [
35
+ "x64"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "scripts": {
41
+ "postinstall": "node install-skills.mjs"
42
+ },
43
+ "dependencies": {
44
+ "@anthropic-ai/claude-agent-sdk": "0.3.251"
45
+ }
46
+ }