wispy-cli 1.1.1 → 1.1.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/core/engine.mjs CHANGED
@@ -1009,6 +1009,9 @@ export class WispyEngine {
1009
1009
 
1010
1010
  destroy() {
1011
1011
  try { this.mcpManager.disconnectAll(); } catch {}
1012
+ try { this.sessions.destroyAll?.(); } catch {}
1013
+ try { this.subagents.killAll?.(); } catch {}
1014
+ try { this.audit.close?.(); } catch {}
1012
1015
  }
1013
1016
  }
1014
1017
 
@@ -345,7 +345,7 @@ export class ProviderRegistry {
345
345
  if (this._provider === "openrouter") headers["HTTP-Referer"] = "https://wispy.dev";
346
346
 
347
347
  const supportsTools = !["ollama"].includes(this._provider);
348
- const body = { model, messages: openaiMessages, temperature: 0.7, max_tokens: 4096 };
348
+ const body = { model, messages: openaiMessages, temperature: 0.7, max_tokens: 4096, stream: true };
349
349
  if (supportsTools && tools.length > 0) {
350
350
  body.tools = tools.map(t => ({
351
351
  type: "function",
@@ -364,24 +364,65 @@ export class ProviderRegistry {
364
364
  throw new Error(`OpenAI API error ${response.status}: ${err.slice(0, 300)}`);
365
365
  }
366
366
 
367
- const data = await response.json();
368
- const choice = data.choices?.[0];
369
- if (!choice) throw new Error("No response from OpenAI");
367
+ const reader = response.body.getReader();
368
+ const decoder = new TextDecoder();
369
+ let buffer = "";
370
+ let fullText = "";
371
+ const toolCallsMap = {};
372
+
373
+ while (true) {
374
+ const { done, value } = await reader.read();
375
+ if (done) break;
376
+
377
+ buffer += decoder.decode(value, { stream: true });
378
+ const lines = buffer.split("\n");
379
+ buffer = lines.pop() ?? "";
380
+
381
+ for (const line of lines) {
382
+ if (!line.startsWith("data: ")) continue;
383
+ const data = line.slice(6).trim();
384
+ if (!data || data === "[DONE]") continue;
385
+
386
+ try {
387
+ const event = JSON.parse(data);
388
+ const delta = event.choices?.[0]?.delta;
389
+ if (!delta) continue;
390
+
391
+ // Text content
392
+ if (delta.content) {
393
+ fullText += delta.content;
394
+ opts.onChunk?.(delta.content);
395
+ }
396
+
397
+ // Tool calls (streamed as incremental chunks)
398
+ if (delta.tool_calls) {
399
+ for (const tc of delta.tool_calls) {
400
+ const idx = tc.index ?? 0;
401
+ if (!toolCallsMap[idx]) {
402
+ toolCallsMap[idx] = { id: tc.id ?? `call_${idx}`, name: "", arguments: "" };
403
+ }
404
+ if (tc.id) toolCallsMap[idx].id = tc.id;
405
+ if (tc.function?.name) toolCallsMap[idx].name += tc.function.name;
406
+ if (tc.function?.arguments) toolCallsMap[idx].arguments += tc.function.arguments;
407
+ }
408
+ }
409
+ } catch { /* skip malformed chunks */ }
410
+ }
411
+ }
370
412
 
371
- if (choice.message?.tool_calls?.length > 0) {
372
- const calls = choice.message.tool_calls.map(tc => ({
413
+ const toolCalls = Object.values(toolCallsMap);
414
+ if (toolCalls.length > 0) {
415
+ const calls = toolCalls.map(tc => ({
373
416
  id: tc.id,
374
- name: tc.function.name,
375
- args: JSON.parse(tc.function.arguments),
417
+ name: tc.name,
418
+ args: (() => { try { return JSON.parse(tc.arguments); } catch { return {}; } })(),
376
419
  }));
377
420
  this._sessionTokens.output += this._estimateTokens(JSON.stringify(calls));
378
421
  return { type: "tool_calls", calls };
379
422
  }
380
423
 
381
- const text = choice.message?.content ?? "";
382
- this._sessionTokens.output += this._estimateTokens(text);
383
- opts.onChunk?.(text);
384
- return { type: "text", text };
424
+ this._sessionTokens.output += this._estimateTokens(fullText);
425
+ return { type: "text", text: fullText };
385
426
  }
386
427
 
387
428
  formatCost() {
package/core/tools.mjs CHANGED
@@ -337,10 +337,18 @@ export class ToolRegistry {
337
337
  try {
338
338
  const serverUrl = `http://127.0.0.1:${DEFAULT_SERVER_PORT}`;
339
339
  if (name === "read_file") {
340
+ // Resolve path relative to process.cwd() before sending to server
341
+ let resolvedPath = args.path.replace(/^~/, os.homedir());
342
+ if (!path.isAbsolute(resolvedPath)) {
343
+ resolvedPath = path.resolve(process.cwd(), resolvedPath);
344
+ }
345
+ if (process.env.WISPY_DEBUG) {
346
+ process.stderr.write(`[wispy] read_file via server: "${args.path}" → "${resolvedPath}"\n`);
347
+ }
340
348
  const resp = await fetch(`${serverUrl}/api/node-filesystem-actions`, {
341
349
  method: "POST",
342
350
  headers: { "Content-Type": "application/json" },
343
- body: JSON.stringify({ subAction: "read_file", path: args.path }),
351
+ body: JSON.stringify({ subAction: "read_file", path: resolvedPath }),
344
352
  signal: AbortSignal.timeout(10_000),
345
353
  });
346
354
  const data = await resp.json();
@@ -386,7 +394,13 @@ export class ToolRegistry {
386
394
  try {
387
395
  switch (name) {
388
396
  case "read_file": {
389
- const filePath = args.path.replace(/^~/, os.homedir());
397
+ let filePath = args.path.replace(/^~/, os.homedir());
398
+ if (!path.isAbsolute(filePath)) {
399
+ filePath = path.resolve(process.cwd(), filePath);
400
+ }
401
+ if (process.env.WISPY_DEBUG) {
402
+ process.stderr.write(`[wispy] read_file: "${args.path}" → "${filePath}"\n`);
403
+ }
390
404
  const content = await readFile(filePath, "utf8");
391
405
  const truncated = content.length > 10_000
392
406
  ? content.slice(0, 10_000) + `\n\n... (truncated, ${content.length} chars total)`
@@ -395,7 +409,10 @@ export class ToolRegistry {
395
409
  }
396
410
 
397
411
  case "write_file": {
398
- const filePath = args.path.replace(/^~/, os.homedir());
412
+ let filePath = args.path.replace(/^~/, os.homedir());
413
+ if (!path.isAbsolute(filePath)) {
414
+ filePath = path.resolve(process.cwd(), filePath);
415
+ }
399
416
  const dir = path.dirname(filePath);
400
417
  await mkdir(dir, { recursive: true });
401
418
  await writeFile(filePath, args.content, "utf8");
@@ -453,7 +470,10 @@ export class ToolRegistry {
453
470
  }
454
471
 
455
472
  case "file_edit": {
456
- const filePath = args.path.replace(/^~/, os.homedir());
473
+ let filePath = args.path.replace(/^~/, os.homedir());
474
+ if (!path.isAbsolute(filePath)) {
475
+ filePath = path.resolve(process.cwd(), filePath);
476
+ }
457
477
  const content = await readFile(filePath, "utf8");
458
478
  if (!content.includes(args.old_text)) {
459
479
  return { success: false, error: `Text not found in ${filePath}` };
@@ -865,8 +865,11 @@ async function runOneShot(engine, message) {
865
865
  } else {
866
866
  console.error(red(`\n❌ ${err.message.slice(0, 200)}`));
867
867
  }
868
+ engine.destroy();
868
869
  process.exit(1);
869
870
  }
871
+ engine.destroy();
872
+ process.exit(0);
870
873
  }
871
874
 
872
875
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "🌿 Wispy — AI workspace assistant with multi-agent orchestration and multi-channel bot support",
5
5
  "license": "MIT",
6
6
  "author": "Minseo & Poropo",