devez-vibe 0.1.43 → 0.1.45

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
@@ -7,10 +7,10 @@ import {
7
7
  deleteSession,
8
8
  forkSession,
9
9
  getSessionInfo,
10
- getSessionMessages,
11
- listSessions,
12
- query,
13
- } from "@anthropic-ai/claude-agent-sdk";
10
+ getSessionMessages,
11
+ listSessions,
12
+ startup,
13
+ } from "@anthropic-ai/claude-agent-sdk";
14
14
 
15
15
  const VERSION = process.env.DEVEZ_VIBE_VERSION || "dev";
16
16
  const sessions = new Map();
@@ -82,13 +82,23 @@ function hostRequest(method, params, signal) {
82
82
  });
83
83
  }
84
84
 
85
- function sanitizedEnvironment() {
85
+ function sanitizedEnvironment() {
86
86
  const env = { ...process.env };
87
87
  delete env.ANTHROPIC_API_KEY;
88
88
  delete env.ANTHROPIC_AUTH_TOKEN;
89
89
  env.CLAUDE_AGENT_SDK_CLIENT_APP = `devez-vibe/${VERSION}`;
90
- return env;
91
- }
90
+ return env;
91
+ }
92
+
93
+ function applyClaudeExecutable(options, params) {
94
+ const executable = String(params.claudePath || "").trim();
95
+ if (!executable) return;
96
+ // On Windows use the SDK's matching native CLI bundle. It reads the same
97
+ // ~/.claude credentials, settings, hooks, skills, and plugins, while avoiding
98
+ // EINVAL from command shims and incompatible separately-installed CLI builds.
99
+ if (process.platform === "win32") return;
100
+ options.pathToClaudeCodeExecutable = executable;
101
+ }
92
102
 
93
103
  function modelCapabilities(models, model) {
94
104
  const value = stripClaudeModel(model);
@@ -125,7 +135,7 @@ function compactClaudeModelName(model) {
125
135
  return fallback || clean(model.displayName || model.resolvedModel || model.value);
126
136
  }
127
137
 
128
- function catalogEntry(model) {
138
+ function catalogEntry(model, defaultResolvedModel) {
129
139
  const value = String(model.value || "");
130
140
  const resolved = String(model.resolvedModel || value);
131
141
  const efforts = model.supportsEffort && Array.isArray(model.supportedEffortLevels)
@@ -138,7 +148,7 @@ function catalogEntry(model) {
138
148
  displayName: compactClaudeModelName(model),
139
149
  defaultReasoningEffort: efforts.includes("high") ? "high" : efforts.at(-1) || "",
140
150
  supportedReasoningEfforts: efforts.map((reasoningEffort) => ({ reasoningEffort })),
141
- isDefault: false,
151
+ isDefault: Boolean(defaultResolvedModel) && resolved === defaultResolvedModel,
142
152
  ...(contextWindow > 0 ? { contextWindow } : {}),
143
153
  };
144
154
  }
@@ -156,18 +166,21 @@ async function loadModelCatalog(params) {
156
166
  env: sanitizedEnvironment(),
157
167
  stderr: (data) => process.stderr.write(data),
158
168
  };
159
- if (params.claudePath) options.pathToClaudeCodeExecutable = params.claudePath;
160
- const agentQuery = query({ prompt: input, options });
169
+ applyClaudeExecutable(options, params);
170
+ const agentQuery = await startAgentQuery(input, options);
161
171
  const consumer = (async () => {
162
172
  try { for await (const _message of agentQuery) { /* initialization only */ } }
163
173
  catch { /* the caller receives the supportedModels error */ }
164
174
  })();
165
175
  try {
166
- const models = await agentQuery.supportedModels();
167
- return {
168
- data: models
169
- .filter((model) => model.value && model.value !== "default")
170
- .map(catalogEntry),
176
+ const models = await agentQuery.supportedModels();
177
+ const defaultResolvedModel = String(
178
+ models.find((model) => model.value === "default")?.resolvedModel || "",
179
+ );
180
+ return {
181
+ data: models
182
+ .filter((model) => model.value && model.value !== "default")
183
+ .map((model) => catalogEntry(model, defaultResolvedModel)),
171
184
  };
172
185
  } finally {
173
186
  input.close();
@@ -202,7 +215,7 @@ function rawSession(id) {
202
215
  return id.startsWith("claude:") ? id.slice("claude:".length) : id;
203
216
  }
204
217
 
205
- function makeOptions(params, sessionId, resume) {
218
+ function makeOptions(params, sessionId, resume) {
206
219
  const options = {
207
220
  cwd: params.cwd || process.cwd(),
208
221
  includePartialMessages: true,
@@ -223,13 +236,23 @@ function makeOptions(params, sessionId, resume) {
223
236
  const model = stripClaudeModel(params.model);
224
237
  if (model) options.model = model;
225
238
  if (params.effort) options.effort = params.effort;
226
- if (params.claudePath) options.pathToClaudeCodeExecutable = params.claudePath;
239
+ applyClaudeExecutable(options, params);
227
240
  if (resume) options.resume = resume;
228
241
  else options.sessionId = sessionId;
229
242
  options.canUseTool = (toolName, input, permission) =>
230
243
  requestToolPermission(toolName, input, permission);
231
- return options;
232
- }
244
+ return options;
245
+ }
246
+
247
+ async function startAgentQuery(prompt, options) {
248
+ const warm = await startup({ options });
249
+ try {
250
+ return warm.query(prompt);
251
+ } catch (error) {
252
+ warm.close();
253
+ throw error;
254
+ }
255
+ }
233
256
 
234
257
  async function requestToolPermission(toolName, input, permission) {
235
258
  if (toolName === "AskUserQuestion") {
@@ -317,10 +340,7 @@ async function createSession(params, resumeId) {
317
340
  tools: new Map(),
318
341
  tasks: new Map(),
319
342
  };
320
- const agentQuery = query({
321
- prompt: queue,
322
- options: makeOptions(params, id, resumeId),
323
- });
343
+ const agentQuery = await startAgentQuery(queue, makeOptions(params, id, resumeId));
324
344
  session.query = agentQuery;
325
345
  sessions.set(id, session);
326
346
  const consumer = consume(session).catch((error) => {
@@ -333,7 +353,16 @@ async function createSession(params, resumeId) {
333
353
  if (session.turn) finishTurn(session, error);
334
354
  });
335
355
  session.consumer = consumer;
336
- const initialization = await agentQuery.initializationResult();
356
+ let initialization;
357
+ try {
358
+ initialization = await agentQuery.initializationResult();
359
+ } catch (error) {
360
+ sessions.delete(id);
361
+ queue.close();
362
+ agentQuery.close();
363
+ await Promise.race([consumer, new Promise((resolve) => setTimeout(resolve, 1000))]);
364
+ throw error;
365
+ }
337
366
  session.models = Array.isArray(initialization.models) ? initialization.models : [];
338
367
  session.effort = supportedEffort(modelCapabilities(session.models, params.model), params.effort);
339
368
  const account = initialization.account || await safeAccount(agentQuery);
@@ -644,8 +673,34 @@ async function consume(session) {
644
673
  }
645
674
  }
646
675
 
647
- async function inputContent(input) {
648
- const content = [];
676
+ const HANDOFF_HEADER = '<devez_provider_handoff chars="';
677
+ const HANDOFF_SEPARATOR = '">\n';
678
+ const HANDOFF_FOOTER = "\n</devez_provider_handoff>\n\n";
679
+
680
+ function prependHandoff(content, handoffContext) {
681
+ if (!handoffContext) return content;
682
+ const handoff = String(handoffContext);
683
+ const prefix = `${HANDOFF_HEADER}${handoff.length}${HANDOFF_SEPARATOR}${handoff}${HANDOFF_FOOTER}`;
684
+ const firstText = content.find((item) => item.type === "text");
685
+ if (firstText) firstText.text = `${prefix}${firstText.text || ""}`;
686
+ else content.unshift({ type: "text", text: prefix });
687
+ return content;
688
+ }
689
+
690
+ function stripHandoff(text) {
691
+ if (!text.startsWith(HANDOFF_HEADER)) return text;
692
+ const separator = text.indexOf(HANDOFF_SEPARATOR, HANDOFF_HEADER.length);
693
+ if (separator < 0) return text;
694
+ const length = Number(text.slice(HANDOFF_HEADER.length, separator));
695
+ if (!Number.isSafeInteger(length) || length < 0) return text;
696
+ const contextStart = separator + HANDOFF_SEPARATOR.length;
697
+ const contextEnd = contextStart + length;
698
+ if (text.slice(contextEnd, contextEnd + HANDOFF_FOOTER.length) !== HANDOFF_FOOTER) return text;
699
+ return text.slice(contextEnd + HANDOFF_FOOTER.length);
700
+ }
701
+
702
+ async function inputContent(input, handoffContext) {
703
+ const content = [];
649
704
  for (const item of Array.isArray(input) ? input : []) {
650
705
  if (item.type === "text") content.push({ type: "text", text: item.text || "" });
651
706
  else if (item.type === "localImage" && item.path) {
@@ -658,7 +713,7 @@ async function inputContent(input) {
658
713
  content.push({ type: "image", source: { type: "base64", media_type: mediaType, data: bytes.toString("base64") } });
659
714
  }
660
715
  }
661
- return content.length ? content : [{ type: "text", text: "" }];
716
+ return prependHandoff(content.length ? content : [{ type: "text", text: "" }], handoffContext);
662
717
  }
663
718
 
664
719
  async function startPrompt(params) {
@@ -679,9 +734,9 @@ async function startPrompt(params) {
679
734
  const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
680
735
  session.turn = { id: turnId, sawStreamText: false };
681
736
  notify("turn/started", { threadId: id, turn: { id: turnId } });
682
- session.queue.push({
683
- type: "user",
684
- message: { role: "user", content: await inputContent(params.input) },
737
+ session.queue.push({
738
+ type: "user",
739
+ message: { role: "user", content: await inputContent(params.input, params.handoffContext) },
685
740
  parent_tool_use_id: null,
686
741
  session_id: id,
687
742
  origin: { kind: "human" },
@@ -702,8 +757,8 @@ function historyTurns(messages) {
702
757
  const tasks = new Map();
703
758
  for (const message of messages) {
704
759
  const blocks = contentBlocks(message.message);
705
- const userText = message.type === "user"
706
- ? blocks.filter((block) => block.type === "text").map((block) => block.text || "").join("\n")
760
+ const userText = message.type === "user"
761
+ ? stripHandoff(blocks.filter((block) => block.type === "text").map((block) => block.text || "").join("\n"))
707
762
  : "";
708
763
  if (userText && !blocks.some((block) => block.type === "tool_result")) {
709
764
  turn = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",