@questionbase/deskfree 0.4.3 → 0.4.5

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/dist/index.d.ts CHANGED
@@ -26,6 +26,8 @@ interface OrchestratorQueryOptions {
26
26
  model: string;
27
27
  /** Agent SDK session ID to resume (for multi-turn conversations). */
28
28
  sessionId?: string;
29
+ /** Path to user's Claude Code executable (uses bundled CLI if omitted). */
30
+ claudeCodePath?: string;
29
31
  }
30
32
  /**
31
33
  * Run the orchestrator agent. Returns an async iterable of SDKMessage events.
@@ -40,6 +42,8 @@ interface HeartbeatOptions {
40
42
  prompt: string;
41
43
  orchestratorServer: McpSdkServerConfigWithInstance;
42
44
  model: string;
45
+ /** Path to user's Claude Code executable (uses bundled CLI if omitted). */
46
+ claudeCodePath?: string;
43
47
  }
44
48
  /**
45
49
  * Run a one-shot heartbeat query (no session persistence).
@@ -390,6 +394,8 @@ interface RuntimeConfig extends LocalConfig {
390
394
  duskHour: number | null;
391
395
  /** IANA timezone string (e.g. 'America/New_York') for sleep scheduling */
392
396
  timezone: string | null;
397
+ /** Absolute path to the user's Claude Code executable (resolved for 'claude-code' provider) */
398
+ claudeCodePath: string | undefined;
393
399
  }
394
400
  /**
395
401
  * Load local config from environment variables.
@@ -401,6 +407,10 @@ declare function loadConfig(): LocalConfig;
401
407
  /**
402
408
  * Merge local config with API-bootstrapped config.
403
409
  * Env var overrides take precedence over API values for debugging/testing.
410
+ *
411
+ * When running multiple agents on the same machine, stateDir and toolsDir
412
+ * are automatically namespaced by botId (unless explicitly overridden via
413
+ * DESKFREE_STATE_DIR / DESKFREE_TOOLS_DIR env vars).
404
414
  */
405
415
  declare function mergeWithRemoteConfig(local: LocalConfig, remote: RuntimeBootstrapConfig): RuntimeConfig;
406
416
 
package/dist/index.js CHANGED
@@ -3,9 +3,9 @@ import { query, tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk'
3
3
  import { createRequire as createRequire$1 } from 'module';
4
4
  import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync, createWriteStream } from 'fs';
5
5
  import { join, dirname, extname } from 'path';
6
+ import { execFileSync, execFile } from 'child_process';
6
7
  import { z } from 'zod';
7
8
  import { appendFile, readFile, mkdir, unlink } from 'fs/promises';
8
- import { execFile } from 'child_process';
9
9
  import { randomUUID } from 'crypto';
10
10
  import { Readable } from 'stream';
11
11
  import { pipeline } from 'stream/promises';
@@ -11295,18 +11295,17 @@ var DISALLOWED_BUILTIN_TOOLS = [
11295
11295
  "Agent"
11296
11296
  ];
11297
11297
  function runOrchestrator(opts) {
11298
- const { prompt, orchestratorServer, model, sessionId } = opts;
11298
+ const { prompt, orchestratorServer, model, sessionId, claudeCodePath } = opts;
11299
11299
  return query({
11300
11300
  prompt,
11301
11301
  options: {
11302
- debug: true,
11303
- debugFile: "/dev/stderr",
11304
11302
  stderr: (data) => {
11305
11303
  process.stderr.write(`[orchestrator-sdk] ${data}
11306
11304
  `);
11307
11305
  },
11308
11306
  systemPrompt: DESKFREE_AGENT_DIRECTIVE,
11309
11307
  model,
11308
+ ...claudeCodePath ? { pathToClaudeCodeExecutable: claudeCodePath } : {},
11310
11309
  maxTurns: MAX_ORCHESTRATOR_TURNS,
11311
11310
  permissionMode: "bypassPermissions",
11312
11311
  allowDangerouslySkipPermissions: true,
@@ -11334,12 +11333,13 @@ function runOrchestrator(opts) {
11334
11333
  });
11335
11334
  }
11336
11335
  function runHeartbeat(opts) {
11337
- const { prompt, orchestratorServer, model } = opts;
11336
+ const { prompt, orchestratorServer, model, claudeCodePath } = opts;
11338
11337
  return query({
11339
11338
  prompt,
11340
11339
  options: {
11341
11340
  systemPrompt: DESKFREE_AGENT_DIRECTIVE,
11342
11341
  model,
11342
+ ...claudeCodePath ? { pathToClaudeCodeExecutable: claudeCodePath } : {},
11343
11343
  maxTurns: MAX_ORCHESTRATOR_TURNS,
11344
11344
  permissionMode: "bypassPermissions",
11345
11345
  allowDangerouslySkipPermissions: true,
@@ -11450,8 +11450,27 @@ function loadConfig() {
11450
11450
  };
11451
11451
  }
11452
11452
  function mergeWithRemoteConfig(local, remote) {
11453
+ const stateDirOverridden = !!process.env["DESKFREE_STATE_DIR"];
11454
+ const toolsDirOverridden = !!process.env["DESKFREE_TOOLS_DIR"];
11455
+ const stateDir = stateDirOverridden ? local.stateDir : isDocker ? local.stateDir : `.deskfree/${remote.botId}/state`;
11456
+ const toolsDir = toolsDirOverridden ? local.toolsDir : isDocker ? local.toolsDir : `.deskfree/${remote.botId}/tools`;
11457
+ let claudeCodePath;
11458
+ if (remote.provider === "claude-code") {
11459
+ try {
11460
+ claudeCodePath = execFileSync("which", ["claude"], {
11461
+ encoding: "utf8"
11462
+ }).trim();
11463
+ } catch {
11464
+ throw new Error(
11465
+ 'Provider is "claude-code" but the `claude` CLI was not found on PATH. Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code'
11466
+ );
11467
+ }
11468
+ }
11453
11469
  return {
11454
11470
  ...local,
11471
+ stateDir,
11472
+ toolsDir,
11473
+ claudeCodePath,
11455
11474
  wsUrl: process.env["DESKFREE_WS_URL"] ?? remote.wsUrl,
11456
11475
  model: process.env["DESKFREE_MODEL"] ?? remote.model,
11457
11476
  awsRegion: process.env["AWS_REGION"] ?? remote.awsRegion,
@@ -12860,7 +12879,8 @@ async function routeMessage(message, client, deps, sessionStore, config) {
12860
12879
  prompt,
12861
12880
  orchestratorServer: deps.createOrchestratorServer(),
12862
12881
  model: deps.model,
12863
- sessionId: existingSessionId
12882
+ sessionId: existingSessionId,
12883
+ claudeCodePath: deps.claudeCodePath
12864
12884
  });
12865
12885
  let fullText = "";
12866
12886
  let capturedSessionId = null;
@@ -13404,7 +13424,7 @@ ${userMessage}
13404
13424
  try {
13405
13425
  await client.reportUsage({
13406
13426
  taskId,
13407
- estimatedCost: result.total_cost_usd
13427
+ estimatedCost: Math.round(result.total_cost_usd * 1e6) / 1e6
13408
13428
  });
13409
13429
  } catch {
13410
13430
  log.warn(`Failed to report usage for task ${taskId}`);
@@ -13538,7 +13558,16 @@ function startHealthServer(port, log) {
13538
13558
  log.info(`Health server listening on port ${port}`);
13539
13559
  });
13540
13560
  server.on("error", (err) => {
13541
- log.warn(`Health server error: ${err.message}`);
13561
+ if (err.code === "EADDRINUSE") {
13562
+ log.info(`Port ${port} in use, requesting OS-assigned port...`);
13563
+ server.listen(0, () => {
13564
+ const addr = server.address();
13565
+ const assignedPort = typeof addr === "object" && addr ? addr.port : "unknown";
13566
+ log.info(`Health server listening on port ${assignedPort}`);
13567
+ });
13568
+ } else {
13569
+ log.warn(`Health server error: ${err.message}`);
13570
+ }
13542
13571
  });
13543
13572
  return {
13544
13573
  close() {
@@ -13546,7 +13575,7 @@ function startHealthServer(port, log) {
13546
13575
  }
13547
13576
  };
13548
13577
  }
13549
- function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log) {
13578
+ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, claudeCodePath) {
13550
13579
  if (intervalMs <= 0) return;
13551
13580
  let running = false;
13552
13581
  async function tick() {
@@ -13557,7 +13586,8 @@ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log) {
13557
13586
  const result = runHeartbeat({
13558
13587
  prompt: DESKFREE_HEARTBEAT_DIRECTIVE,
13559
13588
  orchestratorServer: createOrchServer(),
13560
- model
13589
+ model,
13590
+ claudeCodePath
13561
13591
  });
13562
13592
  for await (const _ of result) {
13563
13593
  }
@@ -13579,8 +13609,6 @@ async function startAgent(opts) {
13579
13609
  const log = opts?.log ?? createLogger("agent", localConfig.logLevel);
13580
13610
  const abortController = new AbortController();
13581
13611
  log.info("DeskFree Agent Runtime starting...");
13582
- mkdirSync(localConfig.stateDir, { recursive: true });
13583
- mkdirSync(localConfig.toolsDir, { recursive: true });
13584
13612
  const client = new DeskFreeClient(localConfig.botToken, localConfig.apiUrl);
13585
13613
  const errorReporter = initErrorReporter(client, log);
13586
13614
  initializeHealth("unknown");
@@ -13593,12 +13621,15 @@ async function startAgent(opts) {
13593
13621
  wsUrl: config.wsUrl,
13594
13622
  model: config.model,
13595
13623
  region: config.awsRegion,
13596
- tools: config.tools.length
13624
+ tools: config.tools.length,
13625
+ botId: config.botId
13597
13626
  });
13598
13627
  } catch (err) {
13599
13628
  const msg = err instanceof Error ? err.message : String(err);
13600
13629
  throw new Error(`Failed to bootstrap config from API: ${msg}`);
13601
13630
  }
13631
+ mkdirSync(config.stateDir, { recursive: true });
13632
+ mkdirSync(config.toolsDir, { recursive: true });
13602
13633
  if (config.tools.length > 0) {
13603
13634
  log.info(`Installing ${config.tools.length} tool package(s)...`);
13604
13635
  await installTools(config.tools, config.toolsDir, log);
@@ -13679,7 +13710,8 @@ async function startAgent(opts) {
13679
13710
  {
13680
13711
  createOrchestratorServer: createOrchServer,
13681
13712
  workerManager,
13682
- model: config.model
13713
+ model: config.model,
13714
+ claudeCodePath: config.claudeCodePath
13683
13715
  },
13684
13716
  sessionStore,
13685
13717
  {
@@ -13694,7 +13726,8 @@ async function startAgent(opts) {
13694
13726
  config.model,
13695
13727
  config.heartbeatIntervalMs,
13696
13728
  abortController.signal,
13697
- log
13729
+ log,
13730
+ config.claudeCodePath
13698
13731
  );
13699
13732
  if (config.memoryFileId && config.sleepHour !== null && config.timezone) {
13700
13733
  const memoryFileId = config.memoryFileId;