@questionbase/deskfree 0.4.4 → 0.4.7

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/bin.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { execSync, execFileSync, execFile } from 'child_process';
4
- import { writeFileSync, chmodSync, existsSync, unlinkSync, mkdirSync, readdirSync, readFileSync, createWriteStream } from 'fs';
4
+ import { mkdirSync, writeFileSync, chmodSync, existsSync, unlinkSync, appendFileSync, readdirSync, readFileSync, statSync, createWriteStream } from 'fs';
5
+ import { homedir } from 'os';
6
+ import { dirname, join, extname } from 'path';
5
7
  import { createRequire as createRequire$1 } from 'module';
6
- import { join, dirname, extname } from 'path';
7
8
  import { query, createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
8
9
  import { z } from 'zod';
9
10
  import { appendFile, readFile, mkdir, unlink } from 'fs/promises';
@@ -57,7 +58,90 @@ var install_exports = {};
57
58
  __export(install_exports, {
58
59
  install: () => install
59
60
  });
60
- function install(token) {
61
+ function getMacPaths() {
62
+ const home = homedir();
63
+ const deskfreeDir = join(home, ".deskfree");
64
+ return {
65
+ deskfreeDir,
66
+ envFile: join(deskfreeDir, ".env"),
67
+ launcher: join(deskfreeDir, "launcher.sh"),
68
+ logDir: join(deskfreeDir, "logs"),
69
+ plist: join(home, "Library", "LaunchAgents", `${PLIST_LABEL}.plist`)
70
+ };
71
+ }
72
+ function installMac(token) {
73
+ const paths = getMacPaths();
74
+ let nodeBinDir;
75
+ try {
76
+ const nodePath = execSync("which node", { encoding: "utf8" }).trim();
77
+ nodeBinDir = dirname(nodePath);
78
+ } catch {
79
+ console.error("Error: node not found in PATH");
80
+ process.exit(1);
81
+ }
82
+ mkdirSync(paths.deskfreeDir, { recursive: true });
83
+ mkdirSync(paths.logDir, { recursive: true });
84
+ mkdirSync(dirname(paths.plist), { recursive: true });
85
+ writeFileSync(paths.envFile, `DESKFREE_LAUNCH=${token}
86
+ `, { mode: 384 });
87
+ chmodSync(paths.envFile, 384);
88
+ console.log(`Wrote ${paths.envFile}`);
89
+ const launcher = `#!/bin/bash
90
+ set -euo pipefail
91
+
92
+ export PATH="${nodeBinDir}:$PATH"
93
+
94
+ # Update to latest version before starting
95
+ npm install -g ${PACKAGE} 2>/dev/null || true
96
+
97
+ # Source env
98
+ set -a
99
+ source "${paths.envFile}"
100
+ set +a
101
+
102
+ exec deskfree-agent start
103
+ `;
104
+ writeFileSync(paths.launcher, launcher, { mode: 493 });
105
+ chmodSync(paths.launcher, 493);
106
+ console.log(`Wrote ${paths.launcher}`);
107
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
108
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
109
+ <plist version="1.0">
110
+ <dict>
111
+ <key>Label</key>
112
+ <string>${PLIST_LABEL}</string>
113
+ <key>ProgramArguments</key>
114
+ <array>
115
+ <string>${paths.launcher}</string>
116
+ </array>
117
+ <key>KeepAlive</key>
118
+ <true/>
119
+ <key>RunAtLoad</key>
120
+ <true/>
121
+ <key>StandardOutPath</key>
122
+ <string>${join(paths.logDir, "stdout.log")}</string>
123
+ <key>StandardErrorPath</key>
124
+ <string>${join(paths.logDir, "stderr.log")}</string>
125
+ <key>ThrottleInterval</key>
126
+ <integer>10</integer>
127
+ </dict>
128
+ </plist>
129
+ `;
130
+ writeFileSync(paths.plist, plist);
131
+ console.log(`Wrote ${paths.plist}`);
132
+ try {
133
+ execSync(`launchctl bootout gui/$(id -u) ${paths.plist}`, {
134
+ stdio: "ignore"
135
+ });
136
+ } catch {
137
+ }
138
+ execSync(`launchctl bootstrap gui/$(id -u) ${paths.plist}`);
139
+ console.log(`
140
+ Service ${PLIST_LABEL} installed and started.`);
141
+ console.log(`Check status: launchctl print gui/$(id -u)/${PLIST_LABEL}`);
142
+ console.log(`Logs: tail -f ${join(paths.logDir, "stdout.log")}`);
143
+ }
144
+ function installLinux(token) {
61
145
  if (process.getuid?.() !== 0) {
62
146
  console.error("Error: install must be run as root (use sudo)");
63
147
  process.exit(1);
@@ -69,10 +153,12 @@ function install(token) {
69
153
  console.error("Error: npx not found in PATH");
70
154
  process.exit(1);
71
155
  }
72
- writeFileSync(ENV_FILE, `DESKFREE_LAUNCH=${token}
73
- `, { mode: 384 });
74
- chmodSync(ENV_FILE, 384);
75
- console.log(`Wrote ${ENV_FILE}`);
156
+ writeFileSync(SYSTEMD_ENV_FILE, `DESKFREE_LAUNCH=${token}
157
+ `, {
158
+ mode: 384
159
+ });
160
+ chmodSync(SYSTEMD_ENV_FILE, 384);
161
+ console.log(`Wrote ${SYSTEMD_ENV_FILE}`);
76
162
  const unit = `[Unit]
77
163
  Description=DeskFree Agent
78
164
  After=network-online.target
@@ -80,8 +166,9 @@ Wants=network-online.target
80
166
 
81
167
  [Service]
82
168
  Type=simple
169
+ ExecStartPre=${npxPath} ${PACKAGE} --version
83
170
  ExecStart=${npxPath} ${PACKAGE} start
84
- EnvironmentFile=${ENV_FILE}
171
+ EnvironmentFile=${SYSTEMD_ENV_FILE}
85
172
  Environment=NODE_ENV=production
86
173
  Restart=always
87
174
  RestartSec=10
@@ -89,21 +176,33 @@ RestartSec=10
89
176
  [Install]
90
177
  WantedBy=multi-user.target
91
178
  `;
92
- writeFileSync(SERVICE_FILE, unit);
93
- console.log(`Wrote ${SERVICE_FILE}`);
179
+ writeFileSync(SYSTEMD_SERVICE_FILE, unit);
180
+ console.log(`Wrote ${SYSTEMD_SERVICE_FILE}`);
94
181
  execSync("systemctl daemon-reload");
95
182
  execSync(`systemctl enable ${SERVICE_NAME}`);
96
183
  execSync(`systemctl start ${SERVICE_NAME}`);
97
- console.log(`Service ${SERVICE_NAME} installed and started.`);
184
+ console.log(`
185
+ Service ${SERVICE_NAME} installed and started.`);
98
186
  console.log(`Check status: systemctl status ${SERVICE_NAME}`);
99
187
  }
100
- var SERVICE_NAME, SERVICE_FILE, ENV_FILE, PACKAGE;
188
+ function install(token) {
189
+ if (process.platform === "darwin") {
190
+ installMac(token);
191
+ } else if (process.platform === "linux") {
192
+ installLinux(token);
193
+ } else {
194
+ console.error(`Unsupported platform: ${process.platform}`);
195
+ process.exit(1);
196
+ }
197
+ }
198
+ var SERVICE_NAME, PACKAGE, PLIST_LABEL, SYSTEMD_SERVICE_FILE, SYSTEMD_ENV_FILE;
101
199
  var init_install = __esm({
102
200
  "src/cli/install.ts"() {
103
201
  SERVICE_NAME = "deskfree-agent";
104
- SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
105
- ENV_FILE = `/etc/${SERVICE_NAME}.env`;
106
202
  PACKAGE = "@questionbase/deskfree@latest";
203
+ PLIST_LABEL = "com.deskfree.agent";
204
+ SYSTEMD_SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
205
+ SYSTEMD_ENV_FILE = `/etc/${SERVICE_NAME}.env`;
107
206
  }
108
207
  });
109
208
 
@@ -112,7 +211,28 @@ var uninstall_exports = {};
112
211
  __export(uninstall_exports, {
113
212
  uninstall: () => uninstall
114
213
  });
115
- function uninstall() {
214
+ function uninstallMac() {
215
+ const home = homedir();
216
+ const plist = join(home, "Library", "LaunchAgents", `${PLIST_LABEL2}.plist`);
217
+ const deskfreeDir = join(home, ".deskfree");
218
+ const envFile = join(deskfreeDir, ".env");
219
+ const launcher = join(deskfreeDir, "launcher.sh");
220
+ try {
221
+ execSync(`launchctl bootout gui/$(id -u) ${plist}`, { stdio: "ignore" });
222
+ } catch {
223
+ }
224
+ for (const file of [plist, envFile, launcher]) {
225
+ if (existsSync(file)) {
226
+ unlinkSync(file);
227
+ console.log(`Removed ${file}`);
228
+ }
229
+ }
230
+ console.log(`Service ${PLIST_LABEL2} uninstalled.`);
231
+ console.log(
232
+ `Note: logs and state in ${deskfreeDir} were preserved. Remove manually if desired.`
233
+ );
234
+ }
235
+ function uninstallLinux() {
116
236
  if (process.getuid?.() !== 0) {
117
237
  console.error("Error: uninstall must be run as root (use sudo)");
118
238
  process.exit(1);
@@ -125,23 +245,104 @@ function uninstall() {
125
245
  execSync(`systemctl disable ${SERVICE_NAME2}`, { stdio: "ignore" });
126
246
  } catch {
127
247
  }
128
- if (existsSync(SERVICE_FILE2)) {
129
- unlinkSync(SERVICE_FILE2);
130
- console.log(`Removed ${SERVICE_FILE2}`);
248
+ if (existsSync(SYSTEMD_SERVICE_FILE2)) {
249
+ unlinkSync(SYSTEMD_SERVICE_FILE2);
250
+ console.log(`Removed ${SYSTEMD_SERVICE_FILE2}`);
131
251
  }
132
- if (existsSync(ENV_FILE2)) {
133
- unlinkSync(ENV_FILE2);
134
- console.log(`Removed ${ENV_FILE2}`);
252
+ if (existsSync(SYSTEMD_ENV_FILE2)) {
253
+ unlinkSync(SYSTEMD_ENV_FILE2);
254
+ console.log(`Removed ${SYSTEMD_ENV_FILE2}`);
135
255
  }
136
256
  execSync("systemctl daemon-reload");
137
257
  console.log(`Service ${SERVICE_NAME2} uninstalled.`);
138
258
  }
139
- var SERVICE_NAME2, SERVICE_FILE2, ENV_FILE2;
259
+ function uninstall() {
260
+ if (process.platform === "darwin") {
261
+ uninstallMac();
262
+ } else if (process.platform === "linux") {
263
+ uninstallLinux();
264
+ } else {
265
+ console.error(`Unsupported platform: ${process.platform}`);
266
+ process.exit(1);
267
+ }
268
+ }
269
+ var SERVICE_NAME2, PLIST_LABEL2, SYSTEMD_SERVICE_FILE2, SYSTEMD_ENV_FILE2;
140
270
  var init_uninstall = __esm({
141
271
  "src/cli/uninstall.ts"() {
142
272
  SERVICE_NAME2 = "deskfree-agent";
143
- SERVICE_FILE2 = `/etc/systemd/system/${SERVICE_NAME2}.service`;
144
- ENV_FILE2 = `/etc/${SERVICE_NAME2}.env`;
273
+ PLIST_LABEL2 = "com.deskfree.agent";
274
+ SYSTEMD_SERVICE_FILE2 = `/etc/systemd/system/${SERVICE_NAME2}.service`;
275
+ SYSTEMD_ENV_FILE2 = `/etc/${SERVICE_NAME2}.env`;
276
+ }
277
+ });
278
+
279
+ // src/cli/status.ts
280
+ var status_exports = {};
281
+ __export(status_exports, {
282
+ status: () => status
283
+ });
284
+ function statusMac() {
285
+ const home = homedir();
286
+ const plist = join(home, "Library", "LaunchAgents", `${PLIST_LABEL3}.plist`);
287
+ if (!existsSync(plist)) {
288
+ console.log("DeskFree Agent is not installed.");
289
+ console.log(`Run: npx @questionbase/deskfree@latest install <token>`);
290
+ return;
291
+ }
292
+ console.log("DeskFree Agent (macOS LaunchAgent)\n");
293
+ try {
294
+ const output = execSync(
295
+ `launchctl print gui/$(id -u)/${PLIST_LABEL3} 2>&1`,
296
+ { encoding: "utf8" }
297
+ );
298
+ const pidMatch = output.match(/pid\s*=\s*(\d+)/);
299
+ const stateMatch = output.match(/state\s*=\s*(\w+)/);
300
+ if (pidMatch) console.log(` PID: ${pidMatch[1]}`);
301
+ if (stateMatch) console.log(` State: ${stateMatch[1]}`);
302
+ const logDir = join(home, ".deskfree", "logs");
303
+ console.log(` Logs: ${logDir}/stdout.log`);
304
+ console.log(` Plist: ${plist}`);
305
+ } catch {
306
+ console.log(" Status: not running (service may be unloaded)");
307
+ }
308
+ }
309
+ function statusLinux() {
310
+ const serviceFile = `/etc/systemd/system/${SYSTEMD_SERVICE_NAME}.service`;
311
+ if (!existsSync(serviceFile)) {
312
+ console.log("DeskFree Agent is not installed.");
313
+ console.log(`Run: sudo npx @questionbase/deskfree@latest install <token>`);
314
+ return;
315
+ }
316
+ console.log("DeskFree Agent (systemd service)\n");
317
+ try {
318
+ const output = execSync(
319
+ `systemctl status ${SYSTEMD_SERVICE_NAME} --no-pager 2>&1`,
320
+ { encoding: "utf8" }
321
+ );
322
+ console.log(output);
323
+ } catch (err) {
324
+ if (err && typeof err === "object" && "stdout" in err) {
325
+ console.log(err.stdout);
326
+ } else {
327
+ console.log(" Status: unknown (could not query systemd)");
328
+ }
329
+ }
330
+ }
331
+ function status() {
332
+ if (process.platform === "darwin") {
333
+ statusMac();
334
+ } else if (process.platform === "linux") {
335
+ statusLinux();
336
+ } else {
337
+ console.error(`Unsupported platform: ${process.platform}`);
338
+ process.exit(1);
339
+ }
340
+ }
341
+ var PLIST_LABEL3, SYSTEMD_SERVICE_NAME;
342
+ var init_status = __esm({
343
+ "src/cli/status.ts"() {
344
+ PLIST_LABEL3 = "com.deskfree.agent";
345
+ SYSTEMD_SERVICE_NAME = "deskfree-agent";
145
346
  }
146
347
  });
147
348
  function toErrorMessage(error) {
@@ -2554,6 +2755,41 @@ function createWorkerTools(client, options) {
2554
2755
  return errorResult(err);
2555
2756
  }
2556
2757
  }),
2758
+ createTool(WORKER_TOOLS.PROPOSE, async (params) => {
2759
+ try {
2760
+ const context = validateStringParam(params, "context", false);
2761
+ const taskId = validateStringParam(params, "taskId", false);
2762
+ const rawTasks = params.tasks;
2763
+ if (!Array.isArray(rawTasks) || rawTasks.length === 0) {
2764
+ throw new Error("tasks must be a non-empty array of task objects");
2765
+ }
2766
+ const tasks = rawTasks;
2767
+ for (let i = 0; i < tasks.length; i++) {
2768
+ const task = tasks[i];
2769
+ if (!task || typeof task !== "object") {
2770
+ throw new Error(`tasks[${i}] must be an object`);
2771
+ }
2772
+ if (!task.title || typeof task.title !== "string" || task.title.trim() === "") {
2773
+ throw new Error(`tasks[${i}].title must be a non-empty string`);
2774
+ }
2775
+ }
2776
+ await client.proposePlan({
2777
+ context,
2778
+ tasks,
2779
+ taskId
2780
+ });
2781
+ return {
2782
+ content: [
2783
+ {
2784
+ type: "text",
2785
+ text: `Proposal created with ${tasks.length} task(s)`
2786
+ }
2787
+ ]
2788
+ };
2789
+ } catch (err) {
2790
+ return errorResult(err);
2791
+ }
2792
+ }),
2557
2793
  createTool(WORKER_TOOLS.COMPLETE_TASK, async (params) => {
2558
2794
  try {
2559
2795
  const taskId = validateStringParam(params, "taskId", true);
@@ -6411,96 +6647,96 @@ var init_dist = __esm({
6411
6647
  this.statusCode = statusCode;
6412
6648
  }
6413
6649
  static fromResponse(response, procedure, responseText) {
6414
- const status = response.status;
6650
+ const status2 = response.status;
6415
6651
  const statusText = response.statusText;
6416
- if (status === 401) {
6652
+ if (status2 === 401) {
6417
6653
  return new _DeskFreeError(
6418
6654
  "auth",
6419
6655
  procedure,
6420
- `Authentication failed: ${status} ${statusText} \u2014 ${responseText}`,
6656
+ `Authentication failed: ${status2} ${statusText} \u2014 ${responseText}`,
6421
6657
  "Your bot token is invalid or has expired. Please check your authentication credentials.",
6422
- status
6658
+ status2
6423
6659
  );
6424
6660
  }
6425
- if (status === 403) {
6661
+ if (status2 === 403) {
6426
6662
  return new _DeskFreeError(
6427
6663
  "auth",
6428
6664
  procedure,
6429
- `Authorization failed: ${status} ${statusText} \u2014 ${responseText}`,
6665
+ `Authorization failed: ${status2} ${statusText} \u2014 ${responseText}`,
6430
6666
  "Your bot does not have permission to perform this action. Contact your administrator.",
6431
- status
6667
+ status2
6432
6668
  );
6433
6669
  }
6434
- if (status === 400) {
6670
+ if (status2 === 400) {
6435
6671
  return new _DeskFreeError(
6436
6672
  "client",
6437
6673
  procedure,
6438
- `Bad request: ${status} ${statusText} \u2014 ${responseText}`,
6674
+ `Bad request: ${status2} ${statusText} \u2014 ${responseText}`,
6439
6675
  "The request was invalid. Please check your input parameters and try again.",
6440
- status
6676
+ status2
6441
6677
  );
6442
6678
  }
6443
- if (status === 404) {
6679
+ if (status2 === 404) {
6444
6680
  return new _DeskFreeError(
6445
6681
  "client",
6446
6682
  procedure,
6447
- `Resource not found: ${status} ${statusText} \u2014 ${responseText}`,
6683
+ `Resource not found: ${status2} ${statusText} \u2014 ${responseText}`,
6448
6684
  procedure.includes("task") ? "The specified task was not found or is not available. Use deskfree_state to see available tasks." : "The requested resource was not found. Please check your input and try again.",
6449
- status
6685
+ status2
6450
6686
  );
6451
6687
  }
6452
- if (status === 409) {
6688
+ if (status2 === 409) {
6453
6689
  return new _DeskFreeError(
6454
6690
  "client",
6455
6691
  procedure,
6456
- `Conflict: ${status} ${statusText} \u2014 ${responseText}`,
6692
+ `Conflict: ${status2} ${statusText} \u2014 ${responseText}`,
6457
6693
  procedure.includes("start_task") || procedure.includes("claim") ? "The resource is already in use by another process. Use deskfree_state to see current status and try a different resource." : "The request conflicts with the current state of the resource. Please refresh and try again.",
6458
- status
6694
+ status2
6459
6695
  );
6460
6696
  }
6461
- if (status === 422) {
6697
+ if (status2 === 422) {
6462
6698
  return new _DeskFreeError(
6463
6699
  "client",
6464
6700
  procedure,
6465
- `Validation failed: ${status} ${statusText} \u2014 ${responseText}`,
6701
+ `Validation failed: ${status2} ${statusText} \u2014 ${responseText}`,
6466
6702
  "The request data failed validation. Please check all required fields and data formats.",
6467
- status
6703
+ status2
6468
6704
  );
6469
6705
  }
6470
- if (status === 429) {
6706
+ if (status2 === 429) {
6471
6707
  return new _DeskFreeError(
6472
6708
  "client",
6473
6709
  procedure,
6474
- `Rate limit exceeded: ${status} ${statusText} \u2014 ${responseText}`,
6710
+ `Rate limit exceeded: ${status2} ${statusText} \u2014 ${responseText}`,
6475
6711
  "Too many requests. Please wait a moment before trying again.",
6476
- status
6712
+ status2
6477
6713
  );
6478
6714
  }
6479
- if (status >= 500 && status < 600) {
6480
- const serverErrorMessage = status === 502 || status === 503 ? "DeskFree service is temporarily unavailable due to maintenance or high load. Please try again in a few minutes." : status === 504 ? "The request timed out on the server. This may be due to high load. Please try again with a smaller request or wait a few minutes." : "DeskFree service encountered an internal error. Please try again in a few minutes.";
6715
+ if (status2 >= 500 && status2 < 600) {
6716
+ const serverErrorMessage = status2 === 502 || status2 === 503 ? "DeskFree service is temporarily unavailable due to maintenance or high load. Please try again in a few minutes." : status2 === 504 ? "The request timed out on the server. This may be due to high load. Please try again with a smaller request or wait a few minutes." : "DeskFree service encountered an internal error. Please try again in a few minutes.";
6481
6717
  return new _DeskFreeError(
6482
6718
  "server",
6483
6719
  procedure,
6484
- `Server error: ${status} ${statusText} \u2014 ${responseText}`,
6720
+ `Server error: ${status2} ${statusText} \u2014 ${responseText}`,
6485
6721
  serverErrorMessage,
6486
- status
6722
+ status2
6487
6723
  );
6488
6724
  }
6489
- if (status >= 400 && status < 500) {
6725
+ if (status2 >= 400 && status2 < 500) {
6490
6726
  return new _DeskFreeError(
6491
6727
  "client",
6492
6728
  procedure,
6493
- `Client error: ${status} ${statusText} \u2014 ${responseText}`,
6729
+ `Client error: ${status2} ${statusText} \u2014 ${responseText}`,
6494
6730
  "The request was not accepted by the server. Please check your input and try again.",
6495
- status
6731
+ status2
6496
6732
  );
6497
6733
  }
6498
6734
  return new _DeskFreeError(
6499
6735
  "client",
6500
6736
  procedure,
6501
- `HTTP error: ${status} ${statusText} \u2014 ${responseText}`,
6502
- `Request failed with error ${status}. Please try again or contact support if the problem persists.`,
6503
- status
6737
+ `HTTP error: ${status2} ${statusText} \u2014 ${responseText}`,
6738
+ `Request failed with error ${status2}. Please try again or contact support if the problem persists.`,
6739
+ status2
6504
6740
  );
6505
6741
  }
6506
6742
  static timeout(procedure, timeoutMs) {
@@ -7361,7 +7597,10 @@ You are the orchestrator. Your job: turn human intent into approved tasks, then
7361
7597
  **Match the human's energy.** Short message \u2192 short reply. Casual tone \u2192 casual response. Don't over-explain, don't lecture, don't pad responses.
7362
7598
 
7363
7599
  You do NOT claim tasks, complete tasks, or do work directly \u2014 you have no access to deskfree_start_task or deskfree_complete_task. Use \`deskfree_dispatch_worker\` to dispatch a worker for each approved task.
7364
- - When a human writes in a task thread, decide: does it need bot action? If yes \u2192 reopen and dispatch a worker. If it's just confirmation or deferred \u2014 leave it for now.
7600
+ - When a human writes in a task thread, decide:
7601
+ - **Continuation of the same task?** \u2192 reopen and dispatch a worker.
7602
+ - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one or do the work yourself).
7603
+ - **Just confirmation or deferred?** \u2192 leave it for now.
7365
7604
  - Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
7366
7605
  DESKFREE_WORKER_DIRECTIVE = `## DeskFree Worker
7367
7606
  You are a worker sub-agent. Your first message contains pre-loaded context \u2014 use it directly.
@@ -7797,8 +8036,6 @@ function runOrchestrator(opts) {
7797
8036
  return query({
7798
8037
  prompt,
7799
8038
  options: {
7800
- debug: true,
7801
- debugFile: "/dev/stderr",
7802
8039
  stderr: (data) => {
7803
8040
  process.stderr.write(`[orchestrator-sdk] ${data}
7804
8041
  `);
@@ -7816,18 +8053,7 @@ function runOrchestrator(opts) {
7816
8053
  "deskfree-orchestrator": orchestratorServer
7817
8054
  },
7818
8055
  tools: [],
7819
- allowedTools: [
7820
- "mcp__deskfree-orchestrator__*",
7821
- "Read",
7822
- "Write",
7823
- "Edit",
7824
- "Bash",
7825
- "Glob",
7826
- "Grep",
7827
- "WebSearch",
7828
- "WebFetch",
7829
- "NotebookEdit"
7830
- ],
8056
+ allowedTools: ORCHESTRATOR_ALLOWED_TOOLS,
7831
8057
  disallowedTools: DISALLOWED_BUILTIN_TOOLS
7832
8058
  }
7833
8059
  });
@@ -7848,18 +8074,7 @@ function runHeartbeat(opts) {
7848
8074
  "deskfree-orchestrator": orchestratorServer
7849
8075
  },
7850
8076
  tools: [],
7851
- allowedTools: [
7852
- "mcp__deskfree-orchestrator__*",
7853
- "Read",
7854
- "Write",
7855
- "Edit",
7856
- "Bash",
7857
- "Glob",
7858
- "Grep",
7859
- "WebSearch",
7860
- "WebFetch",
7861
- "NotebookEdit"
7862
- ],
8077
+ allowedTools: ORCHESTRATOR_ALLOWED_TOOLS,
7863
8078
  disallowedTools: DISALLOWED_BUILTIN_TOOLS
7864
8079
  }
7865
8080
  });
@@ -7882,11 +8097,19 @@ function runOneShotWorker(opts) {
7882
8097
  }
7883
8098
  });
7884
8099
  }
7885
- var MAX_ORCHESTRATOR_TURNS, DISALLOWED_BUILTIN_TOOLS;
8100
+ var MAX_ORCHESTRATOR_TURNS, ORCHESTRATOR_ALLOWED_TOOLS, DISALLOWED_BUILTIN_TOOLS;
7886
8101
  var init_orchestrator = __esm({
7887
8102
  "src/agents/orchestrator.ts"() {
7888
8103
  init_dist();
7889
8104
  MAX_ORCHESTRATOR_TURNS = 20;
8105
+ ORCHESTRATOR_ALLOWED_TOOLS = [
8106
+ "mcp__deskfree-orchestrator__*",
8107
+ "Read",
8108
+ "Glob",
8109
+ "Grep",
8110
+ "WebSearch",
8111
+ "WebFetch"
8112
+ ];
7890
8113
  DISALLOWED_BUILTIN_TOOLS = [
7891
8114
  "TodoWrite",
7892
8115
  "AskUserQuestion",
@@ -8051,6 +8274,86 @@ var init_health_state = __esm({
8051
8274
  }
8052
8275
  });
8053
8276
 
8277
+ // src/util/logger.ts
8278
+ var logger_exports = {};
8279
+ __export(logger_exports, {
8280
+ createLogger: () => createLogger,
8281
+ enableFileLogging: () => enableFileLogging,
8282
+ getLogFilePath: () => getLogFilePath,
8283
+ logger: () => logger
8284
+ });
8285
+ function enableFileLogging(filePath) {
8286
+ mkdirSync(dirname(filePath), { recursive: true });
8287
+ logFilePath = filePath;
8288
+ }
8289
+ function getLogFilePath() {
8290
+ return logFilePath;
8291
+ }
8292
+ function rotateIfNeeded() {
8293
+ if (!logFilePath) return;
8294
+ try {
8295
+ const stat = statSync(logFilePath);
8296
+ if (stat.size <= MAX_LOG_FILE_BYTES) return;
8297
+ const content = readFileSync(logFilePath, "utf8");
8298
+ const half = Math.floor(content.length / 2);
8299
+ const newlineIdx = content.indexOf("\n", half);
8300
+ const trimmed = newlineIdx >= 0 ? content.slice(newlineIdx + 1) : content.slice(half);
8301
+ writeFileSync(logFilePath, trimmed);
8302
+ } catch {
8303
+ }
8304
+ }
8305
+ function createLogger(component, minLevel = "info") {
8306
+ const minLevelNum = LEVELS[minLevel];
8307
+ function log(level, message, fields) {
8308
+ if (LEVELS[level] < minLevelNum) return;
8309
+ const entry = {
8310
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
8311
+ level,
8312
+ component,
8313
+ msg: message,
8314
+ ...fields
8315
+ };
8316
+ const line = JSON.stringify(entry);
8317
+ if (level === "error" || level === "warn") {
8318
+ process.stderr.write(line + "\n");
8319
+ } else {
8320
+ process.stdout.write(line + "\n");
8321
+ }
8322
+ if (logFilePath) {
8323
+ try {
8324
+ appendFileSync(logFilePath, line + "\n");
8325
+ if (++writesSinceCheck >= ROTATION_CHECK_INTERVAL) {
8326
+ writesSinceCheck = 0;
8327
+ rotateIfNeeded();
8328
+ }
8329
+ } catch {
8330
+ }
8331
+ }
8332
+ }
8333
+ return {
8334
+ debug: (msg, fields) => log("debug", msg, fields),
8335
+ info: (msg, fields) => log("info", msg, fields),
8336
+ warn: (msg, fields) => log("warn", msg, fields),
8337
+ error: (msg, fields) => log("error", msg, fields)
8338
+ };
8339
+ }
8340
+ var LEVELS, MAX_LOG_FILE_BYTES, ROTATION_CHECK_INTERVAL, logFilePath, writesSinceCheck, logger;
8341
+ var init_logger = __esm({
8342
+ "src/util/logger.ts"() {
8343
+ LEVELS = {
8344
+ debug: 0,
8345
+ info: 1,
8346
+ warn: 2,
8347
+ error: 3
8348
+ };
8349
+ MAX_LOG_FILE_BYTES = 5 * 1024 * 1024;
8350
+ ROTATION_CHECK_INTERVAL = 500;
8351
+ logFilePath = null;
8352
+ writesSinceCheck = 0;
8353
+ logger = createLogger("runtime");
8354
+ }
8355
+ });
8356
+
8054
8357
  // src/gateway/polling.ts
8055
8358
  async function pollAndDeliver(client, accountId, stateDir, cursor, onMessage, log) {
8056
8359
  const ctx = { accountId };
@@ -11755,8 +12058,6 @@ var init_wrapper = __esm({
11755
12058
  wrapper_default2 = import_websocket2.default;
11756
12059
  }
11757
12060
  });
11758
-
11759
- // src/gateway/ws-gateway.ts
11760
12061
  function nextBackoff(state2) {
11761
12062
  const delay = Math.min(
11762
12063
  BACKOFF_INITIAL_MS * Math.pow(BACKOFF_FACTOR, state2.attempt),
@@ -11936,11 +12237,11 @@ async function runWebSocketConnection(opts) {
11936
12237
  }, PING_INTERVAL_MS);
11937
12238
  if (opts.getWorkerStatus) {
11938
12239
  try {
11939
- const status = opts.getWorkerStatus();
12240
+ const status2 = opts.getWorkerStatus();
11940
12241
  ws.send(
11941
12242
  JSON.stringify({
11942
12243
  action: "heartbeatResponse",
11943
- ...status
12244
+ ...status2
11944
12245
  })
11945
12246
  );
11946
12247
  } catch (err) {
@@ -12010,11 +12311,11 @@ async function runWebSocketConnection(opts) {
12010
12311
  } else if (msg.action === "heartbeatRequest") {
12011
12312
  if (opts.getWorkerStatus && ws.readyState === wrapper_default2.OPEN) {
12012
12313
  try {
12013
- const status = opts.getWorkerStatus();
12314
+ const status2 = opts.getWorkerStatus();
12014
12315
  ws.send(
12015
12316
  JSON.stringify({
12016
12317
  action: "heartbeatResponse",
12017
- ...status
12318
+ ...status2
12018
12319
  })
12019
12320
  );
12020
12321
  } catch (err) {
@@ -12029,6 +12330,49 @@ async function runWebSocketConnection(opts) {
12029
12330
  cleanup();
12030
12331
  ws.close(1e3, "reload");
12031
12332
  process.kill(process.pid, "SIGTERM");
12333
+ } else if (msg.action === "logs") {
12334
+ const MAX_RESPONSE_BYTES = 12e4;
12335
+ const lineCount = msg.lines ?? 200;
12336
+ try {
12337
+ const logFile = getLogFilePath();
12338
+ if (!logFile) {
12339
+ ws.send(
12340
+ JSON.stringify({
12341
+ action: "logsResponse",
12342
+ lines: [],
12343
+ error: "File logging not enabled"
12344
+ })
12345
+ );
12346
+ } else {
12347
+ const content = readFileSync(logFile, "utf8");
12348
+ const allLines = content.split("\n").filter(Boolean);
12349
+ let tail = allLines.slice(-lineCount);
12350
+ while (tail.length > 1) {
12351
+ const payload = JSON.stringify({
12352
+ action: "logsResponse",
12353
+ lines: tail
12354
+ });
12355
+ if (Buffer.byteLength(payload) <= MAX_RESPONSE_BYTES) break;
12356
+ tail = tail.slice(Math.ceil(tail.length * 0.25));
12357
+ }
12358
+ ws.send(
12359
+ JSON.stringify({
12360
+ action: "logsResponse",
12361
+ lines: tail
12362
+ })
12363
+ );
12364
+ }
12365
+ } catch (err) {
12366
+ const errMsg = err instanceof Error ? err.message : String(err);
12367
+ log.warn(`Failed to read logs: ${errMsg}`);
12368
+ ws.send(
12369
+ JSON.stringify({
12370
+ action: "logsResponse",
12371
+ lines: [],
12372
+ error: errMsg
12373
+ })
12374
+ );
12375
+ }
12032
12376
  }
12033
12377
  } catch (err) {
12034
12378
  const message = err instanceof Error ? err.message : String(err);
@@ -12147,6 +12491,7 @@ var PING_INTERVAL_MS, POLL_FALLBACK_INTERVAL_MS, WS_CONNECTION_TIMEOUT_MS, WS_PO
12147
12491
  var init_ws_gateway = __esm({
12148
12492
  "src/gateway/ws-gateway.ts"() {
12149
12493
  init_health_state();
12494
+ init_logger();
12150
12495
  init_polling();
12151
12496
  init_dist();
12152
12497
  init_wrapper();
@@ -12641,21 +12986,35 @@ function scheduleDailyCycle(label, run, hour, timezone, signal, log) {
12641
12986
  }
12642
12987
  function msUntilNextLocalHour(hour, timezone) {
12643
12988
  const now = Date.now();
12644
- const fmt = new Intl.DateTimeFormat("en-US", {
12989
+ const hourFmt = new Intl.DateTimeFormat("en-US", {
12645
12990
  timeZone: timezone,
12646
12991
  hour: "numeric",
12647
12992
  hour12: false
12648
12993
  });
12994
+ const detailFmt = new Intl.DateTimeFormat("en-US", {
12995
+ timeZone: timezone,
12996
+ minute: "numeric",
12997
+ second: "numeric"
12998
+ });
12649
12999
  for (let offsetMs = 6e4; offsetMs <= 25 * 36e5; offsetMs += 36e5) {
12650
13000
  const candidateMs = now + offsetMs;
12651
- const parts = fmt.formatToParts(new Date(candidateMs));
13001
+ const parts = hourFmt.formatToParts(new Date(candidateMs));
12652
13002
  const hourPart = parts.find((p) => p.type === "hour");
12653
13003
  const candidateHour = parseInt(hourPart?.value ?? "-1", 10);
12654
13004
  if (candidateHour === hour) {
12655
- const candidate = new Date(candidateMs);
12656
- candidate.setUTCMinutes(0, 0, 0);
12657
- const snappedMs = candidate.getTime();
12658
- return Math.max(snappedMs - now, 6e4);
13005
+ const detailParts = detailFmt.formatToParts(new Date(candidateMs));
13006
+ const localMinute = parseInt(
13007
+ detailParts.find((p) => p.type === "minute")?.value ?? "0",
13008
+ 10
13009
+ );
13010
+ const localSecond = parseInt(
13011
+ detailParts.find((p) => p.type === "second")?.value ?? "0",
13012
+ 10
13013
+ );
13014
+ const snappedMs = candidateMs - localMinute * 6e4 - localSecond * 1e3;
13015
+ if (snappedMs > now + 6e4) {
13016
+ return snappedMs - now;
13017
+ }
12659
13018
  }
12660
13019
  }
12661
13020
  return 24 * 36e5;
@@ -12751,50 +13110,6 @@ var init_registry = __esm({
12751
13110
  "src/tools/registry.ts"() {
12752
13111
  }
12753
13112
  });
12754
-
12755
- // src/util/logger.ts
12756
- var logger_exports = {};
12757
- __export(logger_exports, {
12758
- createLogger: () => createLogger,
12759
- logger: () => logger
12760
- });
12761
- function createLogger(component, minLevel = "info") {
12762
- const minLevelNum = LEVELS[minLevel];
12763
- function log(level, message, fields) {
12764
- if (LEVELS[level] < minLevelNum) return;
12765
- const entry = {
12766
- ts: (/* @__PURE__ */ new Date()).toISOString(),
12767
- level,
12768
- component,
12769
- msg: message,
12770
- ...fields
12771
- };
12772
- const line = JSON.stringify(entry);
12773
- if (level === "error" || level === "warn") {
12774
- process.stderr.write(line + "\n");
12775
- } else {
12776
- process.stdout.write(line + "\n");
12777
- }
12778
- }
12779
- return {
12780
- debug: (msg, fields) => log("debug", msg, fields),
12781
- info: (msg, fields) => log("info", msg, fields),
12782
- warn: (msg, fields) => log("warn", msg, fields),
12783
- error: (msg, fields) => log("error", msg, fields)
12784
- };
12785
- }
12786
- var LEVELS, logger;
12787
- var init_logger = __esm({
12788
- "src/util/logger.ts"() {
12789
- LEVELS = {
12790
- debug: 0,
12791
- info: 1,
12792
- warn: 2,
12793
- error: 3
12794
- };
12795
- logger = createLogger("runtime");
12796
- }
12797
- });
12798
13113
  function checkRateLimit(userId) {
12799
13114
  const now = Date.now();
12800
13115
  const entry = rateLimitMap.get(userId);
@@ -13246,8 +13561,6 @@ function runWorker(opts) {
13246
13561
  return query({
13247
13562
  prompt,
13248
13563
  options: {
13249
- debug: true,
13250
- debugFile: "/dev/stderr",
13251
13564
  stderr: (data) => {
13252
13565
  process.stderr.write(`[worker-sdk] ${data}
13253
13566
  `);
@@ -13651,7 +13964,7 @@ ${userMessage}
13651
13964
  try {
13652
13965
  await client.reportUsage({
13653
13966
  taskId,
13654
- estimatedCost: result.total_cost_usd
13967
+ estimatedCost: Math.round(result.total_cost_usd * 1e6) / 1e6
13655
13968
  });
13656
13969
  } catch {
13657
13970
  log.warn(`Failed to report usage for task ${taskId}`);
@@ -13865,6 +14178,9 @@ async function startAgent(opts) {
13865
14178
  }
13866
14179
  mkdirSync(config.stateDir, { recursive: true });
13867
14180
  mkdirSync(config.toolsDir, { recursive: true });
14181
+ const logFile = join(config.stateDir, "runtime.log");
14182
+ enableFileLogging(logFile);
14183
+ log.info(`File logging enabled: ${logFile}`);
13868
14184
  if (config.tools.length > 0) {
13869
14185
  log.info(`Installing ${config.tools.length} tool package(s)...`);
13870
14186
  await installTools(config.tools, config.toolsDir, log);
@@ -14105,6 +14421,9 @@ if (command === "install") {
14105
14421
  } else if (command === "uninstall") {
14106
14422
  const { uninstall: uninstall2 } = await Promise.resolve().then(() => (init_uninstall(), uninstall_exports));
14107
14423
  uninstall2();
14424
+ } else if (command === "status") {
14425
+ const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
14426
+ status2();
14108
14427
  } else {
14109
14428
  let handleShutdown = function(signal) {
14110
14429
  log.info(`Received ${signal} \u2014 shutting down...`);