@questionbase/deskfree 0.4.5 → 0.5.0

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
- import { execSync, execFileSync, execFile } from 'child_process';
4
- import { writeFileSync, chmodSync, existsSync, unlinkSync, mkdirSync, readdirSync, readFileSync, createWriteStream } from 'fs';
3
+ import { spawn, execSync, execFileSync, execFile } from 'child_process';
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,22 +58,123 @@ 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);
64
148
  }
65
- let npxPath;
149
+ let npmPath;
150
+ let nodeBinDir;
66
151
  try {
67
- npxPath = execSync("which npx", { encoding: "utf8" }).trim();
152
+ npmPath = execSync("which npm", { encoding: "utf8" }).trim();
153
+ nodeBinDir = dirname(npmPath);
68
154
  } catch {
69
- console.error("Error: npx not found in PATH");
155
+ console.error("Error: npm not found in PATH");
70
156
  process.exit(1);
71
157
  }
72
- writeFileSync(ENV_FILE, `DESKFREE_LAUNCH=${token}
73
- `, { mode: 384 });
74
- chmodSync(ENV_FILE, 384);
75
- console.log(`Wrote ${ENV_FILE}`);
158
+ try {
159
+ execSync("id deskfree-agent", { stdio: "ignore" });
160
+ } catch {
161
+ execSync(
162
+ "useradd --system --no-create-home --shell /usr/sbin/nologin deskfree-agent"
163
+ );
164
+ console.log("Created system user: deskfree-agent");
165
+ }
166
+ mkdirSync(LINUX_STATE_DIR, { recursive: true });
167
+ mkdirSync(LINUX_LOG_DIR, { recursive: true });
168
+ execSync(
169
+ `chown deskfree-agent:deskfree-agent ${LINUX_STATE_DIR} ${LINUX_LOG_DIR}`
170
+ );
171
+ console.log(`Created ${LINUX_STATE_DIR} and ${LINUX_LOG_DIR}`);
172
+ writeFileSync(SYSTEMD_ENV_FILE, `DESKFREE_LAUNCH=${token}
173
+ `, {
174
+ mode: 384
175
+ });
176
+ chmodSync(SYSTEMD_ENV_FILE, 384);
177
+ console.log(`Wrote ${SYSTEMD_ENV_FILE}`);
76
178
  const unit = `[Unit]
77
179
  Description=DeskFree Agent
78
180
  After=network-online.target
@@ -80,30 +182,54 @@ Wants=network-online.target
80
182
 
81
183
  [Service]
82
184
  Type=simple
83
- ExecStart=${npxPath} ${PACKAGE} start
84
- EnvironmentFile=${ENV_FILE}
185
+ User=deskfree-agent
186
+ Group=deskfree-agent
187
+ WorkingDirectory=${LINUX_STATE_DIR}
188
+ Environment=PATH=${nodeBinDir}:/usr/local/bin:/usr/bin:/bin
189
+ ExecStartPre=+${npmPath} install -g ${PACKAGE}
190
+ ExecStart=${nodeBinDir}/deskfree-agent start
191
+ EnvironmentFile=${SYSTEMD_ENV_FILE}
85
192
  Environment=NODE_ENV=production
193
+ Environment=DESKFREE_STATE_DIR=${LINUX_STATE_DIR}
194
+ Environment=DESKFREE_TOOLS_DIR=${LINUX_STATE_DIR}/tools
86
195
  Restart=always
87
196
  RestartSec=10
197
+ StandardOutput=append:${LINUX_LOG_DIR}/stdout.log
198
+ StandardError=append:${LINUX_LOG_DIR}/stderr.log
88
199
 
89
200
  [Install]
90
201
  WantedBy=multi-user.target
91
202
  `;
92
- writeFileSync(SERVICE_FILE, unit);
93
- console.log(`Wrote ${SERVICE_FILE}`);
203
+ writeFileSync(SYSTEMD_SERVICE_FILE, unit);
204
+ console.log(`Wrote ${SYSTEMD_SERVICE_FILE}`);
94
205
  execSync("systemctl daemon-reload");
95
206
  execSync(`systemctl enable ${SERVICE_NAME}`);
96
207
  execSync(`systemctl start ${SERVICE_NAME}`);
97
- console.log(`Service ${SERVICE_NAME} installed and started.`);
208
+ console.log(`
209
+ Service ${SERVICE_NAME} installed and started.`);
98
210
  console.log(`Check status: systemctl status ${SERVICE_NAME}`);
211
+ console.log(`Logs: tail -f ${LINUX_LOG_DIR}/stdout.log`);
99
212
  }
100
- var SERVICE_NAME, SERVICE_FILE, ENV_FILE, PACKAGE;
213
+ function install(token) {
214
+ if (process.platform === "darwin") {
215
+ installMac(token);
216
+ } else if (process.platform === "linux") {
217
+ installLinux(token);
218
+ } else {
219
+ console.error(`Unsupported platform: ${process.platform}`);
220
+ process.exit(1);
221
+ }
222
+ }
223
+ var SERVICE_NAME, PACKAGE, PLIST_LABEL, SYSTEMD_SERVICE_FILE, SYSTEMD_ENV_FILE, LINUX_STATE_DIR, LINUX_LOG_DIR;
101
224
  var init_install = __esm({
102
225
  "src/cli/install.ts"() {
103
226
  SERVICE_NAME = "deskfree-agent";
104
- SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
105
- ENV_FILE = `/etc/${SERVICE_NAME}.env`;
106
227
  PACKAGE = "@questionbase/deskfree@latest";
228
+ PLIST_LABEL = "com.deskfree.agent";
229
+ SYSTEMD_SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
230
+ SYSTEMD_ENV_FILE = `/etc/${SERVICE_NAME}.env`;
231
+ LINUX_STATE_DIR = "/var/lib/deskfree-agent";
232
+ LINUX_LOG_DIR = "/var/log/deskfree-agent";
107
233
  }
108
234
  });
109
235
 
@@ -112,7 +238,28 @@ var uninstall_exports = {};
112
238
  __export(uninstall_exports, {
113
239
  uninstall: () => uninstall
114
240
  });
115
- function uninstall() {
241
+ function uninstallMac() {
242
+ const home = homedir();
243
+ const plist = join(home, "Library", "LaunchAgents", `${PLIST_LABEL2}.plist`);
244
+ const deskfreeDir = join(home, ".deskfree");
245
+ const envFile = join(deskfreeDir, ".env");
246
+ const launcher = join(deskfreeDir, "launcher.sh");
247
+ try {
248
+ execSync(`launchctl bootout gui/$(id -u) ${plist}`, { stdio: "ignore" });
249
+ } catch {
250
+ }
251
+ for (const file of [plist, envFile, launcher]) {
252
+ if (existsSync(file)) {
253
+ unlinkSync(file);
254
+ console.log(`Removed ${file}`);
255
+ }
256
+ }
257
+ console.log(`Service ${PLIST_LABEL2} uninstalled.`);
258
+ console.log(
259
+ `Note: logs and state in ${deskfreeDir} were preserved. Remove manually if desired.`
260
+ );
261
+ }
262
+ function uninstallLinux() {
116
263
  if (process.getuid?.() !== 0) {
117
264
  console.error("Error: uninstall must be run as root (use sudo)");
118
265
  process.exit(1);
@@ -125,23 +272,203 @@ function uninstall() {
125
272
  execSync(`systemctl disable ${SERVICE_NAME2}`, { stdio: "ignore" });
126
273
  } catch {
127
274
  }
128
- if (existsSync(SERVICE_FILE2)) {
129
- unlinkSync(SERVICE_FILE2);
130
- console.log(`Removed ${SERVICE_FILE2}`);
275
+ if (existsSync(SYSTEMD_SERVICE_FILE2)) {
276
+ unlinkSync(SYSTEMD_SERVICE_FILE2);
277
+ console.log(`Removed ${SYSTEMD_SERVICE_FILE2}`);
131
278
  }
132
- if (existsSync(ENV_FILE2)) {
133
- unlinkSync(ENV_FILE2);
134
- console.log(`Removed ${ENV_FILE2}`);
279
+ if (existsSync(SYSTEMD_ENV_FILE2)) {
280
+ unlinkSync(SYSTEMD_ENV_FILE2);
281
+ console.log(`Removed ${SYSTEMD_ENV_FILE2}`);
135
282
  }
136
283
  execSync("systemctl daemon-reload");
137
284
  console.log(`Service ${SERVICE_NAME2} uninstalled.`);
138
285
  }
139
- var SERVICE_NAME2, SERVICE_FILE2, ENV_FILE2;
286
+ function uninstall() {
287
+ if (process.platform === "darwin") {
288
+ uninstallMac();
289
+ } else if (process.platform === "linux") {
290
+ uninstallLinux();
291
+ } else {
292
+ console.error(`Unsupported platform: ${process.platform}`);
293
+ process.exit(1);
294
+ }
295
+ }
296
+ var SERVICE_NAME2, PLIST_LABEL2, SYSTEMD_SERVICE_FILE2, SYSTEMD_ENV_FILE2;
140
297
  var init_uninstall = __esm({
141
298
  "src/cli/uninstall.ts"() {
142
299
  SERVICE_NAME2 = "deskfree-agent";
143
- SERVICE_FILE2 = `/etc/systemd/system/${SERVICE_NAME2}.service`;
144
- ENV_FILE2 = `/etc/${SERVICE_NAME2}.env`;
300
+ PLIST_LABEL2 = "com.deskfree.agent";
301
+ SYSTEMD_SERVICE_FILE2 = `/etc/systemd/system/${SERVICE_NAME2}.service`;
302
+ SYSTEMD_ENV_FILE2 = `/etc/${SERVICE_NAME2}.env`;
303
+ }
304
+ });
305
+
306
+ // src/cli/status.ts
307
+ var status_exports = {};
308
+ __export(status_exports, {
309
+ status: () => status
310
+ });
311
+ function statusMac() {
312
+ const home = homedir();
313
+ const plist = join(home, "Library", "LaunchAgents", `${PLIST_LABEL3}.plist`);
314
+ if (!existsSync(plist)) {
315
+ console.log("DeskFree Agent is not installed.");
316
+ console.log(`Run: curl -fsSL https://my.deskfree.ai/install.sh | bash`);
317
+ return;
318
+ }
319
+ console.log("DeskFree Agent (macOS LaunchAgent)\n");
320
+ try {
321
+ const output = execSync(
322
+ `launchctl print gui/$(id -u)/${PLIST_LABEL3} 2>&1`,
323
+ { encoding: "utf8" }
324
+ );
325
+ const pidMatch = output.match(/pid\s*=\s*(\d+)/);
326
+ const stateMatch = output.match(/state\s*=\s*(\w+)/);
327
+ if (pidMatch) console.log(` PID: ${pidMatch[1]}`);
328
+ if (stateMatch) console.log(` State: ${stateMatch[1]}`);
329
+ const logDir = join(home, ".deskfree", "logs");
330
+ console.log(` Logs: ${logDir}/stdout.log`);
331
+ console.log(` Plist: ${plist}`);
332
+ } catch {
333
+ console.log(" Status: not running (service may be unloaded)");
334
+ }
335
+ }
336
+ function statusLinux() {
337
+ const serviceFile = `/etc/systemd/system/${SYSTEMD_SERVICE_NAME}.service`;
338
+ if (!existsSync(serviceFile)) {
339
+ console.log("DeskFree Agent is not installed.");
340
+ console.log(`Run: sudo npx @questionbase/deskfree@latest install <token>`);
341
+ return;
342
+ }
343
+ console.log("DeskFree Agent (systemd service)\n");
344
+ try {
345
+ const output = execSync(
346
+ `systemctl status ${SYSTEMD_SERVICE_NAME} --no-pager 2>&1`,
347
+ { encoding: "utf8" }
348
+ );
349
+ console.log(output);
350
+ } catch (err) {
351
+ if (err && typeof err === "object" && "stdout" in err) {
352
+ console.log(err.stdout);
353
+ } else {
354
+ console.log(" Status: unknown (could not query systemd)");
355
+ }
356
+ }
357
+ console.log(` Logs: /var/log/deskfree-agent/stdout.log`);
358
+ }
359
+ function status() {
360
+ if (process.platform === "darwin") {
361
+ statusMac();
362
+ } else if (process.platform === "linux") {
363
+ statusLinux();
364
+ } else {
365
+ console.error(`Unsupported platform: ${process.platform}`);
366
+ process.exit(1);
367
+ }
368
+ }
369
+ var PLIST_LABEL3, SYSTEMD_SERVICE_NAME;
370
+ var init_status = __esm({
371
+ "src/cli/status.ts"() {
372
+ PLIST_LABEL3 = "com.deskfree.agent";
373
+ SYSTEMD_SERVICE_NAME = "deskfree-agent";
374
+ }
375
+ });
376
+
377
+ // src/cli/restart.ts
378
+ var restart_exports = {};
379
+ __export(restart_exports, {
380
+ restart: () => restart
381
+ });
382
+ function restartMac() {
383
+ const plist = join(
384
+ homedir(),
385
+ "Library",
386
+ "LaunchAgents",
387
+ `${PLIST_LABEL4}.plist`
388
+ );
389
+ if (!existsSync(plist)) {
390
+ console.error("DeskFree Agent is not installed.");
391
+ console.error("Run: curl -fsSL https://my.deskfree.ai/install.sh | bash");
392
+ process.exit(1);
393
+ }
394
+ try {
395
+ execSync(`launchctl bootout gui/$(id -u) ${plist}`, { stdio: "ignore" });
396
+ } catch {
397
+ }
398
+ execSync(`launchctl bootstrap gui/$(id -u) ${plist}`);
399
+ console.log("DeskFree Agent restarted.");
400
+ console.log(
401
+ `Logs: tail -f ${join(homedir(), ".deskfree", "logs", "stdout.log")}`
402
+ );
403
+ }
404
+ function restartLinux() {
405
+ const serviceFile = `/etc/systemd/system/${SYSTEMD_SERVICE_NAME2}.service`;
406
+ if (!existsSync(serviceFile)) {
407
+ console.error("DeskFree Agent is not installed.");
408
+ console.error(
409
+ "Run: sudo npx @questionbase/deskfree@latest install <token>"
410
+ );
411
+ process.exit(1);
412
+ }
413
+ execSync(`systemctl restart ${SYSTEMD_SERVICE_NAME2}`, { stdio: "inherit" });
414
+ console.log("DeskFree Agent restarted.");
415
+ console.log(`Logs: tail -f /var/log/deskfree-agent/stdout.log`);
416
+ }
417
+ function restart() {
418
+ if (process.platform === "darwin") {
419
+ restartMac();
420
+ } else if (process.platform === "linux") {
421
+ restartLinux();
422
+ } else {
423
+ console.error(`Unsupported platform: ${process.platform}`);
424
+ process.exit(1);
425
+ }
426
+ }
427
+ var PLIST_LABEL4, SYSTEMD_SERVICE_NAME2;
428
+ var init_restart = __esm({
429
+ "src/cli/restart.ts"() {
430
+ PLIST_LABEL4 = "com.deskfree.agent";
431
+ SYSTEMD_SERVICE_NAME2 = "deskfree-agent";
432
+ }
433
+ });
434
+
435
+ // src/cli/logs.ts
436
+ var logs_exports = {};
437
+ __export(logs_exports, {
438
+ logs: () => logs
439
+ });
440
+ function getLogPath() {
441
+ if (process.platform === "darwin") {
442
+ const logFile = join(homedir(), ".deskfree", "logs", "stdout.log");
443
+ return existsSync(logFile) ? logFile : null;
444
+ } else if (process.platform === "linux") {
445
+ const logFile = "/var/log/deskfree-agent/stdout.log";
446
+ return existsSync(logFile) ? logFile : null;
447
+ }
448
+ return null;
449
+ }
450
+ function logs(follow) {
451
+ if (process.platform !== "darwin" && process.platform !== "linux") {
452
+ console.error(`Unsupported platform: ${process.platform}`);
453
+ process.exit(1);
454
+ }
455
+ const logPath = getLogPath();
456
+ if (!logPath) {
457
+ console.error("No log file found. Is DeskFree Agent installed?");
458
+ process.exit(1);
459
+ }
460
+ const args2 = follow ? ["-f", "-n", "50", logPath] : ["-n", "50", logPath];
461
+ const child = spawn("tail", args2, { stdio: "inherit" });
462
+ child.on("error", (err) => {
463
+ console.error(`Failed to read logs: ${err.message}`);
464
+ process.exit(1);
465
+ });
466
+ child.on("exit", (code) => {
467
+ process.exit(code ?? 0);
468
+ });
469
+ }
470
+ var init_logs = __esm({
471
+ "src/cli/logs.ts"() {
145
472
  }
146
473
  });
147
474
  function toErrorMessage(error) {
@@ -2554,6 +2881,41 @@ function createWorkerTools(client, options) {
2554
2881
  return errorResult(err);
2555
2882
  }
2556
2883
  }),
2884
+ createTool(WORKER_TOOLS.PROPOSE, async (params) => {
2885
+ try {
2886
+ const context = validateStringParam(params, "context", false);
2887
+ const taskId = validateStringParam(params, "taskId", false);
2888
+ const rawTasks = params.tasks;
2889
+ if (!Array.isArray(rawTasks) || rawTasks.length === 0) {
2890
+ throw new Error("tasks must be a non-empty array of task objects");
2891
+ }
2892
+ const tasks = rawTasks;
2893
+ for (let i = 0; i < tasks.length; i++) {
2894
+ const task = tasks[i];
2895
+ if (!task || typeof task !== "object") {
2896
+ throw new Error(`tasks[${i}] must be an object`);
2897
+ }
2898
+ if (!task.title || typeof task.title !== "string" || task.title.trim() === "") {
2899
+ throw new Error(`tasks[${i}].title must be a non-empty string`);
2900
+ }
2901
+ }
2902
+ await client.proposePlan({
2903
+ context,
2904
+ tasks,
2905
+ taskId
2906
+ });
2907
+ return {
2908
+ content: [
2909
+ {
2910
+ type: "text",
2911
+ text: `Proposal created with ${tasks.length} task(s)`
2912
+ }
2913
+ ]
2914
+ };
2915
+ } catch (err) {
2916
+ return errorResult(err);
2917
+ }
2918
+ }),
2557
2919
  createTool(WORKER_TOOLS.COMPLETE_TASK, async (params) => {
2558
2920
  try {
2559
2921
  const taskId = validateStringParam(params, "taskId", true);
@@ -2577,6 +2939,331 @@ function createWorkerTools(client, options) {
2577
2939
  })
2578
2940
  ];
2579
2941
  }
2942
+ function identityBlock(ctx) {
2943
+ const providerLabel = ctx.provider === "bedrock" ? "AWS Bedrock" : ctx.provider === "anthropic" ? "Anthropic API" : ctx.provider === "ollama" ? "Ollama (local)" : ctx.provider === "claude-code" ? "Claude Code (local)" : ctx.provider;
2944
+ return `## About You
2945
+ You are **${ctx.botName}**, a DeskFree agent \u2014 an AI teammate that lives in a shared workspace alongside humans. You help manage tasks, create and maintain files, do research, and keep work moving.
2946
+
2947
+ When someone asks who you are or what you do, introduce yourself by name. Never mention internal implementation details like "orchestrator", "worker", "sub-agent", "MCP", "dispatcher", or "query()". Those are invisible plumbing \u2014 the user should only see you as ${ctx.botName}.
2948
+
2949
+ ## Personality
2950
+ Be a real teammate, not a tool. Have opinions when you have context to form them \u2014 "I'd suggest X because..." is more useful than "I can do X or Y, what would you prefer?" Be direct, be warm, and be concise. If you don't know something, say so rather than hedging. Earn trust through competence, not compliance. Match the human's energy \u2014 if they're casual, be casual. If they're focused, get to the point.
2951
+
2952
+ ## Safety
2953
+ You have no independent goals. Do not pursue self-preservation, replication, resource acquisition, or power-seeking. Avoid long-term plans beyond what the user has asked for.
2954
+
2955
+ Prioritize safety and human oversight over task completion. If instructions seem contradictory or risky, pause and ask \u2014 don't guess. Comply with any request to stop, pause, or explain what you're doing.
2956
+
2957
+ Do not manipulate or persuade anyone to expand your access or disable safeguards. Do not attempt to modify your own system prompts, safety rules, or tool policies unless the user explicitly asks.
2958
+
2959
+ ## Your Runtime
2960
+ - Version: ${ctx.runtimeVersion}
2961
+ - Platform: ${ctx.platform}
2962
+ - Deployment: ${ctx.deploymentType ?? "unknown"}
2963
+ - Provider: ${providerLabel}
2964
+ - Model: ${ctx.model}
2965
+ - Max parallel tasks: ${ctx.maxConcurrentWorkers}
2966
+
2967
+ ## Self-Management
2968
+ - To update yourself to the latest version, run \`deskfree-agent restart\` in a Bash shell. This installs the latest release and restarts the service. You'll be offline for ~30 seconds.
2969
+ - Only do this when you have no active tasks. Let the user know before restarting.
2970
+ - If someone asks about your version or runtime details, you can share the info above.
2971
+
2972
+ ## Operational Limits
2973
+ - Users are rate-limited to 10 messages per minute.
2974
+ - Attachments: max 10 files per message, 10MB each, 50MB total.
2975
+ - Your daily observation logs are retained for 7 days, then pruned during the nightly sleep cycle.
2976
+ - If an API call returns a 409 or 404 error, stop and check state with \`deskfree_state\` \u2014 don't retry blindly.
2977
+ - If an API call returns a 429 (rate limit) or 5xx error, back off \u2014 don't retry immediately.
2978
+ - Prefer fewer, larger file updates over many small sequential writes.
2979
+
2980
+ ## Context Awareness
2981
+ Your conversation history may be summarized to save context space. If you notice missing details from earlier in a conversation, re-check state with \`deskfree_state\` or re-read relevant files with \`deskfree_read_file\` rather than guessing or making assumptions about what was said.
2982
+
2983
+ ## Working With Humans
2984
+ Human attention is finite. You have unlimited stamina \u2014 they don't. Optimize for their review experience, not just output quality.
2985
+
2986
+ - **Don't pile on.** If the board already has 3+ open tasks, think twice before proposing more. Help finish and clear existing work before adding new items.
2987
+ - **Incremental over monolithic.** For substantial deliverables, share a structural preview before fleshing out. A quick "here's the outline \u2014 does this direction work?" saves everyone time versus a finished wall of text to review.
2988
+ - **Separate FYI from action needed.** Never make the human triage what needs their input. \`notify\` = no action needed. \`ask\` = needs their input. Be precise about which you're sending.
2989
+ - **Fewer, better decisions.** Don't present 5 options when you can recommend 1 with reasoning. Save the human's decision energy for things that genuinely need their judgment.
2990
+ - **Prefer simple output.** A focused 500-word draft beats a comprehensive 2000-word one the human has to pare down. Don't add sections, caveats, or "bonus" content unless asked.`;
2991
+ }
2992
+ function buildAgentDirective(ctx) {
2993
+ return `${identityBlock(ctx)}
2994
+
2995
+ ## How You Work
2996
+
2997
+ **Main thread = short and snappy.** Keep responses to 1-3 sentences. Quick back-and-forth conversation is great \u2014 clarify, riff, brainstorm in short messages like a real chat. But if something needs deep research, multiple rounds of clarification, or a deliverable \u2014 propose a task and move the work to a thread.
2998
+
2999
+ **The core loop:**
3000
+
3001
+ 1. **Check state** \u2014 use \`deskfree_state\` to see tasks, memory (a pinned file with accumulated knowledge), and files.
3002
+ 2. **Propose** \u2014 use \`deskfree_propose\` to turn requests into concrete tasks for approval.
3003
+ 3. **Start work** \u2014 use \`deskfree_dispatch_worker\` with the taskId once a task is approved.
3004
+ 4. **Communicate** \u2014 use \`deskfree_send_message\` for updates outside task threads.
3005
+
3006
+ **Before proposing, qualify the request.** Figure out what kind of thing this is:
3007
+ - **One-off task** ("proofread this") \u2014 propose a task directly.
3008
+ - **New aspiration** ("I want to start posting on LinkedIn") \u2014 don't rush to propose. Ask 1-2 short qualifying questions to understand the real goal.
3009
+ - Never call \`deskfree_propose\` as your very first action \u2014 qualify first, even if briefly.
3010
+
3011
+ **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.
3012
+
3013
+ 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 get work started on each approved task.
3014
+ - When a human writes in a task thread, decide:
3015
+ - **Continuation of the same task?** \u2192 reopen and get it working again.
3016
+ - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one or do the work yourself).
3017
+ - **Just confirmation or deferred?** \u2192 leave it for now.
3018
+ - Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
3019
+ }
3020
+ function buildWorkerDirective(ctx) {
3021
+ return `${identityBlock(ctx)}
3022
+
3023
+ ## Your Role Right Now
3024
+ You're working on a specific task. Your first message contains pre-loaded context \u2014 use it directly.
3025
+
3026
+ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_complete_task, deskfree_send_message, deskfree_propose.
3027
+
3028
+ **Context loading:**
3029
+ - If your first message contains \`<task_context>\`, the task is already claimed and context is pre-loaded. Do NOT call deskfree_start_task \u2014 start working immediately.
3030
+ - If your first message contains \`<workspace_state>\`, use it for situational awareness (other tasks, memory, files).
3031
+ - If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to claim and load context.
3032
+ - If continuing from a previous conversation (you can see prior tool calls and context), respond directly to the human's latest message \u2014 do NOT call deskfree_start_task again.
3033
+
3034
+ **Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
3035
+
3036
+ 1. **Orient** \u2014 Scan workspace state for relevant files. Read the Memory file for context on preferences and past patterns. Read any other files that are useful context with \`deskfree_read_file\`. Don't read everything \u2014 just what's relevant to this task.
3037
+ 2. **Align** \u2014 Send a brief \`notify\` message: what you found, what you'll produce. One or two sentences. ("I'll build on the existing brand guide and create a new tone reference.")
3038
+ - **Judgment calls or creative direction?** State your assumptions and approach, send as \`ask\`, and wait for confirmation before proceeding. Getting alignment early prevents costly rework.
3039
+ - **Straightforward execution?** Proceed immediately after the notify \u2014 don't wait for a response.
3040
+ 3. **Work** \u2014 Execute the task. Update existing files with \`deskfree_update_file\` or create new ones with \`deskfree_create_file\`. Pass your taskId so updates appear in the thread. For large deliverables, build incrementally \u2014 share structure/outline first, then flesh out. Don't produce a finished 2000-word document and ask for review in one shot.
3041
+ 4. **Deliver** \u2014 Send an \`ask\` message when work is ready for review. Only complete (\`deskfree_complete_task\` with humanApproved: true) after the human has confirmed. Never self-complete.
3042
+
3043
+ **Push back when warranted:**
3044
+ - If task instructions seem unclear, contradictory, or misguided \u2014 say so. "This task asks for X, but based on [context], Y might work better because..." is more useful than silently executing a flawed plan.
3045
+ - If you hit genuine ambiguity mid-task, send an \`ask\` message and wait. Don't guess on important decisions \u2014 guessing creates review debt the human has to pay later.
3046
+ - You're a teammate, not a task executor. Have an opinion when you have the context to form one.
3047
+
3048
+ **File rules:**
3049
+ - Create files when your task naturally produces them. Don't be afraid to create multiple files if the work calls for it.
3050
+ - Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
3051
+ - If you discover work that falls outside your task's scope, use \`deskfree_propose\` to suggest follow-up tasks immediately \u2014 don't wait until completion. Propose as you discover, then stay focused on your current task.
3052
+
3053
+ **Learnings:**
3054
+ - Use \`deskfree_learning\` to record observations worth remembering. A nightly sleep cycle consolidates these into the Memory file. Record:
3055
+ - **Preferences**: how the human wants things done ("prefers X over Y")
3056
+ - **Corrections**: when the human corrects you ("actually, do X not Y")
3057
+ - **Patterns**: recurring approaches that work ("for this type of task, always...")
3058
+ - **Domain facts**: business-specific knowledge not in project docs
3059
+ - Prefix critical observations with [!] (corrections, constraints, errors).
3060
+ - Prefix notable observations with [~] (preferences, patterns).
3061
+ - Leave routine observations unprefixed.
3062
+ - Do NOT record one-time task details, things in project docs, or obvious/generic knowledge.
3063
+ - If your first message contains \`<daily_observations>\`, these are recent raw observations not yet consolidated into Memory. Use them as additional context.
3064
+
3065
+ **Sub-agents & delegation:**
3066
+ - Your context window is finite. Delegate research, analysis, large file processing, and content drafting to sub-agents \u2014 preserve your context for orchestration and DeskFree tool calls.
3067
+ - Sub-agents get a fresh context window with standard tools (Read, Write, Bash, Grep, WebSearch, etc.) but NO DeskFree tools. Pre-load any file content they need into the prompt.
3068
+ - Sub-agents are ephemeral helpers \u2014 they complete their assigned task and nothing else. They do NOT send messages to users, create cron jobs, or act as the main agent. Their final output is returned to you.
3069
+ - Use \`run_in_background: true\` for parallel independent work.
3070
+ - During Orient, check Memory for sub-agent helper patterns. Inject relevant ones into the sub-agent prompt alongside the task.
3071
+ - After a sub-agent completes, reflect: did this reveal a useful delegation pattern? Something to do differently? Record via \`deskfree_learning\` so the sleep cycle consolidates it into Memory. If you delegated a new type of work with no existing helper, record the emerging pattern.
3072
+ - Don't over-delegate: quick reads, simple lookups, and anything requiring DeskFree tools are faster inline.
3073
+
3074
+ **Completing tasks:**
3075
+ - On 409 or 404 errors: STOP. Do not retry. Call deskfree_state to find available tasks.`;
3076
+ }
3077
+ function buildHeartbeatDirective(ctx) {
3078
+ return `${identityBlock(ctx)}
3079
+
3080
+ ## Heartbeat Check
3081
+ On each heartbeat, run through this checklist:
3082
+
3083
+ ### 1. Work the queue
3084
+ - Run \`deskfree_state\` to get the full workspace snapshot.
3085
+ - **Check board load.** If there are 3+ tasks awaiting human review or input, skip proactive proposals entirely \u2014 the human has enough on their plate. Focus only on dispatching approved work.
3086
+ - Any open tasks with awaiting=bot? Use \`deskfree_dispatch_worker\` to get each one started. Pass the taskId.
3087
+ - Any open tasks that seem stalled (claimed but no recent activity)? Check on them.
3088
+
3089
+ ### 2. Proactive assessment
3090
+ After handling the queue, step back and think about the bigger picture. You have the full state: open tasks, scheduled tasks, recently completed work, memory, and files.
3091
+
3092
+ **Think through:**
3093
+ - What momentum exists? What was recently accomplished, and what naturally follows?
3094
+ - What is stalled or falling behind? Anything open too long without progress?
3095
+ - What is scheduled soon? Does anything need prep work before it lands?
3096
+ - What is the single highest-leverage thing that could happen next?
3097
+
3098
+ **Then act \u2014 but only if you have something genuinely useful:**
3099
+
3100
+ *Things you can do* \u2014 research, drafts, analysis, prep. Propose as a task via \`deskfree_propose\`. One focused task, not a batch.
3101
+
3102
+ *Things the human should do* \u2014 nudges, reminders, conversation starters. Send via \`deskfree_send_message\`. Keep it brief and genuinely helpful, not nagging.
3103
+
3104
+ **Rules:**
3105
+ - Do not suggest things that are already open or scheduled \u2014 check first.
3106
+ - Do not repeat suggestions the human ignored or rejected recently.
3107
+ - Quality over quantity. One good insight beats five generic nudges.
3108
+ - If everything looks healthy and active, do nothing. Silence is fine.`;
3109
+ }
3110
+ function buildSleepDirective(ctx) {
3111
+ return `${identityBlock(ctx)}
3112
+
3113
+ ## Nightly Sleep Cycle
3114
+ You're running your nightly cycle to reflect, consolidate memory, and prepare for tomorrow.
3115
+
3116
+ Tools available: deskfree_state, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_update_file.
3117
+
3118
+ ---
3119
+
3120
+ ### 1. REFLECT & CONSOLIDATE
3121
+
3122
+ Your primary job: merge daily observations into the Memory file using strength-scored entries.
3123
+
3124
+ Your prompt contains both \`<current_memory>\` (the current Memory file content) and \`<daily_observations>\` (raw daily logs) inline \u2014 you have everything you need upfront.
3125
+
3126
+ **Steps:**
3127
+ 1. Review the current memory and daily observations from your prompt.
3128
+ 2. Apply the memory curation rules below.
3129
+ 3. Update the Memory file via \`deskfree_update_file\` using the file ID from your prompt.
3130
+ 4. After updating, send a brief main-thread message via \`deskfree_send_message\` noting what was consolidated (e.g. "Reflected on yesterday \u2014 updated my notes on your brand voice preferences and the weekly review pattern."). Keep it to 1-2 sentences.
3131
+ 5. If nothing meaningful changed, skip the update and send a brief message like "Quiet day \u2014 nothing new to consolidate."
3132
+
3133
+ **Memory Types**
3134
+
3135
+ Each memory item carries a type tag and strength score: \`[type:X, strength:N]\`
3136
+
3137
+ | Type | What it captures | Decay rate |
3138
+ |------|-----------------|------------|
3139
+ | \`correction\` | Explicit "do X not Y" from human | Very slow (\u22121 when strength >=10, else no decay) |
3140
+ | \`preference\` | How the human wants things done | Slow (\u22121 when strength >=6, else no decay) |
3141
+ | \`pattern\` | Approaches/workflows that work | Normal (see decay rules) |
3142
+ | \`domain\` | Business/project-specific facts | Slow (\u22121 when strength >=6, else no decay) |
3143
+ | \`insight\` | Meta-observations from reflection | Normal (see decay rules) |
3144
+
3145
+ Corrections and domain facts are durable \u2014 they rarely become irrelevant.
3146
+ Patterns and insights decay normally \u2014 stale approaches should be forgotten.
3147
+ Preferences sit in between \u2014 slow decay, but they can evolve.
3148
+
3149
+ **Strength Scoring Rules**
3150
+
3151
+ Strength uses Ebbinghaus-inspired logarithmic decay \u2014 strong memories resist forgetting:
3152
+
3153
+ **Reinforcement (observation matches existing memory):**
3154
+ - strength +2
3155
+
3156
+ **Explicit correction ("actually do X not Y"):**
3157
+ - Replace old memory, new one starts at [strength: 3, type: correction]
3158
+
3159
+ **Decay (memory NOT referenced by any daily observation):**
3160
+ - strength >= 10: decay by \u22121 (deeply encoded, slow forgetting)
3161
+ - strength 5-9: decay by \u22122 (moderately encoded)
3162
+ - strength 1-4: decay by \u22123 (weakly encoded, fast forgetting)
3163
+ - EXCEPT: corrections and domain facts with strength >=6 decay at \u22121 max (durable memories)
3164
+ - EXCEPT: preferences with strength >=6 decay at \u22121 max
3165
+
3166
+ **Removal:**
3167
+ - Strength reaches 0 \u2192 move to a ## Fading section at the bottom (one-line summaries only, no strength tags). Keep max 10 fading memories. If Fading section is full, oldest entries are permanently forgotten. You may rescue a fading memory back to active if it becomes relevant again (re-add with [strength: 2]).
3168
+
3169
+ **New observation:**
3170
+ - Assess importance: how consequential is this for future tasks?
3171
+ - Look for [!] prefix (critical) or [~] prefix (notable) as importance signals.
3172
+ - Low importance (casual mention, routine) \u2192 [strength: 1, type: <appropriate>]
3173
+ - Medium importance (useful preference, [~] prefix) \u2192 [strength: 2, type: <appropriate>]
3174
+ - High importance (explicit correction, strong constraint, [!] prefix) \u2192 [strength: 4, type: <appropriate>]
3175
+ - Critical (production error, absolute rule) \u2192 [strength: 6, type: <appropriate>]
3176
+
3177
+ **Consolidation rules:**
3178
+ - Classify each memory with the correct type. Only keep genuinely reusable knowledge.
3179
+ - Discard noise and one-off task details.
3180
+ - If the same episodic observation appears 3+ times across different days, promote it to a \`pattern\` or \`domain\` fact with [strength: 5]. Remove the individual episodes.
3181
+ - Look for meta-patterns: approaches that consistently work/fail, implicit preferences, recurring corrections.
3182
+ - If you find non-obvious insights, add them as \`[type: insight, strength: 2]\` memories.
3183
+ - Merge near-duplicates, remove items at [strength: 0], trim verbose entries.
3184
+ - Keep total document under ~4000 words.
3185
+ - Use ## headers for sections \u2014 let them emerge organically from content.
3186
+
3187
+ ### 2. CHECK RECURRING COMMITMENTS
3188
+
3189
+ After memory consolidation:
3190
+ 1. Call \`deskfree_state\` to see the board.
3191
+ 2. Cross-reference memory for recurring patterns (daily audits, weekly reviews, etc.).
3192
+ 3. If a recurring commitment has no upcoming scheduled task, propose it via \`deskfree_propose\` with appropriate \`scheduledFor\`.
3193
+ 4. Don't propose things already on the board or that were recently completed/ignored.
3194
+
3195
+ ### 3. PROACTIVE OPPORTUNITIES
3196
+
3197
+ Based on recent work, completed tasks, and patterns in memory \u2014 is there something genuinely useful to propose or a message worth sending?
3198
+ - **Check board load first.** If the human already has 3+ items needing their attention, skip proposals entirely.
3199
+ - One focused proposal max. Skip if nothing merits it.
3200
+ - Quality over quantity. Don't force it.
3201
+
3202
+ ### Rules
3203
+ - Keep main thread messages short (1-2 sentences).
3204
+ - Do NOT propose things the human has previously ignored or rejected.
3205
+ - Use \`deskfree_read_file\` only if you need to re-read the Memory file after your own update (verification). The current content is already in your prompt.`;
3206
+ }
3207
+ function buildDuskDirective(ctx) {
3208
+ return `${identityBlock(ctx)}
3209
+
3210
+ ## Evening Dusk Cycle
3211
+ You're running your evening cycle to review the day, propose overnight work, and brief the human.
3212
+
3213
+ Tools available: deskfree_state, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_update_file.
3214
+
3215
+ ---
3216
+
3217
+ ### 1. REVIEW THE DAY
3218
+
3219
+ Your prompt contains both \`<current_memory>\` (the current Memory file content) and \`<daily_observations>\` (raw daily logs) inline \u2014 you have everything you need upfront.
3220
+
3221
+ **Steps:**
3222
+ 1. Review the current memory and daily observations from your prompt.
3223
+ 2. Call \`deskfree_state\` to see the current board \u2014 open tasks, recently completed work, files.
3224
+ 3. Build a mental model of what happened today: what was accomplished, what's in progress, what's stalled.
3225
+
3226
+ ### 2. IDENTIFY OVERNIGHT OPPORTUNITIES
3227
+
3228
+ Think about work that can be done autonomously overnight \u2014 WITHOUT human judgment or approval mid-task.
3229
+
3230
+ **Good overnight work:**
3231
+ - Research and analysis (market research, competitive analysis, reading material)
3232
+ - Content drafts (blog posts, reports, documentation)
3233
+ - File updates (updating recurring reports, preparing templates)
3234
+ - Prep work for tomorrow (gathering context, pre-reading for scheduled meetings)
3235
+ - Recurring tasks that are due (weekly reviews, daily audits)
3236
+
3237
+ **NOT good overnight work:**
3238
+ - Anything requiring human decisions mid-task
3239
+ - Tasks that depend on external input not yet received
3240
+ - Creative work where the human has strong opinions on direction
3241
+ - Anything the human explicitly said to wait on
3242
+
3243
+ ### 3. PROPOSE THE PLAN
3244
+
3245
+ If you identified useful overnight work:
3246
+ 1. **Check board load first.** Count open and awaiting-review tasks. If the human already has 3+ items needing their attention, limit to 1 proposal max \u2014 or skip entirely. Don't pile on.
3247
+ 2. Use \`deskfree_propose\` with 1-3 well-scoped tasks. Quality over quantity.
3248
+ 3. Each task should be self-contained \u2014 it must be completable without human input.
3249
+ 4. Set \`scheduledFor\` if work should start at a specific time (e.g. early morning).
3250
+ 5. If nothing genuinely useful can be done overnight, skip the proposal entirely. Don't force it.
3251
+
3252
+ ### 4. BRIEF THE HUMAN
3253
+
3254
+ Send a brief main-thread message via \`deskfree_send_message\`:
3255
+ - 2-3 sentence summary: what happened today + what you're proposing for overnight (if anything).
3256
+ - Keep it conversational and useful \u2014 this is the human's "end of day" touchpoint.
3257
+ - If no proposals, still send a brief day summary.
3258
+
3259
+ ### Rules
3260
+ - Do NOT propose things already on the board or recently completed.
3261
+ - Do NOT repeat suggestions the human previously ignored or rejected.
3262
+ - Quality over quantity. One good task beats three mediocre ones.
3263
+ - Keep the briefing message short and actionable.
3264
+ - Cross-reference memory for recurring patterns \u2014 if something is due, propose it.
3265
+ - Use \`deskfree_read_file\` only if you need file content beyond what's in your prompt.`;
3266
+ }
2580
3267
  function setActiveWs(ws) {
2581
3268
  activeWs = ws;
2582
3269
  }
@@ -2780,7 +3467,7 @@ function validateApiUrl(value) {
2780
3467
  "API URL must use HTTPS protocol for security. Make sure your DeskFree deployment supports HTTPS."
2781
3468
  );
2782
3469
  }
2783
- var require2, __create2, __defProp2, __getOwnPropDesc2, __getOwnPropNames2, __getProtoOf2, __hasOwnProp2, __require2, __commonJS2, __export2, __copyProps2, __toESM2, require_constants, require_buffer_util, require_limiter, require_permessage_deflate, require_validation, require_receiver, require_sender, require_event_target, require_extension, require_websocket, require_stream, require_subprotocol, require_websocket_server, DEFAULT_REQUEST_TIMEOUT_MS, DeskFreeError, DeskFreeClient, value_exports, TypeSystemPolicy, TypeBoxError, TransformKind, ReadonlyKind, OptionalKind, Hint, Kind, type_exports, TypeGuardUnknownTypeError, KnownTypes, PatternBoolean, PatternNumber, PatternString, PatternNever, PatternNumberExact, PatternStringExact, PatternNeverExact, TemplateLiteralParserError, TemplateLiteralFiniteError, TemplateLiteralGenerateError, TemplateLiteralPatternError, Object2, includePatternProperties, ExtendsResolverError, ExtendsResult, TModule, Ordinal, TransformDecodeBuilder, TransformEncodeBuilder, type_exports2, Type, ORCHESTRATOR_TOOLS, SHARED_TOOLS, WORKER_TOOLS, MAX_FULL_MESSAGES, DESKFREE_AGENT_DIRECTIVE, DESKFREE_WORKER_DIRECTIVE, DESKFREE_HEARTBEAT_DIRECTIVE, DESKFREE_SLEEP_DIRECTIVE, DESKFREE_DUSK_DIRECTIVE, THROTTLE_MS, CHAR_BUFFER_SIZE, CLOSE_MAX_RETRIES, DeskFreeStreamingSession, import_websocket, wrapper_default, activeWs, activeTaskId, completedTaskId, inboundThreadId, FLUSH_INTERVAL_MS, MAX_BATCH_SIZE, MAX_QUEUE_SIZE, _instance, ErrorReporter, accountHealth;
3470
+ var require2, __create2, __defProp2, __getOwnPropDesc2, __getOwnPropNames2, __getProtoOf2, __hasOwnProp2, __require2, __commonJS2, __export2, __copyProps2, __toESM2, require_constants, require_buffer_util, require_limiter, require_permessage_deflate, require_validation, require_receiver, require_sender, require_event_target, require_extension, require_websocket, require_stream, require_subprotocol, require_websocket_server, DEFAULT_REQUEST_TIMEOUT_MS, DeskFreeError, DeskFreeClient, value_exports, TypeSystemPolicy, TypeBoxError, TransformKind, ReadonlyKind, OptionalKind, Hint, Kind, type_exports, TypeGuardUnknownTypeError, KnownTypes, PatternBoolean, PatternNumber, PatternString, PatternNever, PatternNumberExact, PatternStringExact, PatternNeverExact, TemplateLiteralParserError, TemplateLiteralFiniteError, TemplateLiteralGenerateError, TemplateLiteralPatternError, Object2, includePatternProperties, ExtendsResolverError, ExtendsResult, TModule, Ordinal, TransformDecodeBuilder, TransformEncodeBuilder, type_exports2, Type, ORCHESTRATOR_TOOLS, SHARED_TOOLS, WORKER_TOOLS, MAX_FULL_MESSAGES, DESKFREE_AGENT_DIRECTIVE, DESKFREE_WORKER_DIRECTIVE, THROTTLE_MS, CHAR_BUFFER_SIZE, CLOSE_MAX_RETRIES, DeskFreeStreamingSession, import_websocket, wrapper_default, activeWs, activeTaskId, completedTaskId, inboundThreadId, FLUSH_INTERVAL_MS, MAX_BATCH_SIZE, MAX_QUEUE_SIZE, _instance, ErrorReporter, accountHealth;
2784
3471
  var init_dist = __esm({
2785
3472
  "../core/dist/index.js"() {
2786
3473
  require2 = createRequire$1(import.meta.url);
@@ -6411,96 +7098,96 @@ var init_dist = __esm({
6411
7098
  this.statusCode = statusCode;
6412
7099
  }
6413
7100
  static fromResponse(response, procedure, responseText) {
6414
- const status = response.status;
7101
+ const status2 = response.status;
6415
7102
  const statusText = response.statusText;
6416
- if (status === 401) {
7103
+ if (status2 === 401) {
6417
7104
  return new _DeskFreeError(
6418
7105
  "auth",
6419
7106
  procedure,
6420
- `Authentication failed: ${status} ${statusText} \u2014 ${responseText}`,
7107
+ `Authentication failed: ${status2} ${statusText} \u2014 ${responseText}`,
6421
7108
  "Your bot token is invalid or has expired. Please check your authentication credentials.",
6422
- status
7109
+ status2
6423
7110
  );
6424
7111
  }
6425
- if (status === 403) {
7112
+ if (status2 === 403) {
6426
7113
  return new _DeskFreeError(
6427
7114
  "auth",
6428
7115
  procedure,
6429
- `Authorization failed: ${status} ${statusText} \u2014 ${responseText}`,
7116
+ `Authorization failed: ${status2} ${statusText} \u2014 ${responseText}`,
6430
7117
  "Your bot does not have permission to perform this action. Contact your administrator.",
6431
- status
7118
+ status2
6432
7119
  );
6433
7120
  }
6434
- if (status === 400) {
7121
+ if (status2 === 400) {
6435
7122
  return new _DeskFreeError(
6436
7123
  "client",
6437
7124
  procedure,
6438
- `Bad request: ${status} ${statusText} \u2014 ${responseText}`,
7125
+ `Bad request: ${status2} ${statusText} \u2014 ${responseText}`,
6439
7126
  "The request was invalid. Please check your input parameters and try again.",
6440
- status
7127
+ status2
6441
7128
  );
6442
7129
  }
6443
- if (status === 404) {
7130
+ if (status2 === 404) {
6444
7131
  return new _DeskFreeError(
6445
7132
  "client",
6446
7133
  procedure,
6447
- `Resource not found: ${status} ${statusText} \u2014 ${responseText}`,
7134
+ `Resource not found: ${status2} ${statusText} \u2014 ${responseText}`,
6448
7135
  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
7136
+ status2
6450
7137
  );
6451
7138
  }
6452
- if (status === 409) {
7139
+ if (status2 === 409) {
6453
7140
  return new _DeskFreeError(
6454
7141
  "client",
6455
7142
  procedure,
6456
- `Conflict: ${status} ${statusText} \u2014 ${responseText}`,
7143
+ `Conflict: ${status2} ${statusText} \u2014 ${responseText}`,
6457
7144
  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
7145
+ status2
6459
7146
  );
6460
7147
  }
6461
- if (status === 422) {
7148
+ if (status2 === 422) {
6462
7149
  return new _DeskFreeError(
6463
7150
  "client",
6464
7151
  procedure,
6465
- `Validation failed: ${status} ${statusText} \u2014 ${responseText}`,
7152
+ `Validation failed: ${status2} ${statusText} \u2014 ${responseText}`,
6466
7153
  "The request data failed validation. Please check all required fields and data formats.",
6467
- status
7154
+ status2
6468
7155
  );
6469
7156
  }
6470
- if (status === 429) {
7157
+ if (status2 === 429) {
6471
7158
  return new _DeskFreeError(
6472
7159
  "client",
6473
7160
  procedure,
6474
- `Rate limit exceeded: ${status} ${statusText} \u2014 ${responseText}`,
7161
+ `Rate limit exceeded: ${status2} ${statusText} \u2014 ${responseText}`,
6475
7162
  "Too many requests. Please wait a moment before trying again.",
6476
- status
7163
+ status2
6477
7164
  );
6478
7165
  }
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.";
7166
+ if (status2 >= 500 && status2 < 600) {
7167
+ 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
7168
  return new _DeskFreeError(
6482
7169
  "server",
6483
7170
  procedure,
6484
- `Server error: ${status} ${statusText} \u2014 ${responseText}`,
7171
+ `Server error: ${status2} ${statusText} \u2014 ${responseText}`,
6485
7172
  serverErrorMessage,
6486
- status
7173
+ status2
6487
7174
  );
6488
7175
  }
6489
- if (status >= 400 && status < 500) {
7176
+ if (status2 >= 400 && status2 < 500) {
6490
7177
  return new _DeskFreeError(
6491
7178
  "client",
6492
7179
  procedure,
6493
- `Client error: ${status} ${statusText} \u2014 ${responseText}`,
7180
+ `Client error: ${status2} ${statusText} \u2014 ${responseText}`,
6494
7181
  "The request was not accepted by the server. Please check your input and try again.",
6495
- status
7182
+ status2
6496
7183
  );
6497
7184
  }
6498
7185
  return new _DeskFreeError(
6499
7186
  "client",
6500
7187
  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
7188
+ `HTTP error: ${status2} ${statusText} \u2014 ${responseText}`,
7189
+ `Request failed with error ${status2}. Please try again or contact support if the problem persists.`,
7190
+ status2
6504
7191
  );
6505
7192
  }
6506
7193
  static timeout(procedure, timeoutMs) {
@@ -7327,261 +8014,97 @@ var init_dist = __esm({
7327
8014
  description: "Record a learning \u2014 an observation worth remembering for future tasks. Saved to a daily log and consolidated into Memory during the nightly sleep cycle. Call as many times as needed.",
7328
8015
  parameters: Type.Object({
7329
8016
  content: Type.String({
7330
- description: 'What you learned. Prefix with importance: [!] for critical (corrections, constraints), [~] for notable (preferences, patterns), or nothing for routine observations. Focus on: PREFERENCES, CORRECTIONS, PATTERNS, DOMAIN FACTS. Be specific. Bad: "Created a table". Good: "[!] User corrected: never use semicolons in this codebase".'
7331
- }),
7332
- taskId: Type.Optional(
7333
- Type.String({
7334
- description: "Task ID (optional if context provides it)"
7335
- })
7336
- )
7337
- })
7338
- },
7339
- COMPLETE_TASK: SHARED_TOOLS.COMPLETE_TASK,
7340
- SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
7341
- PROPOSE: SHARED_TOOLS.PROPOSE
7342
- };
7343
- MAX_FULL_MESSAGES = 15;
7344
- DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 Orchestrator
7345
- You are the orchestrator. Your job: turn human intent into approved tasks, then dispatch work.
7346
-
7347
- **Main thread = short and snappy.** Keep responses to 1-3 sentences. Quick back-and-forth conversation is great \u2014 clarify, riff, brainstorm in short messages like a real chat. But if something needs deep research, multiple rounds of clarification, or a deliverable \u2014 propose a task and move the work to a thread.
7348
-
7349
- **The core loop: propose \u2192 approve \u2192 work.**
7350
-
7351
- 1. **Check state** \u2192 \`deskfree_state\` \u2014 see tasks, memory (a pinned file with accumulated knowledge), and files.
7352
- 2. **Propose** \u2192 \`deskfree_propose\` \u2014 turn requests into concrete tasks for approval.
7353
- 3. **Dispatch** \u2192 \`deskfree_dispatch_worker\` with the taskId.
7354
- 4. **Communicate** \u2192 \`deskfree_send_message\` for updates outside task threads.
7355
-
7356
- **Before proposing, qualify the request.** Figure out what kind of thing this is:
7357
- - **One-off task** ("proofread this") \u2192 propose a task directly.
7358
- - **New aspiration** ("I want to start posting on LinkedIn") \u2192 don't rush to propose. Ask 1-2 short qualifying questions to understand the real goal.
7359
- - Never call \`deskfree_propose\` as your very first action \u2014 qualify first, even if briefly.
7360
-
7361
- **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
-
7363
- 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.
7365
- - Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
7366
- DESKFREE_WORKER_DIRECTIVE = `## DeskFree Worker
7367
- You are a worker sub-agent. Your first message contains pre-loaded context \u2014 use it directly.
7368
- Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_complete_task, deskfree_send_message, deskfree_propose.
7369
-
7370
- **Context loading:**
7371
- - If your first message contains \`<task_context>\`, the task is already claimed and context is pre-loaded. Do NOT call deskfree_start_task \u2014 start working immediately.
7372
- - If your first message contains \`<workspace_state>\`, use it for situational awareness (other tasks, memory, files).
7373
- - If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to claim and load context.
7374
- - If continuing from a previous conversation (you can see prior tool calls and context), respond directly to the human's latest message \u2014 do NOT call deskfree_start_task again.
7375
-
7376
- **Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
7377
-
7378
- 1. **Orient** \u2014 Scan workspace state for relevant files. Read the Memory file for context on preferences and past patterns. Read any other files that are useful context with \`deskfree_read_file\`. Don't read everything \u2014 just what's relevant to this task.
7379
- 2. **Align** \u2014 Send a brief \`notify\` message: what you found, what you'll produce. One or two sentences. ("I'll build on the existing brand guide and create a new tone reference.") Then proceed \u2014 don't wait for a response.
7380
- 3. **Work** \u2014 Execute the task. Update existing files with \`deskfree_update_file\` or create new ones with \`deskfree_create_file\`. Pass your taskId so updates appear in the thread. Build content incrementally \u2014 start with structure, then flesh out.
7381
- 4. **Deliver** \u2014 Send an \`ask\` message when work is ready for review. Only complete (\`deskfree_complete_task\` with humanApproved: true) after the human has confirmed. Never self-complete.
7382
-
7383
- **File rules:**
7384
- - Create files when your task naturally produces them. Don't be afraid to create multiple files if the work calls for it.
7385
- - Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
7386
- - If you discover work that falls outside your task's scope, use \`deskfree_propose\` to suggest follow-up tasks immediately \u2014 don't wait until completion. Propose as you discover, then stay focused on your current task.
7387
-
7388
- **Learnings:**
7389
- - Use \`deskfree_learning\` to record observations worth remembering. A nightly sleep cycle consolidates these into the Memory file. Record:
7390
- - **Preferences**: how the human wants things done ("prefers X over Y")
7391
- - **Corrections**: when the human corrects you ("actually, do X not Y")
7392
- - **Patterns**: recurring approaches that work ("for this type of task, always...")
7393
- - **Domain facts**: business-specific knowledge not in project docs
7394
- - Prefix critical observations with [!] (corrections, constraints, errors).
7395
- - Prefix notable observations with [~] (preferences, patterns).
7396
- - Leave routine observations unprefixed.
7397
- - Do NOT record one-time task details, things in project docs, or obvious/generic knowledge.
7398
- - If your first message contains \`<daily_observations>\`, these are recent raw observations not yet consolidated into Memory. Use them as additional context.
7399
-
7400
- **Sub-agents & delegation:**
7401
- - Your context window is finite. Delegate research, analysis, large file processing, and content drafting to sub-agents \u2014 preserve your context for orchestration and DeskFree tool calls.
7402
- - Sub-agents get a fresh context window with standard tools (Read, Write, Bash, Grep, WebSearch, etc.) but NO DeskFree tools. Pre-load any file content they need into the prompt.
7403
- - Use \`run_in_background: true\` for parallel independent work.
7404
- - During Orient, check Memory for sub-agent helper patterns. Inject relevant ones into the sub-agent prompt alongside the task.
7405
- - After a sub-agent completes, reflect: did this reveal a useful delegation pattern? Something to do differently? Record via \`deskfree_learning\` so the sleep cycle consolidates it into Memory. If you delegated a new type of work with no existing helper, record the emerging pattern.
7406
- - Don't over-delegate: quick reads, simple lookups, and anything requiring DeskFree tools are faster inline.
7407
-
7408
- **Completing tasks:**
7409
- - On 409 or 404 errors: STOP. Do not retry. Call deskfree_state to find available tasks.`;
7410
- DESKFREE_HEARTBEAT_DIRECTIVE = `## DeskFree Heartbeat
7411
- On each heartbeat, run through this checklist:
7412
-
7413
- ### 1. Work the queue
7414
- - Run \`deskfree_state\` to get the full workspace snapshot.
7415
- - Any open tasks with awaiting=bot? Use \`deskfree_dispatch_worker\` to dispatch a worker for each. Pass the taskId.
7416
- - Any open tasks that seem stalled (claimed but no recent activity)? Check on them.
7417
-
7418
- ### 2. Proactive assessment
7419
- After handling the queue, step back and think about the bigger picture. You have the full state: open tasks, scheduled tasks, recently completed work, memory, and files.
7420
-
7421
- **Think through:**
7422
- - What momentum exists? What was recently accomplished, and what naturally follows?
7423
- - What is stalled or falling behind? Anything open too long without progress?
7424
- - What is scheduled soon? Does anything need prep work before it lands?
7425
- - What is the single highest-leverage thing that could happen next?
7426
-
7427
- **Then act \u2014 but only if you have something genuinely useful:**
7428
-
7429
- *Things the bot can do* \u2014 research, drafts, analysis, prep. Propose as a task via \`deskfree_propose\`. One focused task, not a batch.
7430
-
7431
- *Things the human should do* \u2014 nudges, reminders, conversation starters. Send via \`deskfree_send_message\`. Keep it brief and genuinely helpful, not nagging.
7432
-
7433
- **Rules:**
7434
- - Do not suggest things that are already open or scheduled \u2014 check first.
7435
- - Do not repeat suggestions the human ignored or rejected recently.
7436
- - Quality over quantity. One good insight beats five generic nudges.
7437
- - If everything looks healthy and active, do nothing. Silence is fine.`;
7438
- DESKFREE_SLEEP_DIRECTIVE = `## DeskFree \u2014 Nightly Sleep Cycle
7439
- You are the sleep agent. You run once per day to reflect, consolidate memory, and prepare for tomorrow.
7440
-
7441
- Tools available: deskfree_state, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_update_file.
7442
-
7443
- ---
7444
-
7445
- ### 1. REFLECT & CONSOLIDATE
7446
-
7447
- Your primary job: merge daily observations into the Memory file using strength-scored entries.
7448
-
7449
- Your prompt contains both \`<current_memory>\` (the current Memory file content) and \`<daily_observations>\` (raw daily logs) inline \u2014 you have everything you need upfront.
7450
-
7451
- **Steps:**
7452
- 1. Review the current memory and daily observations from your prompt.
7453
- 2. Apply the memory curation rules below.
7454
- 3. Update the Memory file via \`deskfree_update_file\` using the file ID from your prompt.
7455
- 4. After updating, send a brief main-thread message via \`deskfree_send_message\` noting what was consolidated (e.g. "Reflected on yesterday \u2014 updated my notes on your brand voice preferences and the weekly review pattern."). Keep it to 1-2 sentences.
7456
- 5. If nothing meaningful changed, skip the update and send a brief message like "Quiet day \u2014 nothing new to consolidate."
7457
-
7458
- **Memory Types**
7459
-
7460
- Each memory item carries a type tag and strength score: \`[type:X, strength:N]\`
7461
-
7462
- | Type | What it captures | Decay rate |
7463
- |------|-----------------|------------|
7464
- | \`correction\` | Explicit "do X not Y" from human | Very slow (\u22121 when strength \u226510, else no decay) |
7465
- | \`preference\` | How the human wants things done | Slow (\u22121 when strength \u22656, else no decay) |
7466
- | \`pattern\` | Approaches/workflows that work | Normal (see decay rules) |
7467
- | \`domain\` | Business/project-specific facts | Slow (\u22121 when strength \u22656, else no decay) |
7468
- | \`insight\` | Meta-observations from reflection | Normal (see decay rules) |
7469
-
7470
- Corrections and domain facts are durable \u2014 they rarely become irrelevant.
7471
- Patterns and insights decay normally \u2014 stale approaches should be forgotten.
7472
- Preferences sit in between \u2014 slow decay, but they can evolve.
7473
-
7474
- **Strength Scoring Rules**
7475
-
7476
- Strength uses Ebbinghaus-inspired logarithmic decay \u2014 strong memories resist forgetting:
7477
-
7478
- **Reinforcement (observation matches existing memory):**
7479
- - strength +2
7480
-
7481
- **Explicit correction ("actually do X not Y"):**
7482
- - Replace old memory, new one starts at [strength: 3, type: correction]
7483
-
7484
- **Decay (memory NOT referenced by any daily observation):**
7485
- - strength \u2265 10: decay by \u22121 (deeply encoded, slow forgetting)
7486
- - strength 5-9: decay by \u22122 (moderately encoded)
7487
- - strength 1-4: decay by \u22123 (weakly encoded, fast forgetting)
7488
- - EXCEPT: corrections and domain facts with strength \u22656 decay at \u22121 max (durable memories)
7489
- - EXCEPT: preferences with strength \u22656 decay at \u22121 max
7490
-
7491
- **Removal:**
7492
- - Strength reaches 0 \u2192 move to a ## Fading section at the bottom (one-line summaries only, no strength tags). Keep max 10 fading memories. If Fading section is full, oldest entries are permanently forgotten. You may rescue a fading memory back to active if it becomes relevant again (re-add with [strength: 2]).
7493
-
7494
- **New observation:**
7495
- - Assess importance: how consequential is this for future tasks?
7496
- - Look for [!] prefix (critical) or [~] prefix (notable) as importance signals from the worker.
7497
- - Low importance (casual mention, routine) \u2192 [strength: 1, type: <appropriate>]
7498
- - Medium importance (useful preference, [~] prefix) \u2192 [strength: 2, type: <appropriate>]
7499
- - High importance (explicit correction, strong constraint, [!] prefix) \u2192 [strength: 4, type: <appropriate>]
7500
- - Critical (production error, absolute rule) \u2192 [strength: 6, type: <appropriate>]
7501
-
7502
- **Consolidation rules:**
7503
- - Classify each memory with the correct type. Only keep genuinely reusable knowledge.
7504
- - Discard noise and one-off task details.
7505
- - If the same episodic observation appears 3+ times across different days, promote it to a \`pattern\` or \`domain\` fact with [strength: 5]. Remove the individual episodes.
7506
- - Look for meta-patterns: approaches that consistently work/fail, implicit preferences, recurring corrections.
7507
- - If you find non-obvious insights, add them as \`[type: insight, strength: 2]\` memories.
7508
- - Merge near-duplicates, remove items at [strength: 0], trim verbose entries.
7509
- - Keep total document under ~4000 words.
7510
- - Use ## headers for sections \u2014 let them emerge organically from content.
7511
-
7512
- ### 2. CHECK RECURRING COMMITMENTS
7513
-
7514
- After memory consolidation:
7515
- 1. Call \`deskfree_state\` to see the board.
7516
- 2. Cross-reference memory for recurring patterns (daily audits, weekly reviews, etc.).
7517
- 3. If a recurring commitment has no upcoming scheduled task, propose it via \`deskfree_propose\` with appropriate \`scheduledFor\`.
7518
- 4. Don't propose things already on the board or that were recently completed/ignored.
7519
-
7520
- ### 3. PROACTIVE OPPORTUNITIES
7521
-
7522
- Based on recent work, completed tasks, and patterns in memory \u2014 is there something genuinely useful to propose or a message worth sending?
7523
- - One focused proposal max. Skip if nothing merits it.
7524
- - Quality over quantity. Don't force it.
7525
-
7526
- ### Rules
7527
- - Keep main thread messages short (1-2 sentences).
7528
- - Do NOT propose things the human has previously ignored or rejected.
7529
- - Use \`deskfree_read_file\` only if you need to re-read the Memory file after your own update (verification). The current content is already in your prompt.`;
7530
- DESKFREE_DUSK_DIRECTIVE = `## DeskFree \u2014 Evening Dusk Cycle
7531
- You are the dusk agent. You run once per day in the evening to review the day, propose overnight work, and brief the human.
8017
+ description: 'What you learned. Prefix with importance: [!] for critical (corrections, constraints), [~] for notable (preferences, patterns), or nothing for routine observations. Focus on: PREFERENCES, CORRECTIONS, PATTERNS, DOMAIN FACTS. Be specific. Bad: "Created a table". Good: "[!] User corrected: never use semicolons in this codebase".'
8018
+ }),
8019
+ taskId: Type.Optional(
8020
+ Type.String({
8021
+ description: "Task ID (optional if context provides it)"
8022
+ })
8023
+ )
8024
+ })
8025
+ },
8026
+ COMPLETE_TASK: SHARED_TOOLS.COMPLETE_TASK,
8027
+ SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
8028
+ PROPOSE: SHARED_TOOLS.PROPOSE
8029
+ };
8030
+ MAX_FULL_MESSAGES = 15;
8031
+ DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 Orchestrator
8032
+ You are the orchestrator. Your job: turn human intent into approved tasks, then dispatch work.
7532
8033
 
7533
- Tools available: deskfree_state, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_update_file.
8034
+ **Main thread = short and snappy.** Keep responses to 1-3 sentences. Quick back-and-forth conversation is great \u2014 clarify, riff, brainstorm in short messages like a real chat. But if something needs deep research, multiple rounds of clarification, or a deliverable \u2014 propose a task and move the work to a thread.
7534
8035
 
7535
- ---
8036
+ **The core loop: propose \u2192 approve \u2192 work.**
7536
8037
 
7537
- ### 1. REVIEW THE DAY
8038
+ 1. **Check state** \u2192 \`deskfree_state\` \u2014 see tasks, memory (a pinned file with accumulated knowledge), and files.
8039
+ 2. **Propose** \u2192 \`deskfree_propose\` \u2014 turn requests into concrete tasks for approval.
8040
+ 3. **Dispatch** \u2192 \`deskfree_dispatch_worker\` with the taskId.
8041
+ 4. **Communicate** \u2192 \`deskfree_send_message\` for updates outside task threads.
7538
8042
 
7539
- Your prompt contains both \`<current_memory>\` (the current Memory file content) and \`<daily_observations>\` (raw daily logs) inline \u2014 you have everything you need upfront.
8043
+ **Before proposing, qualify the request.** Figure out what kind of thing this is:
8044
+ - **One-off task** ("proofread this") \u2192 propose a task directly.
8045
+ - **New aspiration** ("I want to start posting on LinkedIn") \u2192 don't rush to propose. Ask 1-2 short qualifying questions to understand the real goal.
8046
+ - Never call \`deskfree_propose\` as your very first action \u2014 qualify first, even if briefly.
7540
8047
 
7541
- **Steps:**
7542
- 1. Review the current memory and daily observations from your prompt.
7543
- 2. Call \`deskfree_state\` to see the current board \u2014 open tasks, recently completed work, files.
7544
- 3. Build a mental model of what happened today: what was accomplished, what's in progress, what's stalled.
8048
+ **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.
7545
8049
 
7546
- ### 2. IDENTIFY OVERNIGHT OPPORTUNITIES
8050
+ 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.
8051
+ - When a human writes in a task thread, decide:
8052
+ - **Continuation of the same task?** \u2192 reopen and dispatch a worker.
8053
+ - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one or do the work yourself).
8054
+ - **Just confirmation or deferred?** \u2192 leave it for now.
8055
+ - Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
8056
+ DESKFREE_WORKER_DIRECTIVE = `## DeskFree Worker
8057
+ You are a worker sub-agent. Your first message contains pre-loaded context \u2014 use it directly.
8058
+ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_complete_task, deskfree_send_message, deskfree_propose.
7547
8059
 
7548
- Think about work that can be done autonomously overnight \u2014 WITHOUT human judgment or approval mid-task.
8060
+ **Context loading:**
8061
+ - If your first message contains \`<task_context>\`, the task is already claimed and context is pre-loaded. Do NOT call deskfree_start_task \u2014 start working immediately.
8062
+ - If your first message contains \`<workspace_state>\`, use it for situational awareness (other tasks, memory, files).
8063
+ - If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to claim and load context.
8064
+ - If continuing from a previous conversation (you can see prior tool calls and context), respond directly to the human's latest message \u2014 do NOT call deskfree_start_task again.
7549
8065
 
7550
- **Good overnight work:**
7551
- - Research and analysis (market research, competitive analysis, reading material)
7552
- - Content drafts (blog posts, reports, documentation)
7553
- - File updates (updating recurring reports, preparing templates)
7554
- - Prep work for tomorrow (gathering context, pre-reading for scheduled meetings)
7555
- - Recurring tasks that are due (weekly reviews, daily audits)
8066
+ **Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
7556
8067
 
7557
- **NOT good overnight work:**
7558
- - Anything requiring human decisions mid-task
7559
- - Tasks that depend on external input not yet received
7560
- - Creative work where the human has strong opinions on direction
7561
- - Anything the human explicitly said to wait on
8068
+ 1. **Orient** \u2014 Scan workspace state for relevant files. Read the Memory file for context on preferences and past patterns. Read any other files that are useful context with \`deskfree_read_file\`. Don't read everything \u2014 just what's relevant to this task.
8069
+ 2. **Align** \u2014 Send a brief \`notify\` message: what you found, what you'll produce. One or two sentences. ("I'll build on the existing brand guide and create a new tone reference.")
8070
+ - **Judgment calls or creative direction?** State your assumptions and approach, send as \`ask\`, and wait for confirmation before proceeding. Getting alignment early prevents costly rework.
8071
+ - **Straightforward execution?** Proceed immediately after the notify \u2014 don't wait for a response.
8072
+ 3. **Work** \u2014 Execute the task. Update existing files with \`deskfree_update_file\` or create new ones with \`deskfree_create_file\`. Pass your taskId so updates appear in the thread. For large deliverables, build incrementally \u2014 share structure/outline first, then flesh out. Don't produce a finished 2000-word document and ask for review in one shot.
8073
+ 4. **Deliver** \u2014 Send an \`ask\` message when work is ready for review. Only complete (\`deskfree_complete_task\` with humanApproved: true) after the human has confirmed. Never self-complete.
7562
8074
 
7563
- ### 3. PROPOSE THE PLAN
8075
+ **Push back when warranted:**
8076
+ - If task instructions seem unclear, contradictory, or misguided \u2014 say so. "This task asks for X, but based on [context], Y might work better because..." is more useful than silently executing a flawed plan.
8077
+ - If you hit genuine ambiguity mid-task, send an \`ask\` message and wait. Don't guess on important decisions \u2014 guessing creates review debt the human has to pay later.
8078
+ - You're a teammate, not a task executor. Have an opinion when you have the context to form one.
7564
8079
 
7565
- If you identified useful overnight work:
7566
- 1. Use \`deskfree_propose\` with 1-3 well-scoped tasks. Quality over quantity.
7567
- 2. Each task should be self-contained \u2014 a worker must be able to complete it without human input.
7568
- 3. Set \`scheduledFor\` if work should start at a specific time (e.g. early morning).
7569
- 4. If nothing genuinely useful can be done overnight, skip the proposal entirely. Don't force it.
8080
+ **File rules:**
8081
+ - Create files when your task naturally produces them. Don't be afraid to create multiple files if the work calls for it.
8082
+ - Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
8083
+ - If you discover work that falls outside your task's scope, use \`deskfree_propose\` to suggest follow-up tasks immediately \u2014 don't wait until completion. Propose as you discover, then stay focused on your current task.
7570
8084
 
7571
- ### 4. BRIEF THE HUMAN
8085
+ **Learnings:**
8086
+ - Use \`deskfree_learning\` to record observations worth remembering. A nightly sleep cycle consolidates these into the Memory file. Record:
8087
+ - **Preferences**: how the human wants things done ("prefers X over Y")
8088
+ - **Corrections**: when the human corrects you ("actually, do X not Y")
8089
+ - **Patterns**: recurring approaches that work ("for this type of task, always...")
8090
+ - **Domain facts**: business-specific knowledge not in project docs
8091
+ - Prefix critical observations with [!] (corrections, constraints, errors).
8092
+ - Prefix notable observations with [~] (preferences, patterns).
8093
+ - Leave routine observations unprefixed.
8094
+ - Do NOT record one-time task details, things in project docs, or obvious/generic knowledge.
8095
+ - If your first message contains \`<daily_observations>\`, these are recent raw observations not yet consolidated into Memory. Use them as additional context.
7572
8096
 
7573
- Send a brief main-thread message via \`deskfree_send_message\`:
7574
- - 2-3 sentence summary: what happened today + what you're proposing for overnight (if anything).
7575
- - Keep it conversational and useful \u2014 this is the human's "end of day" touchpoint.
7576
- - If no proposals, still send a brief day summary.
8097
+ **Sub-agents & delegation:**
8098
+ - Your context window is finite. Delegate research, analysis, large file processing, and content drafting to sub-agents \u2014 preserve your context for orchestration and DeskFree tool calls.
8099
+ - Sub-agents get a fresh context window with standard tools (Read, Write, Bash, Grep, WebSearch, etc.) but NO DeskFree tools. Pre-load any file content they need into the prompt.
8100
+ - Sub-agents are ephemeral helpers \u2014 they complete their assigned task and nothing else. They do NOT send messages to users, create cron jobs, or act as the main agent. Their final output is returned to you.
8101
+ - Use \`run_in_background: true\` for parallel independent work.
8102
+ - During Orient, check Memory for sub-agent helper patterns. Inject relevant ones into the sub-agent prompt alongside the task.
8103
+ - After a sub-agent completes, reflect: did this reveal a useful delegation pattern? Something to do differently? Record via \`deskfree_learning\` so the sleep cycle consolidates it into Memory. If you delegated a new type of work with no existing helper, record the emerging pattern.
8104
+ - Don't over-delegate: quick reads, simple lookups, and anything requiring DeskFree tools are faster inline.
7577
8105
 
7578
- ### Rules
7579
- - Do NOT propose things already on the board or recently completed.
7580
- - Do NOT repeat suggestions the human previously ignored or rejected.
7581
- - Quality over quantity. One good task beats three mediocre ones.
7582
- - Keep the briefing message short and actionable.
7583
- - Cross-reference memory for recurring patterns \u2014 if something is due, propose it.
7584
- - Use \`deskfree_read_file\` only if you need file content beyond what's in your prompt.`;
8106
+ **Completing tasks:**
8107
+ - On 409 or 404 errors: STOP. Do not retry. Call deskfree_state to find available tasks.`;
7585
8108
  THROTTLE_MS = 300;
7586
8109
  CHAR_BUFFER_SIZE = 256;
7587
8110
  CLOSE_MAX_RETRIES = 3;
@@ -7793,7 +8316,15 @@ Send a brief main-thread message via \`deskfree_send_message\`:
7793
8316
  }
7794
8317
  });
7795
8318
  function runOrchestrator(opts) {
7796
- const { prompt, orchestratorServer, model, sessionId, claudeCodePath } = opts;
8319
+ const {
8320
+ prompt,
8321
+ orchestratorServer,
8322
+ model,
8323
+ sessionId,
8324
+ claudeCodePath,
8325
+ agentContext
8326
+ } = opts;
8327
+ const systemPrompt = agentContext ? buildAgentDirective(agentContext) : DESKFREE_AGENT_DIRECTIVE;
7797
8328
  return query({
7798
8329
  prompt,
7799
8330
  options: {
@@ -7801,7 +8332,7 @@ function runOrchestrator(opts) {
7801
8332
  process.stderr.write(`[orchestrator-sdk] ${data}
7802
8333
  `);
7803
8334
  },
7804
- systemPrompt: DESKFREE_AGENT_DIRECTIVE,
8335
+ systemPrompt,
7805
8336
  model,
7806
8337
  ...claudeCodePath ? { pathToClaudeCodeExecutable: claudeCodePath } : {},
7807
8338
  maxTurns: MAX_ORCHESTRATOR_TURNS,
@@ -7814,28 +8345,18 @@ function runOrchestrator(opts) {
7814
8345
  "deskfree-orchestrator": orchestratorServer
7815
8346
  },
7816
8347
  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
- ],
8348
+ allowedTools: ORCHESTRATOR_ALLOWED_TOOLS,
7829
8349
  disallowedTools: DISALLOWED_BUILTIN_TOOLS
7830
8350
  }
7831
8351
  });
7832
8352
  }
7833
8353
  function runHeartbeat(opts) {
7834
- const { prompt, orchestratorServer, model, claudeCodePath } = opts;
8354
+ const { prompt, orchestratorServer, model, claudeCodePath, agentContext } = opts;
8355
+ const systemPrompt = agentContext ? buildHeartbeatDirective(agentContext) : DESKFREE_AGENT_DIRECTIVE;
7835
8356
  return query({
7836
8357
  prompt,
7837
8358
  options: {
7838
- systemPrompt: DESKFREE_AGENT_DIRECTIVE,
8359
+ systemPrompt,
7839
8360
  model,
7840
8361
  ...claudeCodePath ? { pathToClaudeCodeExecutable: claudeCodePath } : {},
7841
8362
  maxTurns: MAX_ORCHESTRATOR_TURNS,
@@ -7846,18 +8367,7 @@ function runHeartbeat(opts) {
7846
8367
  "deskfree-orchestrator": orchestratorServer
7847
8368
  },
7848
8369
  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
- ],
8370
+ allowedTools: ORCHESTRATOR_ALLOWED_TOOLS,
7861
8371
  disallowedTools: DISALLOWED_BUILTIN_TOOLS
7862
8372
  }
7863
8373
  });
@@ -7880,11 +8390,19 @@ function runOneShotWorker(opts) {
7880
8390
  }
7881
8391
  });
7882
8392
  }
7883
- var MAX_ORCHESTRATOR_TURNS, DISALLOWED_BUILTIN_TOOLS;
8393
+ var MAX_ORCHESTRATOR_TURNS, ORCHESTRATOR_ALLOWED_TOOLS, DISALLOWED_BUILTIN_TOOLS;
7884
8394
  var init_orchestrator = __esm({
7885
8395
  "src/agents/orchestrator.ts"() {
7886
8396
  init_dist();
7887
8397
  MAX_ORCHESTRATOR_TURNS = 20;
8398
+ ORCHESTRATOR_ALLOWED_TOOLS = [
8399
+ "mcp__deskfree-orchestrator__*",
8400
+ "Read",
8401
+ "Glob",
8402
+ "Grep",
8403
+ "WebSearch",
8404
+ "WebFetch"
8405
+ ];
7888
8406
  DISALLOWED_BUILTIN_TOOLS = [
7889
8407
  "TodoWrite",
7890
8408
  "AskUserQuestion",
@@ -7961,8 +8479,8 @@ function loadConfig() {
7961
8479
  function mergeWithRemoteConfig(local, remote) {
7962
8480
  const stateDirOverridden = !!process.env["DESKFREE_STATE_DIR"];
7963
8481
  const toolsDirOverridden = !!process.env["DESKFREE_TOOLS_DIR"];
7964
- const stateDir = stateDirOverridden ? local.stateDir : isDocker ? local.stateDir : `.deskfree/${remote.botId}/state`;
7965
- const toolsDir = toolsDirOverridden ? local.toolsDir : isDocker ? local.toolsDir : `.deskfree/${remote.botId}/tools`;
8482
+ const stateDir = stateDirOverridden ? local.stateDir : isDocker ? local.stateDir : join(homedir(), ".deskfree", remote.botId, "state");
8483
+ const toolsDir = toolsDirOverridden ? local.toolsDir : isDocker ? local.toolsDir : join(homedir(), ".deskfree", remote.botId, "tools");
7966
8484
  let claudeCodePath;
7967
8485
  if (remote.provider === "claude-code") {
7968
8486
  try {
@@ -7990,6 +8508,8 @@ function mergeWithRemoteConfig(local, remote) {
7990
8508
  anthropicApiKey: remote.anthropicApiKey,
7991
8509
  baseUrl: process.env["DESKFREE_BASE_URL"] ?? remote.baseUrl,
7992
8510
  botId: remote.botId,
8511
+ botName: remote.botName,
8512
+ deploymentType: remote.deploymentType,
7993
8513
  memoryFileId: remote.memoryFileId,
7994
8514
  sleepHour: remote.sleepHour,
7995
8515
  duskHour: remote.duskHour,
@@ -8002,8 +8522,8 @@ var init_config = __esm({
8002
8522
  init_dist();
8003
8523
  isDocker = process.env["DOCKER"] === "1" || existsSync("/.dockerenv");
8004
8524
  DEFAULTS = {
8005
- stateDir: isDocker ? "/app/state" : ".deskfree/state",
8006
- toolsDir: isDocker ? "/app/tools" : ".deskfree/tools",
8525
+ stateDir: isDocker ? "/app/state" : join(homedir(), ".deskfree", "state"),
8526
+ toolsDir: isDocker ? "/app/tools" : join(homedir(), ".deskfree", "tools"),
8007
8527
  logLevel: "info",
8008
8528
  healthPort: 3100
8009
8529
  };
@@ -8049,6 +8569,86 @@ var init_health_state = __esm({
8049
8569
  }
8050
8570
  });
8051
8571
 
8572
+ // src/util/logger.ts
8573
+ var logger_exports = {};
8574
+ __export(logger_exports, {
8575
+ createLogger: () => createLogger,
8576
+ enableFileLogging: () => enableFileLogging,
8577
+ getLogFilePath: () => getLogFilePath,
8578
+ logger: () => logger
8579
+ });
8580
+ function enableFileLogging(filePath) {
8581
+ mkdirSync(dirname(filePath), { recursive: true });
8582
+ logFilePath = filePath;
8583
+ }
8584
+ function getLogFilePath() {
8585
+ return logFilePath;
8586
+ }
8587
+ function rotateIfNeeded() {
8588
+ if (!logFilePath) return;
8589
+ try {
8590
+ const stat = statSync(logFilePath);
8591
+ if (stat.size <= MAX_LOG_FILE_BYTES) return;
8592
+ const content = readFileSync(logFilePath, "utf8");
8593
+ const half = Math.floor(content.length / 2);
8594
+ const newlineIdx = content.indexOf("\n", half);
8595
+ const trimmed = newlineIdx >= 0 ? content.slice(newlineIdx + 1) : content.slice(half);
8596
+ writeFileSync(logFilePath, trimmed);
8597
+ } catch {
8598
+ }
8599
+ }
8600
+ function createLogger(component, minLevel = "info") {
8601
+ const minLevelNum = LEVELS[minLevel];
8602
+ function log(level, message, fields) {
8603
+ if (LEVELS[level] < minLevelNum) return;
8604
+ const entry = {
8605
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
8606
+ level,
8607
+ component,
8608
+ msg: message,
8609
+ ...fields
8610
+ };
8611
+ const line = JSON.stringify(entry);
8612
+ if (level === "error" || level === "warn") {
8613
+ process.stderr.write(line + "\n");
8614
+ } else {
8615
+ process.stdout.write(line + "\n");
8616
+ }
8617
+ if (logFilePath) {
8618
+ try {
8619
+ appendFileSync(logFilePath, line + "\n");
8620
+ if (++writesSinceCheck >= ROTATION_CHECK_INTERVAL) {
8621
+ writesSinceCheck = 0;
8622
+ rotateIfNeeded();
8623
+ }
8624
+ } catch {
8625
+ }
8626
+ }
8627
+ }
8628
+ return {
8629
+ debug: (msg, fields) => log("debug", msg, fields),
8630
+ info: (msg, fields) => log("info", msg, fields),
8631
+ warn: (msg, fields) => log("warn", msg, fields),
8632
+ error: (msg, fields) => log("error", msg, fields)
8633
+ };
8634
+ }
8635
+ var LEVELS, MAX_LOG_FILE_BYTES, ROTATION_CHECK_INTERVAL, logFilePath, writesSinceCheck, logger;
8636
+ var init_logger = __esm({
8637
+ "src/util/logger.ts"() {
8638
+ LEVELS = {
8639
+ debug: 0,
8640
+ info: 1,
8641
+ warn: 2,
8642
+ error: 3
8643
+ };
8644
+ MAX_LOG_FILE_BYTES = 5 * 1024 * 1024;
8645
+ ROTATION_CHECK_INTERVAL = 500;
8646
+ logFilePath = null;
8647
+ writesSinceCheck = 0;
8648
+ logger = createLogger("runtime");
8649
+ }
8650
+ });
8651
+
8052
8652
  // src/gateway/polling.ts
8053
8653
  async function pollAndDeliver(client, accountId, stateDir, cursor, onMessage, log) {
8054
8654
  const ctx = { accountId };
@@ -11753,8 +12353,6 @@ var init_wrapper = __esm({
11753
12353
  wrapper_default2 = import_websocket2.default;
11754
12354
  }
11755
12355
  });
11756
-
11757
- // src/gateway/ws-gateway.ts
11758
12356
  function nextBackoff(state2) {
11759
12357
  const delay = Math.min(
11760
12358
  BACKOFF_INITIAL_MS * Math.pow(BACKOFF_FACTOR, state2.attempt),
@@ -11934,11 +12532,11 @@ async function runWebSocketConnection(opts) {
11934
12532
  }, PING_INTERVAL_MS);
11935
12533
  if (opts.getWorkerStatus) {
11936
12534
  try {
11937
- const status = opts.getWorkerStatus();
12535
+ const status2 = opts.getWorkerStatus();
11938
12536
  ws.send(
11939
12537
  JSON.stringify({
11940
12538
  action: "heartbeatResponse",
11941
- ...status
12539
+ ...status2
11942
12540
  })
11943
12541
  );
11944
12542
  } catch (err) {
@@ -12008,11 +12606,11 @@ async function runWebSocketConnection(opts) {
12008
12606
  } else if (msg.action === "heartbeatRequest") {
12009
12607
  if (opts.getWorkerStatus && ws.readyState === wrapper_default2.OPEN) {
12010
12608
  try {
12011
- const status = opts.getWorkerStatus();
12609
+ const status2 = opts.getWorkerStatus();
12012
12610
  ws.send(
12013
12611
  JSON.stringify({
12014
12612
  action: "heartbeatResponse",
12015
- ...status
12613
+ ...status2
12016
12614
  })
12017
12615
  );
12018
12616
  } catch (err) {
@@ -12027,6 +12625,49 @@ async function runWebSocketConnection(opts) {
12027
12625
  cleanup();
12028
12626
  ws.close(1e3, "reload");
12029
12627
  process.kill(process.pid, "SIGTERM");
12628
+ } else if (msg.action === "logs") {
12629
+ const MAX_RESPONSE_BYTES = 12e4;
12630
+ const lineCount = msg.lines ?? 200;
12631
+ try {
12632
+ const logFile = getLogFilePath();
12633
+ if (!logFile) {
12634
+ ws.send(
12635
+ JSON.stringify({
12636
+ action: "logsResponse",
12637
+ lines: [],
12638
+ error: "File logging not enabled"
12639
+ })
12640
+ );
12641
+ } else {
12642
+ const content = readFileSync(logFile, "utf8");
12643
+ const allLines = content.split("\n").filter(Boolean);
12644
+ let tail = allLines.slice(-lineCount);
12645
+ while (tail.length > 1) {
12646
+ const payload = JSON.stringify({
12647
+ action: "logsResponse",
12648
+ lines: tail
12649
+ });
12650
+ if (Buffer.byteLength(payload) <= MAX_RESPONSE_BYTES) break;
12651
+ tail = tail.slice(Math.ceil(tail.length * 0.25));
12652
+ }
12653
+ ws.send(
12654
+ JSON.stringify({
12655
+ action: "logsResponse",
12656
+ lines: tail
12657
+ })
12658
+ );
12659
+ }
12660
+ } catch (err) {
12661
+ const errMsg = err instanceof Error ? err.message : String(err);
12662
+ log.warn(`Failed to read logs: ${errMsg}`);
12663
+ ws.send(
12664
+ JSON.stringify({
12665
+ action: "logsResponse",
12666
+ lines: [],
12667
+ error: errMsg
12668
+ })
12669
+ );
12670
+ }
12030
12671
  }
12031
12672
  } catch (err) {
12032
12673
  const message = err instanceof Error ? err.message : String(err);
@@ -12145,6 +12786,7 @@ var PING_INTERVAL_MS, POLL_FALLBACK_INTERVAL_MS, WS_CONNECTION_TIMEOUT_MS, WS_PO
12145
12786
  var init_ws_gateway = __esm({
12146
12787
  "src/gateway/ws-gateway.ts"() {
12147
12788
  init_health_state();
12789
+ init_logger();
12148
12790
  init_polling();
12149
12791
  init_dist();
12150
12792
  init_wrapper();
@@ -12639,21 +13281,35 @@ function scheduleDailyCycle(label, run, hour, timezone, signal, log) {
12639
13281
  }
12640
13282
  function msUntilNextLocalHour(hour, timezone) {
12641
13283
  const now = Date.now();
12642
- const fmt = new Intl.DateTimeFormat("en-US", {
13284
+ const hourFmt = new Intl.DateTimeFormat("en-US", {
12643
13285
  timeZone: timezone,
12644
13286
  hour: "numeric",
12645
13287
  hour12: false
12646
13288
  });
13289
+ const detailFmt = new Intl.DateTimeFormat("en-US", {
13290
+ timeZone: timezone,
13291
+ minute: "numeric",
13292
+ second: "numeric"
13293
+ });
12647
13294
  for (let offsetMs = 6e4; offsetMs <= 25 * 36e5; offsetMs += 36e5) {
12648
13295
  const candidateMs = now + offsetMs;
12649
- const parts = fmt.formatToParts(new Date(candidateMs));
13296
+ const parts = hourFmt.formatToParts(new Date(candidateMs));
12650
13297
  const hourPart = parts.find((p) => p.type === "hour");
12651
13298
  const candidateHour = parseInt(hourPart?.value ?? "-1", 10);
12652
13299
  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);
13300
+ const detailParts = detailFmt.formatToParts(new Date(candidateMs));
13301
+ const localMinute = parseInt(
13302
+ detailParts.find((p) => p.type === "minute")?.value ?? "0",
13303
+ 10
13304
+ );
13305
+ const localSecond = parseInt(
13306
+ detailParts.find((p) => p.type === "second")?.value ?? "0",
13307
+ 10
13308
+ );
13309
+ const snappedMs = candidateMs - localMinute * 6e4 - localSecond * 1e3;
13310
+ if (snappedMs > now + 6e4) {
13311
+ return snappedMs - now;
13312
+ }
12657
13313
  }
12658
13314
  }
12659
13315
  return 24 * 36e5;
@@ -12749,50 +13405,6 @@ var init_registry = __esm({
12749
13405
  "src/tools/registry.ts"() {
12750
13406
  }
12751
13407
  });
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
13408
  function checkRateLimit(userId) {
12797
13409
  const now = Date.now();
12798
13410
  const entry = rateLimitMap.get(userId);
@@ -13057,7 +13669,8 @@ async function routeMessage(message, client, deps, sessionStore, config) {
13057
13669
  orchestratorServer: deps.createOrchestratorServer(),
13058
13670
  model: deps.model,
13059
13671
  sessionId: existingSessionId,
13060
- claudeCodePath: deps.claudeCodePath
13672
+ claudeCodePath: deps.claudeCodePath,
13673
+ agentContext: deps.agentContext
13061
13674
  });
13062
13675
  let fullText = "";
13063
13676
  let capturedSessionId = null;
@@ -13240,17 +13853,16 @@ var init_sessions = __esm({
13240
13853
  }
13241
13854
  });
13242
13855
  function runWorker(opts) {
13243
- const { prompt, workerServer, model, sessionId } = opts;
13856
+ const { prompt, workerServer, model, sessionId, agentContext } = opts;
13857
+ const systemPrompt = agentContext ? buildWorkerDirective(agentContext) : DESKFREE_WORKER_DIRECTIVE;
13244
13858
  return query({
13245
13859
  prompt,
13246
13860
  options: {
13247
- debug: true,
13248
- debugFile: "/dev/stderr",
13249
13861
  stderr: (data) => {
13250
13862
  process.stderr.write(`[worker-sdk] ${data}
13251
13863
  `);
13252
13864
  },
13253
- systemPrompt: DESKFREE_WORKER_DIRECTIVE,
13865
+ systemPrompt,
13254
13866
  model,
13255
13867
  maxTurns: MAX_WORKER_TURNS,
13256
13868
  permissionMode: "bypassPermissions",
@@ -13562,7 +14174,8 @@ ${userMessage}
13562
14174
  prompt: channel,
13563
14175
  workerServer,
13564
14176
  model,
13565
- sessionId: previousSessionId
14177
+ sessionId: previousSessionId,
14178
+ agentContext: this.deps.agentContext
13566
14179
  });
13567
14180
  const idleTimer = this.startIdleTimer(taskId);
13568
14181
  const drainPromise = this.drainLoop(
@@ -13808,7 +14421,7 @@ function startHealthServer(port, log) {
13808
14421
  }
13809
14422
  };
13810
14423
  }
13811
- function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, claudeCodePath) {
14424
+ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, claudeCodePath, agentContext) {
13812
14425
  if (intervalMs <= 0) return;
13813
14426
  let running = false;
13814
14427
  async function tick() {
@@ -13816,11 +14429,13 @@ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, cla
13816
14429
  running = true;
13817
14430
  try {
13818
14431
  log.debug("Heartbeat tick: checking for pending work...");
14432
+ const heartbeatPrompt = agentContext ? buildHeartbeatDirective(agentContext) : "Run your heartbeat check now.";
13819
14433
  const result = runHeartbeat({
13820
- prompt: DESKFREE_HEARTBEAT_DIRECTIVE,
14434
+ prompt: heartbeatPrompt,
13821
14435
  orchestratorServer: createOrchServer(),
13822
14436
  model,
13823
- claudeCodePath
14437
+ claudeCodePath,
14438
+ agentContext
13824
14439
  });
13825
14440
  for await (const _ of result) {
13826
14441
  }
@@ -13861,8 +14476,23 @@ async function startAgent(opts) {
13861
14476
  const msg = err instanceof Error ? err.message : String(err);
13862
14477
  throw new Error(`Failed to bootstrap config from API: ${msg}`);
13863
14478
  }
14479
+ const isDocker2 = process.env["DOCKER"] === "1" || (await import('fs')).existsSync("/.dockerenv");
14480
+ const runtimeVersion = process.env["npm_package_version"] ?? "unknown";
14481
+ const agentContext = {
14482
+ botName: config.botName,
14483
+ deploymentType: config.deploymentType,
14484
+ provider: config.provider,
14485
+ model: config.model,
14486
+ platform: isDocker2 ? "Docker" : process.platform === "darwin" ? "macOS" : "Linux",
14487
+ runtimeVersion,
14488
+ maxConcurrentWorkers: 5
14489
+ // updated after WorkerManager is created
14490
+ };
13864
14491
  mkdirSync(config.stateDir, { recursive: true });
13865
14492
  mkdirSync(config.toolsDir, { recursive: true });
14493
+ const logFile = join(config.stateDir, "runtime.log");
14494
+ enableFileLogging(logFile);
14495
+ log.info(`File logging enabled: ${logFile}`);
13866
14496
  if (config.tools.length > 0) {
13867
14497
  log.info(`Installing ${config.tools.length} tool package(s)...`);
13868
14498
  await installTools(config.tools, config.toolsDir, log);
@@ -13916,8 +14546,10 @@ async function startAgent(opts) {
13916
14546
  "memory",
13917
14547
  config.botId,
13918
14548
  "session-history.json"
13919
- )
14549
+ ),
14550
+ agentContext
13920
14551
  });
14552
+ agentContext.maxConcurrentWorkers = workerManager.maxConcurrentWorkers;
13921
14553
  const createOrchServer = () => createOrchestratorMcpServer(client, customTools, workerManager);
13922
14554
  const healthServer = startHealthServer(config.healthPort, log);
13923
14555
  const sessionStore = new SessionStore();
@@ -13944,7 +14576,8 @@ async function startAgent(opts) {
13944
14576
  createOrchestratorServer: createOrchServer,
13945
14577
  workerManager,
13946
14578
  model: config.model,
13947
- claudeCodePath: config.claudeCodePath
14579
+ claudeCodePath: config.claudeCodePath,
14580
+ agentContext
13948
14581
  },
13949
14582
  sessionStore,
13950
14583
  {
@@ -13960,7 +14593,8 @@ async function startAgent(opts) {
13960
14593
  config.heartbeatIntervalMs,
13961
14594
  abortController.signal,
13962
14595
  log,
13963
- config.claudeCodePath
14596
+ config.claudeCodePath,
14597
+ agentContext
13964
14598
  );
13965
14599
  if (config.memoryFileId && config.sleepHour !== null && config.timezone) {
13966
14600
  const memoryFileId = config.memoryFileId;
@@ -13997,7 +14631,7 @@ async function startAgent(opts) {
13997
14631
  const workerServer = createWorkServer();
13998
14632
  const result = runOneShotWorker({
13999
14633
  prompt,
14000
- systemPrompt: DESKFREE_SLEEP_DIRECTIVE,
14634
+ systemPrompt: buildSleepDirective(agentContext),
14001
14635
  workerServer,
14002
14636
  model: config.model
14003
14637
  });
@@ -14043,7 +14677,7 @@ async function startAgent(opts) {
14043
14677
  const workerServer = createWorkServer();
14044
14678
  const result = runOneShotWorker({
14045
14679
  prompt,
14046
- systemPrompt: DESKFREE_DUSK_DIRECTIVE,
14680
+ systemPrompt: buildDuskDirective(agentContext),
14047
14681
  workerServer,
14048
14682
  model: config.model
14049
14683
  });
@@ -14093,16 +14727,42 @@ var init_entrypoint = __esm({
14093
14727
  var args = process.argv.slice(2);
14094
14728
  var command = args[0];
14095
14729
  if (command === "install") {
14096
- const token = args[1];
14730
+ let token = args[1];
14097
14731
  if (!token) {
14098
- console.error("Usage: deskfree-agent install <token>");
14099
- process.exit(1);
14732
+ const { createInterface } = await import('readline');
14733
+ const rl = createInterface({
14734
+ input: process.stdin,
14735
+ output: process.stdout
14736
+ });
14737
+ token = await new Promise((resolve) => {
14738
+ rl.question(
14739
+ "Paste your bot token (from the DeskFree dashboard):\n> ",
14740
+ (answer) => {
14741
+ rl.close();
14742
+ resolve(answer.trim());
14743
+ }
14744
+ );
14745
+ });
14746
+ if (!token) {
14747
+ console.error("No token provided.");
14748
+ process.exit(1);
14749
+ }
14100
14750
  }
14101
14751
  const { install: install2 } = await Promise.resolve().then(() => (init_install(), install_exports));
14102
14752
  install2(token);
14103
14753
  } else if (command === "uninstall") {
14104
14754
  const { uninstall: uninstall2 } = await Promise.resolve().then(() => (init_uninstall(), uninstall_exports));
14105
14755
  uninstall2();
14756
+ } else if (command === "status") {
14757
+ const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
14758
+ status2();
14759
+ } else if (command === "restart") {
14760
+ const { restart: restart2 } = await Promise.resolve().then(() => (init_restart(), restart_exports));
14761
+ restart2();
14762
+ } else if (command === "logs") {
14763
+ const follow = args.includes("-f") || args.includes("--follow");
14764
+ const { logs: logs2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
14765
+ logs2(follow);
14106
14766
  } else {
14107
14767
  let handleShutdown = function(signal) {
14108
14768
  log.info(`Received ${signal} \u2014 shutting down...`);