@questionbase/deskfree 0.4.5 → 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.
@@ -7814,18 +8053,7 @@ function runOrchestrator(opts) {
7814
8053
  "deskfree-orchestrator": orchestratorServer
7815
8054
  },
7816
8055
  tools: [],
7817
- allowedTools: [
7818
- "mcp__deskfree-orchestrator__*",
7819
- "Read",
7820
- "Write",
7821
- "Edit",
7822
- "Bash",
7823
- "Glob",
7824
- "Grep",
7825
- "WebSearch",
7826
- "WebFetch",
7827
- "NotebookEdit"
7828
- ],
8056
+ allowedTools: ORCHESTRATOR_ALLOWED_TOOLS,
7829
8057
  disallowedTools: DISALLOWED_BUILTIN_TOOLS
7830
8058
  }
7831
8059
  });
@@ -7846,18 +8074,7 @@ function runHeartbeat(opts) {
7846
8074
  "deskfree-orchestrator": orchestratorServer
7847
8075
  },
7848
8076
  tools: [],
7849
- allowedTools: [
7850
- "mcp__deskfree-orchestrator__*",
7851
- "Read",
7852
- "Write",
7853
- "Edit",
7854
- "Bash",
7855
- "Glob",
7856
- "Grep",
7857
- "WebSearch",
7858
- "WebFetch",
7859
- "NotebookEdit"
7860
- ],
8077
+ allowedTools: ORCHESTRATOR_ALLOWED_TOOLS,
7861
8078
  disallowedTools: DISALLOWED_BUILTIN_TOOLS
7862
8079
  }
7863
8080
  });
@@ -7880,11 +8097,19 @@ function runOneShotWorker(opts) {
7880
8097
  }
7881
8098
  });
7882
8099
  }
7883
- var MAX_ORCHESTRATOR_TURNS, DISALLOWED_BUILTIN_TOOLS;
8100
+ var MAX_ORCHESTRATOR_TURNS, ORCHESTRATOR_ALLOWED_TOOLS, DISALLOWED_BUILTIN_TOOLS;
7884
8101
  var init_orchestrator = __esm({
7885
8102
  "src/agents/orchestrator.ts"() {
7886
8103
  init_dist();
7887
8104
  MAX_ORCHESTRATOR_TURNS = 20;
8105
+ ORCHESTRATOR_ALLOWED_TOOLS = [
8106
+ "mcp__deskfree-orchestrator__*",
8107
+ "Read",
8108
+ "Glob",
8109
+ "Grep",
8110
+ "WebSearch",
8111
+ "WebFetch"
8112
+ ];
7888
8113
  DISALLOWED_BUILTIN_TOOLS = [
7889
8114
  "TodoWrite",
7890
8115
  "AskUserQuestion",
@@ -8049,6 +8274,86 @@ var init_health_state = __esm({
8049
8274
  }
8050
8275
  });
8051
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
+
8052
8357
  // src/gateway/polling.ts
8053
8358
  async function pollAndDeliver(client, accountId, stateDir, cursor, onMessage, log) {
8054
8359
  const ctx = { accountId };
@@ -11753,8 +12058,6 @@ var init_wrapper = __esm({
11753
12058
  wrapper_default2 = import_websocket2.default;
11754
12059
  }
11755
12060
  });
11756
-
11757
- // src/gateway/ws-gateway.ts
11758
12061
  function nextBackoff(state2) {
11759
12062
  const delay = Math.min(
11760
12063
  BACKOFF_INITIAL_MS * Math.pow(BACKOFF_FACTOR, state2.attempt),
@@ -11934,11 +12237,11 @@ async function runWebSocketConnection(opts) {
11934
12237
  }, PING_INTERVAL_MS);
11935
12238
  if (opts.getWorkerStatus) {
11936
12239
  try {
11937
- const status = opts.getWorkerStatus();
12240
+ const status2 = opts.getWorkerStatus();
11938
12241
  ws.send(
11939
12242
  JSON.stringify({
11940
12243
  action: "heartbeatResponse",
11941
- ...status
12244
+ ...status2
11942
12245
  })
11943
12246
  );
11944
12247
  } catch (err) {
@@ -12008,11 +12311,11 @@ async function runWebSocketConnection(opts) {
12008
12311
  } else if (msg.action === "heartbeatRequest") {
12009
12312
  if (opts.getWorkerStatus && ws.readyState === wrapper_default2.OPEN) {
12010
12313
  try {
12011
- const status = opts.getWorkerStatus();
12314
+ const status2 = opts.getWorkerStatus();
12012
12315
  ws.send(
12013
12316
  JSON.stringify({
12014
12317
  action: "heartbeatResponse",
12015
- ...status
12318
+ ...status2
12016
12319
  })
12017
12320
  );
12018
12321
  } catch (err) {
@@ -12027,6 +12330,49 @@ async function runWebSocketConnection(opts) {
12027
12330
  cleanup();
12028
12331
  ws.close(1e3, "reload");
12029
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
+ }
12030
12376
  }
12031
12377
  } catch (err) {
12032
12378
  const message = err instanceof Error ? err.message : String(err);
@@ -12145,6 +12491,7 @@ var PING_INTERVAL_MS, POLL_FALLBACK_INTERVAL_MS, WS_CONNECTION_TIMEOUT_MS, WS_PO
12145
12491
  var init_ws_gateway = __esm({
12146
12492
  "src/gateway/ws-gateway.ts"() {
12147
12493
  init_health_state();
12494
+ init_logger();
12148
12495
  init_polling();
12149
12496
  init_dist();
12150
12497
  init_wrapper();
@@ -12639,21 +12986,35 @@ function scheduleDailyCycle(label, run, hour, timezone, signal, log) {
12639
12986
  }
12640
12987
  function msUntilNextLocalHour(hour, timezone) {
12641
12988
  const now = Date.now();
12642
- const fmt = new Intl.DateTimeFormat("en-US", {
12989
+ const hourFmt = new Intl.DateTimeFormat("en-US", {
12643
12990
  timeZone: timezone,
12644
12991
  hour: "numeric",
12645
12992
  hour12: false
12646
12993
  });
12994
+ const detailFmt = new Intl.DateTimeFormat("en-US", {
12995
+ timeZone: timezone,
12996
+ minute: "numeric",
12997
+ second: "numeric"
12998
+ });
12647
12999
  for (let offsetMs = 6e4; offsetMs <= 25 * 36e5; offsetMs += 36e5) {
12648
13000
  const candidateMs = now + offsetMs;
12649
- const parts = fmt.formatToParts(new Date(candidateMs));
13001
+ const parts = hourFmt.formatToParts(new Date(candidateMs));
12650
13002
  const hourPart = parts.find((p) => p.type === "hour");
12651
13003
  const candidateHour = parseInt(hourPart?.value ?? "-1", 10);
12652
13004
  if (candidateHour === hour) {
12653
- const candidate = new Date(candidateMs);
12654
- candidate.setUTCMinutes(0, 0, 0);
12655
- const snappedMs = candidate.getTime();
12656
- 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
+ }
12657
13018
  }
12658
13019
  }
12659
13020
  return 24 * 36e5;
@@ -12749,50 +13110,6 @@ var init_registry = __esm({
12749
13110
  "src/tools/registry.ts"() {
12750
13111
  }
12751
13112
  });
12752
-
12753
- // src/util/logger.ts
12754
- var logger_exports = {};
12755
- __export(logger_exports, {
12756
- createLogger: () => createLogger,
12757
- logger: () => logger
12758
- });
12759
- function createLogger(component, minLevel = "info") {
12760
- const minLevelNum = LEVELS[minLevel];
12761
- function log(level, message, fields) {
12762
- if (LEVELS[level] < minLevelNum) return;
12763
- const entry = {
12764
- ts: (/* @__PURE__ */ new Date()).toISOString(),
12765
- level,
12766
- component,
12767
- msg: message,
12768
- ...fields
12769
- };
12770
- const line = JSON.stringify(entry);
12771
- if (level === "error" || level === "warn") {
12772
- process.stderr.write(line + "\n");
12773
- } else {
12774
- process.stdout.write(line + "\n");
12775
- }
12776
- }
12777
- return {
12778
- debug: (msg, fields) => log("debug", msg, fields),
12779
- info: (msg, fields) => log("info", msg, fields),
12780
- warn: (msg, fields) => log("warn", msg, fields),
12781
- error: (msg, fields) => log("error", msg, fields)
12782
- };
12783
- }
12784
- var LEVELS, logger;
12785
- var init_logger = __esm({
12786
- "src/util/logger.ts"() {
12787
- LEVELS = {
12788
- debug: 0,
12789
- info: 1,
12790
- warn: 2,
12791
- error: 3
12792
- };
12793
- logger = createLogger("runtime");
12794
- }
12795
- });
12796
13113
  function checkRateLimit(userId) {
12797
13114
  const now = Date.now();
12798
13115
  const entry = rateLimitMap.get(userId);
@@ -13244,8 +13561,6 @@ function runWorker(opts) {
13244
13561
  return query({
13245
13562
  prompt,
13246
13563
  options: {
13247
- debug: true,
13248
- debugFile: "/dev/stderr",
13249
13564
  stderr: (data) => {
13250
13565
  process.stderr.write(`[worker-sdk] ${data}
13251
13566
  `);
@@ -13863,6 +14178,9 @@ async function startAgent(opts) {
13863
14178
  }
13864
14179
  mkdirSync(config.stateDir, { recursive: true });
13865
14180
  mkdirSync(config.toolsDir, { recursive: true });
14181
+ const logFile = join(config.stateDir, "runtime.log");
14182
+ enableFileLogging(logFile);
14183
+ log.info(`File logging enabled: ${logFile}`);
13866
14184
  if (config.tools.length > 0) {
13867
14185
  log.info(`Installing ${config.tools.length} tool package(s)...`);
13868
14186
  await installTools(config.tools, config.toolsDir, log);
@@ -14103,6 +14421,9 @@ if (command === "install") {
14103
14421
  } else if (command === "uninstall") {
14104
14422
  const { uninstall: uninstall2 } = await Promise.resolve().then(() => (init_uninstall(), uninstall_exports));
14105
14423
  uninstall2();
14424
+ } else if (command === "status") {
14425
+ const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
14426
+ status2();
14106
14427
  } else {
14107
14428
  let handleShutdown = function(signal) {
14108
14429
  log.info(`Received ${signal} \u2014 shutting down...`);