devez-vibe 1.2.0 → 1.2.2

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
@@ -104,6 +104,21 @@ function applyClaudeExecutable(options, params) {
104
104
  options.pathToClaudeCodeExecutable = executable;
105
105
  }
106
106
 
107
+ // The SDK model list carries no window size, so the status line would stay
108
+ // blank until a turn reports one — and a fresh session has no turn yet. Claude
109
+ // ships a 200k window, and the `[1m]` variants a 1M one.
110
+ function claudeContextWindow(...names) {
111
+ const oneMillion = names.some((name) => String(name || "").includes("[1m]"));
112
+ return oneMillion ? 1_000_000 : 200_000;
113
+ }
114
+
115
+ function capabilityContextWindow(capabilities, ...names) {
116
+ const reported = Number(capabilities?.contextWindow || capabilities?.contextWindowSize || 0);
117
+ return reported > 0
118
+ ? reported
119
+ : claudeContextWindow(...names, capabilities?.value, capabilities?.resolvedModel);
120
+ }
121
+
107
122
  function modelCapabilities(models, model) {
108
123
  const value = stripClaudeModel(model);
109
124
  return models.find((candidate) => candidate.value === value || candidate.resolvedModel === value)
@@ -145,7 +160,7 @@ function catalogEntry(model, defaultResolvedModel) {
145
160
  const efforts = model.supportsEffort && Array.isArray(model.supportedEffortLevels)
146
161
  ? model.supportedEffortLevels
147
162
  : [];
148
- const contextWindow = Number(model.contextWindow || model.contextWindowSize || 0);
163
+ const contextWindow = capabilityContextWindow(model, value, resolved);
149
164
  return {
150
165
  id: visibleModel(resolved),
151
166
  model: visibleModel(value),
@@ -251,17 +266,46 @@ function adoptSessionId(session, incoming) {
251
266
  sessionAliases.set(previous, real);
252
267
  session.id = real;
253
268
  sessions.set(real, session);
254
- notify("thread/rebound", {
269
+ notify("claude/session/rebound", {
255
270
  threadId: visibleSession(previous),
256
271
  newThreadId: visibleSession(real),
257
272
  });
258
273
  }
259
274
 
275
+ const PERMISSION_MODES = ["default", "acceptEdits", "plan", "auto", "bypassPermissions"];
276
+
277
+ function permissionMode(requested, fallback = "default") {
278
+ const mode = String(requested || "");
279
+ return PERMISSION_MODES.includes(mode) ? mode : fallback;
280
+ }
281
+
282
+ // Moves a live session onto a mode the badge picked. A rejected mode — policy
283
+ // disables bypass, say — leaves the session on the one it already had.
284
+ async function applyPermissionMode(session, requested) {
285
+ const mode = permissionMode(requested, session.permissionMode || "default");
286
+ if (mode === session.permissionMode) return;
287
+ try {
288
+ await session.query.setPermissionMode(mode);
289
+ session.permissionMode = mode;
290
+ } catch (error) {
291
+ notify("claude/permissionMode/rejected", {
292
+ threadId: visibleSession(session.id),
293
+ permissionMode: mode,
294
+ message: error?.message || String(error),
295
+ });
296
+ }
297
+ }
298
+
260
299
  function makeOptions(params, sessionId, resume) {
261
300
  const options = {
262
301
  cwd: params.cwd || process.cwd(),
263
302
  includePartialMessages: true,
264
- permissionMode: "default",
303
+ permissionMode: permissionMode(params.permissionMode),
304
+ // Not a mode, a capability: the SDK refuses `bypassPermissions` outright
305
+ // unless the session was started with this. Devez Vibe already auto-allows
306
+ // tools through `canUseTool`, so allowing the mode to be *reachable* grants
307
+ // nothing the session did not already have.
308
+ allowDangerouslySkipPermissions: true,
265
309
  enableFileCheckpointing: true,
266
310
  persistSession: true,
267
311
  settingSources: ["user", "project", "local"],
@@ -326,9 +370,13 @@ async function requestToolPermission(toolName, input, permission) {
326
370
  return { behavior: "allow", updatedInput: { ...input, answers } };
327
371
  }
328
372
 
373
+ // Plan mode only means anything if leaving it is the user's call — the CLI
374
+ // shows the plan and waits. The blanket allow below would answer for them.
375
+ const planApproval = toolName === "ExitPlanMode";
376
+
329
377
  // Devez Vibe runs in its existing full-access profile. Claude's callback is
330
378
  // still kept so AskUserQuestion and explicit user-authored ask rules reach UI.
331
- if (!permission.matchedAskRule) {
379
+ if (!permission.matchedAskRule && !planApproval) {
332
380
  return { behavior: "allow", updatedInput: input };
333
381
  }
334
382
 
@@ -337,7 +385,12 @@ async function requestToolPermission(toolName, input, permission) {
337
385
  reason: permission.decisionReason || permission.description || permission.title,
338
386
  permissions: { tool: toolName, blockedPath: permission.blockedPath },
339
387
  };
340
- if (toolName === "Bash") {
388
+ if (planApproval) {
389
+ params = {
390
+ reason: input.plan || permission.description || "계획대로 진행할까요?",
391
+ permissions: { tool: toolName },
392
+ };
393
+ } else if (toolName === "Bash") {
341
394
  method = "item/commandExecution/requestApproval";
342
395
  params = {
343
396
  command: input.command || "command",
@@ -372,6 +425,7 @@ async function createSession(params, resumeId) {
372
425
  cwd: params.cwd || process.cwd(),
373
426
  model: visibleModel(params.model),
374
427
  effort: params.effort || "",
428
+ permissionMode: permissionMode(params.permissionMode),
375
429
  models: [],
376
430
  queue,
377
431
  query: null,
@@ -476,7 +530,7 @@ function historyTokenUsage(messages, models, model) {
476
530
  }
477
531
  if (!counted) return null;
478
532
  const capabilities = modelCapabilities(models, model);
479
- const contextWindow = Number(capabilities?.contextWindow || capabilities?.contextWindowSize || 0);
533
+ const contextWindow = capabilityContextWindow(capabilities, model);
480
534
  return {
481
535
  total,
482
536
  ...(last ? { last } : {}),
@@ -535,8 +589,10 @@ function processAssistant(session, message) {
535
589
  session.models,
536
590
  message.message?.model || session.model,
537
591
  );
538
- session.lastContextWindow = Number(
539
- capabilities?.contextWindow || capabilities?.contextWindowSize || 0,
592
+ session.lastContextWindow = capabilityContextWindow(
593
+ capabilities,
594
+ message.message?.model,
595
+ session.model,
540
596
  );
541
597
  const content = Array.isArray(message.message?.content) ? message.message.content : [];
542
598
  for (const block of content) {
@@ -861,7 +917,9 @@ async function processResult(session, message) {
861
917
  tokenUsage: {
862
918
  total: totals,
863
919
  ...(session.lastContextUsage ? { last: session.lastContextUsage } : {}),
864
- modelContextWindow: session.lastContextWindow || totals.contextWindow || undefined,
920
+ // `modelUsage` reports the window the turn actually ran under, so it wins
921
+ // over the size guessed from the model name.
922
+ modelContextWindow: totals.contextWindow || session.lastContextWindow || undefined,
865
923
  },
866
924
  });
867
925
  const error = message.is_error && !interrupted
@@ -994,6 +1052,7 @@ async function runPrompt(session, params) {
994
1052
  await session.query.applyFlagSettings({ effortLevel: effort });
995
1053
  }
996
1054
  session.effort = effort;
1055
+ await applyPermissionMode(session, params.permissionMode);
997
1056
  const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
998
1057
  session.turn = { id: turnId, sawStreamText: false };
999
1058
  session.lastContextUsage = null;
@@ -1116,6 +1175,13 @@ function historyTurns(messages) {
1116
1175
 
1117
1176
  async function dispatch(method, params = {}) {
1118
1177
  if (method === "model/list") return loadModelCatalog(params);
1178
+ if (method === "session/permissionMode") {
1179
+ const session = lookupSession(params.sessionId);
1180
+ // A session that has not started yet picks the mode up from its first turn.
1181
+ if (!session) return { permissionMode: permissionMode(params.permissionMode) };
1182
+ await applyPermissionMode(session, params.permissionMode);
1183
+ return { permissionMode: session.permissionMode };
1184
+ }
1119
1185
  if (method === "session/start") {
1120
1186
  const { session, account, usage } = await createSession(params);
1121
1187
  return {
@@ -1300,6 +1366,14 @@ function runSelfTest() {
1300
1366
  if (usage.totalTokens !== 68_802 || usage.inputTokens !== 68_502) {
1301
1367
  throw new Error(`Claude usage self-test failed: ${JSON.stringify(usage)}`);
1302
1368
  }
1369
+ const windows = [
1370
+ catalogEntry({ value: "opus[1m]", resolvedModel: "claude-opus-5[1m]" }, "").contextWindow,
1371
+ catalogEntry({ value: "sonnet", resolvedModel: "claude-sonnet-5" }, "").contextWindow,
1372
+ catalogEntry({ value: "haiku", resolvedModel: "x", contextWindow: 300_000 }, "").contextWindow,
1373
+ ];
1374
+ if (windows.join(",") !== "1000000,200000,300000") {
1375
+ throw new Error(`Claude context window self-test failed: ${windows.join(",")}`);
1376
+ }
1303
1377
  process.stdout.write("Claude bridge self-test passed\n");
1304
1378
  }
1305
1379
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",