@questionbase/deskfree 0.4.7 → 0.5.1

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,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { execSync, execFileSync, execFile } from 'child_process';
4
- import { mkdirSync, writeFileSync, chmodSync, existsSync, unlinkSync, appendFileSync, readdirSync, readFileSync, statSync, createWriteStream } from 'fs';
5
3
  import { homedir } from 'os';
6
4
  import { dirname, join, extname } from 'path';
5
+ import { spawn, execSync, execFileSync, execFile } from 'child_process';
6
+ import { mkdirSync, writeFileSync, chmodSync, existsSync, unlinkSync, appendFileSync, readdirSync, readFileSync, statSync, createWriteStream } from 'fs';
7
7
  import { createRequire as createRequire$1 } from 'module';
8
8
  import { query, createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
9
9
  import { z } from 'zod';
@@ -33,8 +33,8 @@ var __commonJS = (cb, mod) => function __require3() {
33
33
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
34
34
  };
35
35
  var __export = (target, all) => {
36
- for (var name in all)
37
- __defProp(target, name, { get: all[name], enumerable: true });
36
+ for (var name2 in all)
37
+ __defProp(target, name2, { get: all[name2], enumerable: true });
38
38
  };
39
39
  var __copyProps = (to, from, except, desc) => {
40
40
  if (from && typeof from === "object" || typeof from === "function") {
@@ -52,25 +52,66 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
52
52
  __defProp(target, "default", { value: mod, enumerable: true }) ,
53
53
  mod
54
54
  ));
55
-
56
- // src/cli/install.ts
57
- var install_exports = {};
58
- __export(install_exports, {
59
- install: () => install
60
- });
61
- function getMacPaths() {
55
+ function getPlistLabel(name2) {
56
+ return `com.deskfree.agent.${name2}`;
57
+ }
58
+ function getServiceName(name2) {
59
+ return `deskfree-agent-${name2}`;
60
+ }
61
+ function getMacPaths(name2) {
62
62
  const home = homedir();
63
- const deskfreeDir = join(home, ".deskfree");
63
+ const deskfreeDir = join(home, ".deskfree", name2);
64
+ const plistLabel = getPlistLabel(name2);
64
65
  return {
65
66
  deskfreeDir,
66
67
  envFile: join(deskfreeDir, ".env"),
67
68
  launcher: join(deskfreeDir, "launcher.sh"),
68
69
  logDir: join(deskfreeDir, "logs"),
69
- plist: join(home, "Library", "LaunchAgents", `${PLIST_LABEL}.plist`)
70
+ plist: join(home, "Library", "LaunchAgents", `${plistLabel}.plist`)
71
+ };
72
+ }
73
+ function getLinuxPaths(name2) {
74
+ const serviceName = getServiceName(name2);
75
+ return {
76
+ serviceName,
77
+ serviceFile: `/etc/systemd/system/${serviceName}.service`,
78
+ envFile: `/etc/${serviceName}.env`,
79
+ stateDir: `/var/lib/${serviceName}`,
80
+ logDir: `/var/log/${serviceName}`
70
81
  };
71
82
  }
72
- function installMac(token) {
73
- const paths = getMacPaths();
83
+ function parseName(args) {
84
+ const idx = args.indexOf("--name");
85
+ if (idx === -1) return [DEFAULT_NAME, args];
86
+ const name2 = args[idx + 1];
87
+ if (!name2 || name2.startsWith("-")) {
88
+ console.error("Error: --name requires a value (e.g. --name kasper)");
89
+ process.exit(1);
90
+ }
91
+ if (!/^[a-z0-9-]+$/i.test(name2)) {
92
+ console.error(
93
+ "Error: --name must contain only letters, numbers, and hyphens"
94
+ );
95
+ process.exit(1);
96
+ }
97
+ const remaining = [...args.slice(0, idx), ...args.slice(idx + 2)];
98
+ return [name2, remaining];
99
+ }
100
+ var DEFAULT_NAME;
101
+ var init_paths = __esm({
102
+ "src/cli/paths.ts"() {
103
+ DEFAULT_NAME = "main";
104
+ }
105
+ });
106
+
107
+ // src/cli/install.ts
108
+ var install_exports = {};
109
+ __export(install_exports, {
110
+ install: () => install
111
+ });
112
+ function installMac(token, name2) {
113
+ const paths = getMacPaths(name2);
114
+ const plistLabel = getPlistLabel(name2);
74
115
  let nodeBinDir;
75
116
  try {
76
117
  const nodePath = execSync("which node", { encoding: "utf8" }).trim();
@@ -82,8 +123,13 @@ function installMac(token) {
82
123
  mkdirSync(paths.deskfreeDir, { recursive: true });
83
124
  mkdirSync(paths.logDir, { recursive: true });
84
125
  mkdirSync(dirname(paths.plist), { recursive: true });
85
- writeFileSync(paths.envFile, `DESKFREE_LAUNCH=${token}
86
- `, { mode: 384 });
126
+ writeFileSync(
127
+ paths.envFile,
128
+ `DESKFREE_LAUNCH=${token}
129
+ DESKFREE_INSTANCE_NAME=${name2}
130
+ `,
131
+ { mode: 384 }
132
+ );
87
133
  chmodSync(paths.envFile, 384);
88
134
  console.log(`Wrote ${paths.envFile}`);
89
135
  const launcher = `#!/bin/bash
@@ -109,7 +155,7 @@ exec deskfree-agent start
109
155
  <plist version="1.0">
110
156
  <dict>
111
157
  <key>Label</key>
112
- <string>${PLIST_LABEL}</string>
158
+ <string>${plistLabel}</string>
113
159
  <key>ProgramArguments</key>
114
160
  <array>
115
161
  <string>${paths.launcher}</string>
@@ -137,72 +183,100 @@ exec deskfree-agent start
137
183
  }
138
184
  execSync(`launchctl bootstrap gui/$(id -u) ${paths.plist}`);
139
185
  console.log(`
140
- Service ${PLIST_LABEL} installed and started.`);
141
- console.log(`Check status: launchctl print gui/$(id -u)/${PLIST_LABEL}`);
186
+ Service ${plistLabel} installed and started.`);
187
+ console.log(`Check status: launchctl print gui/$(id -u)/${plistLabel}`);
142
188
  console.log(`Logs: tail -f ${join(paths.logDir, "stdout.log")}`);
143
189
  }
144
- function installLinux(token) {
190
+ function installLinux(token, name2) {
145
191
  if (process.getuid?.() !== 0) {
146
192
  console.error("Error: install must be run as root (use sudo)");
147
193
  process.exit(1);
148
194
  }
149
- let npxPath;
195
+ const paths = getLinuxPaths(name2);
196
+ const serviceName = getServiceName(name2);
197
+ const systemUser = serviceName;
198
+ let npmPath;
199
+ let nodeBinDir;
150
200
  try {
151
- npxPath = execSync("which npx", { encoding: "utf8" }).trim();
201
+ npmPath = execSync("which npm", { encoding: "utf8" }).trim();
202
+ nodeBinDir = dirname(npmPath);
152
203
  } catch {
153
- console.error("Error: npx not found in PATH");
204
+ console.error("Error: npm not found in PATH");
154
205
  process.exit(1);
155
206
  }
156
- writeFileSync(SYSTEMD_ENV_FILE, `DESKFREE_LAUNCH=${token}
157
- `, {
158
- mode: 384
159
- });
160
- chmodSync(SYSTEMD_ENV_FILE, 384);
161
- console.log(`Wrote ${SYSTEMD_ENV_FILE}`);
207
+ try {
208
+ execSync(`id ${systemUser}`, { stdio: "ignore" });
209
+ } catch {
210
+ execSync(
211
+ `useradd --system --no-create-home --shell /usr/sbin/nologin ${systemUser}`
212
+ );
213
+ console.log(`Created system user: ${systemUser}`);
214
+ }
215
+ mkdirSync(paths.stateDir, { recursive: true });
216
+ mkdirSync(paths.logDir, { recursive: true });
217
+ execSync(
218
+ `chown ${systemUser}:${systemUser} ${paths.stateDir} ${paths.logDir}`
219
+ );
220
+ console.log(`Created ${paths.stateDir} and ${paths.logDir}`);
221
+ writeFileSync(
222
+ paths.envFile,
223
+ `DESKFREE_LAUNCH=${token}
224
+ DESKFREE_INSTANCE_NAME=${name2}
225
+ `,
226
+ { mode: 384 }
227
+ );
228
+ chmodSync(paths.envFile, 384);
229
+ console.log(`Wrote ${paths.envFile}`);
162
230
  const unit = `[Unit]
163
- Description=DeskFree Agent
231
+ Description=DeskFree Agent (${name2})
164
232
  After=network-online.target
165
233
  Wants=network-online.target
166
234
 
167
235
  [Service]
168
236
  Type=simple
169
- ExecStartPre=${npxPath} ${PACKAGE} --version
170
- ExecStart=${npxPath} ${PACKAGE} start
171
- EnvironmentFile=${SYSTEMD_ENV_FILE}
237
+ User=${systemUser}
238
+ Group=${systemUser}
239
+ WorkingDirectory=${paths.stateDir}
240
+ Environment=PATH=${nodeBinDir}:/usr/local/bin:/usr/bin:/bin
241
+ ExecStartPre=+${npmPath} install -g ${PACKAGE}
242
+ ExecStart=${nodeBinDir}/deskfree-agent start
243
+ EnvironmentFile=${paths.envFile}
172
244
  Environment=NODE_ENV=production
245
+ Environment=DESKFREE_STATE_DIR=${paths.stateDir}
246
+ Environment=DESKFREE_TOOLS_DIR=${paths.stateDir}/tools
173
247
  Restart=always
174
248
  RestartSec=10
249
+ StandardOutput=append:${paths.logDir}/stdout.log
250
+ StandardError=append:${paths.logDir}/stderr.log
175
251
 
176
252
  [Install]
177
253
  WantedBy=multi-user.target
178
254
  `;
179
- writeFileSync(SYSTEMD_SERVICE_FILE, unit);
180
- console.log(`Wrote ${SYSTEMD_SERVICE_FILE}`);
255
+ writeFileSync(paths.serviceFile, unit);
256
+ console.log(`Wrote ${paths.serviceFile}`);
181
257
  execSync("systemctl daemon-reload");
182
- execSync(`systemctl enable ${SERVICE_NAME}`);
183
- execSync(`systemctl start ${SERVICE_NAME}`);
258
+ execSync(`systemctl enable ${serviceName}`);
259
+ execSync(`systemctl start ${serviceName}`);
184
260
  console.log(`
185
- Service ${SERVICE_NAME} installed and started.`);
186
- console.log(`Check status: systemctl status ${SERVICE_NAME}`);
261
+ Service ${serviceName} installed and started.`);
262
+ console.log(`Check status: systemctl status ${serviceName}`);
263
+ console.log(`Logs: tail -f ${paths.logDir}/stdout.log`);
187
264
  }
188
- function install(token) {
265
+ function install(token, name2) {
189
266
  if (process.platform === "darwin") {
190
- installMac(token);
267
+ installMac(token, name2);
191
268
  } else if (process.platform === "linux") {
192
- installLinux(token);
269
+ installLinux(token, name2);
193
270
  } else {
194
271
  console.error(`Unsupported platform: ${process.platform}`);
195
272
  process.exit(1);
196
273
  }
197
274
  }
198
- var SERVICE_NAME, PACKAGE, PLIST_LABEL, SYSTEMD_SERVICE_FILE, SYSTEMD_ENV_FILE;
275
+ var PACKAGE;
199
276
  var init_install = __esm({
200
277
  "src/cli/install.ts"() {
201
- SERVICE_NAME = "deskfree-agent";
278
+ init_paths();
202
279
  PACKAGE = "@questionbase/deskfree@latest";
203
- PLIST_LABEL = "com.deskfree.agent";
204
- SYSTEMD_SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
205
- SYSTEMD_ENV_FILE = `/etc/${SERVICE_NAME}.env`;
206
280
  }
207
281
  });
208
282
 
@@ -211,68 +285,65 @@ var uninstall_exports = {};
211
285
  __export(uninstall_exports, {
212
286
  uninstall: () => uninstall
213
287
  });
214
- function uninstallMac() {
215
- const home = homedir();
216
- const plist = join(home, "Library", "LaunchAgents", `${PLIST_LABEL2}.plist`);
217
- const deskfreeDir = join(home, ".deskfree");
218
- const envFile = join(deskfreeDir, ".env");
219
- const launcher = join(deskfreeDir, "launcher.sh");
288
+ function uninstallMac(name2) {
289
+ const paths = getMacPaths(name2);
290
+ const plistLabel = getPlistLabel(name2);
220
291
  try {
221
- execSync(`launchctl bootout gui/$(id -u) ${plist}`, { stdio: "ignore" });
292
+ execSync(`launchctl bootout gui/$(id -u) ${paths.plist}`, {
293
+ stdio: "ignore"
294
+ });
222
295
  } catch {
223
296
  }
224
- for (const file of [plist, envFile, launcher]) {
297
+ for (const file of [paths.plist, paths.envFile, paths.launcher]) {
225
298
  if (existsSync(file)) {
226
299
  unlinkSync(file);
227
300
  console.log(`Removed ${file}`);
228
301
  }
229
302
  }
230
- console.log(`Service ${PLIST_LABEL2} uninstalled.`);
303
+ console.log(`Service ${plistLabel} uninstalled.`);
231
304
  console.log(
232
- `Note: logs and state in ${deskfreeDir} were preserved. Remove manually if desired.`
305
+ `Note: logs and state in ${paths.deskfreeDir} were preserved. Remove manually if desired.`
233
306
  );
234
307
  }
235
- function uninstallLinux() {
308
+ function uninstallLinux(name2) {
236
309
  if (process.getuid?.() !== 0) {
237
310
  console.error("Error: uninstall must be run as root (use sudo)");
238
311
  process.exit(1);
239
312
  }
313
+ const paths = getLinuxPaths(name2);
314
+ const serviceName = getServiceName(name2);
240
315
  try {
241
- execSync(`systemctl stop ${SERVICE_NAME2}`, { stdio: "ignore" });
316
+ execSync(`systemctl stop ${serviceName}`, { stdio: "ignore" });
242
317
  } catch {
243
318
  }
244
319
  try {
245
- execSync(`systemctl disable ${SERVICE_NAME2}`, { stdio: "ignore" });
320
+ execSync(`systemctl disable ${serviceName}`, { stdio: "ignore" });
246
321
  } catch {
247
322
  }
248
- if (existsSync(SYSTEMD_SERVICE_FILE2)) {
249
- unlinkSync(SYSTEMD_SERVICE_FILE2);
250
- console.log(`Removed ${SYSTEMD_SERVICE_FILE2}`);
323
+ if (existsSync(paths.serviceFile)) {
324
+ unlinkSync(paths.serviceFile);
325
+ console.log(`Removed ${paths.serviceFile}`);
251
326
  }
252
- if (existsSync(SYSTEMD_ENV_FILE2)) {
253
- unlinkSync(SYSTEMD_ENV_FILE2);
254
- console.log(`Removed ${SYSTEMD_ENV_FILE2}`);
327
+ if (existsSync(paths.envFile)) {
328
+ unlinkSync(paths.envFile);
329
+ console.log(`Removed ${paths.envFile}`);
255
330
  }
256
331
  execSync("systemctl daemon-reload");
257
- console.log(`Service ${SERVICE_NAME2} uninstalled.`);
332
+ console.log(`Service ${serviceName} uninstalled.`);
258
333
  }
259
- function uninstall() {
334
+ function uninstall(name2) {
260
335
  if (process.platform === "darwin") {
261
- uninstallMac();
336
+ uninstallMac(name2);
262
337
  } else if (process.platform === "linux") {
263
- uninstallLinux();
338
+ uninstallLinux(name2);
264
339
  } else {
265
340
  console.error(`Unsupported platform: ${process.platform}`);
266
341
  process.exit(1);
267
342
  }
268
343
  }
269
- var SERVICE_NAME2, PLIST_LABEL2, SYSTEMD_SERVICE_FILE2, SYSTEMD_ENV_FILE2;
270
344
  var init_uninstall = __esm({
271
345
  "src/cli/uninstall.ts"() {
272
- SERVICE_NAME2 = "deskfree-agent";
273
- PLIST_LABEL2 = "com.deskfree.agent";
274
- SYSTEMD_SERVICE_FILE2 = `/etc/systemd/system/${SERVICE_NAME2}.service`;
275
- SYSTEMD_ENV_FILE2 = `/etc/${SERVICE_NAME2}.env`;
346
+ init_paths();
276
347
  }
277
348
  });
278
349
 
@@ -281,44 +352,46 @@ var status_exports = {};
281
352
  __export(status_exports, {
282
353
  status: () => status
283
354
  });
284
- function statusMac() {
285
- const home = homedir();
286
- const plist = join(home, "Library", "LaunchAgents", `${PLIST_LABEL3}.plist`);
287
- if (!existsSync(plist)) {
288
- console.log("DeskFree Agent is not installed.");
289
- console.log(`Run: npx @questionbase/deskfree@latest install <token>`);
355
+ function statusMac(name2) {
356
+ const paths = getMacPaths(name2);
357
+ const plistLabel = getPlistLabel(name2);
358
+ if (!existsSync(paths.plist)) {
359
+ console.log(`DeskFree Agent "${name2}" is not installed.`);
360
+ console.log(`Run: deskfree-agent install <token> --name ${name2}`);
290
361
  return;
291
362
  }
292
- console.log("DeskFree Agent (macOS LaunchAgent)\n");
363
+ console.log(`DeskFree Agent "${name2}" (macOS LaunchAgent)
364
+ `);
293
365
  try {
294
- const output = execSync(
295
- `launchctl print gui/$(id -u)/${PLIST_LABEL3} 2>&1`,
296
- { encoding: "utf8" }
297
- );
366
+ const output = execSync(`launchctl print gui/$(id -u)/${plistLabel} 2>&1`, {
367
+ encoding: "utf8"
368
+ });
298
369
  const pidMatch = output.match(/pid\s*=\s*(\d+)/);
299
370
  const stateMatch = output.match(/state\s*=\s*(\w+)/);
300
371
  if (pidMatch) console.log(` PID: ${pidMatch[1]}`);
301
372
  if (stateMatch) console.log(` State: ${stateMatch[1]}`);
302
- const logDir = join(home, ".deskfree", "logs");
303
- console.log(` Logs: ${logDir}/stdout.log`);
304
- console.log(` Plist: ${plist}`);
373
+ console.log(` Logs: ${paths.logDir}/stdout.log`);
374
+ console.log(` Plist: ${paths.plist}`);
305
375
  } catch {
306
376
  console.log(" Status: not running (service may be unloaded)");
307
377
  }
308
378
  }
309
- function statusLinux() {
310
- const serviceFile = `/etc/systemd/system/${SYSTEMD_SERVICE_NAME}.service`;
311
- if (!existsSync(serviceFile)) {
312
- console.log("DeskFree Agent is not installed.");
313
- console.log(`Run: sudo npx @questionbase/deskfree@latest install <token>`);
379
+ function statusLinux(name2) {
380
+ const paths = getLinuxPaths(name2);
381
+ const serviceName = getServiceName(name2);
382
+ if (!existsSync(paths.serviceFile)) {
383
+ console.log(`DeskFree Agent "${name2}" is not installed.`);
384
+ console.log(
385
+ `Run: sudo npx @questionbase/deskfree@latest install <token> --name ${name2}`
386
+ );
314
387
  return;
315
388
  }
316
- console.log("DeskFree Agent (systemd service)\n");
389
+ console.log(`DeskFree Agent "${name2}" (systemd service)
390
+ `);
317
391
  try {
318
- const output = execSync(
319
- `systemctl status ${SYSTEMD_SERVICE_NAME} --no-pager 2>&1`,
320
- { encoding: "utf8" }
321
- );
392
+ const output = execSync(`systemctl status ${serviceName} --no-pager 2>&1`, {
393
+ encoding: "utf8"
394
+ });
322
395
  console.log(output);
323
396
  } catch (err) {
324
397
  if (err && typeof err === "object" && "stdout" in err) {
@@ -327,22 +400,116 @@ function statusLinux() {
327
400
  console.log(" Status: unknown (could not query systemd)");
328
401
  }
329
402
  }
403
+ console.log(` Logs: ${paths.logDir}/stdout.log`);
330
404
  }
331
- function status() {
405
+ function status(name2) {
332
406
  if (process.platform === "darwin") {
333
- statusMac();
407
+ statusMac(name2);
334
408
  } else if (process.platform === "linux") {
335
- statusLinux();
409
+ statusLinux(name2);
336
410
  } else {
337
411
  console.error(`Unsupported platform: ${process.platform}`);
338
412
  process.exit(1);
339
413
  }
340
414
  }
341
- var PLIST_LABEL3, SYSTEMD_SERVICE_NAME;
342
415
  var init_status = __esm({
343
416
  "src/cli/status.ts"() {
344
- PLIST_LABEL3 = "com.deskfree.agent";
345
- SYSTEMD_SERVICE_NAME = "deskfree-agent";
417
+ init_paths();
418
+ }
419
+ });
420
+
421
+ // src/cli/restart.ts
422
+ var restart_exports = {};
423
+ __export(restart_exports, {
424
+ restart: () => restart
425
+ });
426
+ function restartMac(name2) {
427
+ const paths = getMacPaths(name2);
428
+ if (!existsSync(paths.plist)) {
429
+ console.error(`DeskFree Agent "${name2}" is not installed.`);
430
+ console.error(`Run: deskfree-agent install <token> --name ${name2}`);
431
+ process.exit(1);
432
+ }
433
+ try {
434
+ execSync(`launchctl bootout gui/$(id -u) ${paths.plist}`, {
435
+ stdio: "ignore"
436
+ });
437
+ } catch {
438
+ }
439
+ execSync(`launchctl bootstrap gui/$(id -u) ${paths.plist}`);
440
+ console.log(`DeskFree Agent "${name2}" restarted.`);
441
+ console.log(`Logs: tail -f ${paths.logDir}/stdout.log`);
442
+ }
443
+ function restartLinux(name2) {
444
+ const paths = getLinuxPaths(name2);
445
+ const serviceName = getServiceName(name2);
446
+ if (!existsSync(paths.serviceFile)) {
447
+ console.error(`DeskFree Agent "${name2}" is not installed.`);
448
+ console.error(
449
+ `Run: sudo npx @questionbase/deskfree@latest install <token> --name ${name2}`
450
+ );
451
+ process.exit(1);
452
+ }
453
+ execSync(`systemctl restart ${serviceName}`, { stdio: "inherit" });
454
+ console.log(`DeskFree Agent "${name2}" restarted.`);
455
+ console.log(`Logs: tail -f ${paths.logDir}/stdout.log`);
456
+ }
457
+ function restart(name2) {
458
+ if (process.platform === "darwin") {
459
+ restartMac(name2);
460
+ } else if (process.platform === "linux") {
461
+ restartLinux(name2);
462
+ } else {
463
+ console.error(`Unsupported platform: ${process.platform}`);
464
+ process.exit(1);
465
+ }
466
+ }
467
+ var init_restart = __esm({
468
+ "src/cli/restart.ts"() {
469
+ init_paths();
470
+ }
471
+ });
472
+
473
+ // src/cli/logs.ts
474
+ var logs_exports = {};
475
+ __export(logs_exports, {
476
+ logs: () => logs
477
+ });
478
+ function getLogPath(name2) {
479
+ if (process.platform === "darwin") {
480
+ const logFile = join(getMacPaths(name2).logDir, "stdout.log");
481
+ return existsSync(logFile) ? logFile : null;
482
+ } else if (process.platform === "linux") {
483
+ const logFile = join(getLinuxPaths(name2).logDir, "stdout.log");
484
+ return existsSync(logFile) ? logFile : null;
485
+ }
486
+ return null;
487
+ }
488
+ function logs(follow, name2) {
489
+ if (process.platform !== "darwin" && process.platform !== "linux") {
490
+ console.error(`Unsupported platform: ${process.platform}`);
491
+ process.exit(1);
492
+ }
493
+ const logPath = getLogPath(name2);
494
+ if (!logPath) {
495
+ console.error(
496
+ `No log file found for "${name2}". Is DeskFree Agent installed with --name ${name2}?`
497
+ );
498
+ process.exit(1);
499
+ }
500
+ const args = follow ? ["-f", "-n", "50", logPath] : ["-n", "50", logPath];
501
+ const child = spawn("tail", args, { stdio: "inherit" });
502
+ child.on("error", (err) => {
503
+ console.error(`Failed to read logs: ${err.message}`);
504
+ process.exit(1);
505
+ });
506
+ child.on("exit", (code) => {
507
+ process.exit(code ?? 0);
508
+ });
509
+ }
510
+ var init_logs = __esm({
511
+ "src/cli/logs.ts"() {
512
+ init_paths();
346
513
  }
347
514
  });
348
515
  function toErrorMessage(error) {
@@ -1373,8 +1540,8 @@ function Intersect(types, options) {
1373
1540
  throw new Error("Cannot intersect transform types");
1374
1541
  return IntersectCreate(types, options);
1375
1542
  }
1376
- function Ref(...args2) {
1377
- const [$ref, options] = typeof args2[0] === "string" ? [args2[0], args2[1]] : [args2[0].$id, args2[1]];
1543
+ function Ref(...args) {
1544
+ const [$ref, options] = typeof args[0] === "string" ? [args[0], args[1]] : [args[0].$id, args[1]];
1378
1545
  if (typeof $ref !== "string")
1379
1546
  throw new TypeBoxError("Ref: $ref must be a string");
1380
1547
  return CreateType({ [Kind]: "Ref", $ref }, options);
@@ -1933,78 +2100,78 @@ function RecordKey2(type) {
1933
2100
  function RecordValue2(type) {
1934
2101
  return type.patternProperties[RecordPattern(type)];
1935
2102
  }
1936
- function FromConstructor2(args2, type) {
1937
- type.parameters = FromTypes(args2, type.parameters);
1938
- type.returns = FromType(args2, type.returns);
2103
+ function FromConstructor2(args, type) {
2104
+ type.parameters = FromTypes(args, type.parameters);
2105
+ type.returns = FromType(args, type.returns);
1939
2106
  return type;
1940
2107
  }
1941
- function FromFunction2(args2, type) {
1942
- type.parameters = FromTypes(args2, type.parameters);
1943
- type.returns = FromType(args2, type.returns);
2108
+ function FromFunction2(args, type) {
2109
+ type.parameters = FromTypes(args, type.parameters);
2110
+ type.returns = FromType(args, type.returns);
1944
2111
  return type;
1945
2112
  }
1946
- function FromIntersect5(args2, type) {
1947
- type.allOf = FromTypes(args2, type.allOf);
2113
+ function FromIntersect5(args, type) {
2114
+ type.allOf = FromTypes(args, type.allOf);
1948
2115
  return type;
1949
2116
  }
1950
- function FromUnion7(args2, type) {
1951
- type.anyOf = FromTypes(args2, type.anyOf);
2117
+ function FromUnion7(args, type) {
2118
+ type.anyOf = FromTypes(args, type.anyOf);
1952
2119
  return type;
1953
2120
  }
1954
- function FromTuple4(args2, type) {
2121
+ function FromTuple4(args, type) {
1955
2122
  if (IsUndefined(type.items))
1956
2123
  return type;
1957
- type.items = FromTypes(args2, type.items);
2124
+ type.items = FromTypes(args, type.items);
1958
2125
  return type;
1959
2126
  }
1960
- function FromArray5(args2, type) {
1961
- type.items = FromType(args2, type.items);
2127
+ function FromArray5(args, type) {
2128
+ type.items = FromType(args, type.items);
1962
2129
  return type;
1963
2130
  }
1964
- function FromAsyncIterator2(args2, type) {
1965
- type.items = FromType(args2, type.items);
2131
+ function FromAsyncIterator2(args, type) {
2132
+ type.items = FromType(args, type.items);
1966
2133
  return type;
1967
2134
  }
1968
- function FromIterator2(args2, type) {
1969
- type.items = FromType(args2, type.items);
2135
+ function FromIterator2(args, type) {
2136
+ type.items = FromType(args, type.items);
1970
2137
  return type;
1971
2138
  }
1972
- function FromPromise3(args2, type) {
1973
- type.item = FromType(args2, type.item);
2139
+ function FromPromise3(args, type) {
2140
+ type.item = FromType(args, type.item);
1974
2141
  return type;
1975
2142
  }
1976
- function FromObject2(args2, type) {
1977
- const mappedProperties = FromProperties11(args2, type.properties);
2143
+ function FromObject2(args, type) {
2144
+ const mappedProperties = FromProperties11(args, type.properties);
1978
2145
  return { ...type, ...Object2(mappedProperties) };
1979
2146
  }
1980
- function FromRecord2(args2, type) {
1981
- const mappedKey = FromType(args2, RecordKey2(type));
1982
- const mappedValue = FromType(args2, RecordValue2(type));
2147
+ function FromRecord2(args, type) {
2148
+ const mappedKey = FromType(args, RecordKey2(type));
2149
+ const mappedValue = FromType(args, RecordValue2(type));
1983
2150
  const result = Record(mappedKey, mappedValue);
1984
2151
  return { ...type, ...result };
1985
2152
  }
1986
- function FromArgument(args2, argument) {
1987
- return argument.index in args2 ? args2[argument.index] : Unknown();
2153
+ function FromArgument(args, argument) {
2154
+ return argument.index in args ? args[argument.index] : Unknown();
1988
2155
  }
1989
- function FromProperty2(args2, type) {
2156
+ function FromProperty2(args, type) {
1990
2157
  const isReadonly = IsReadonly(type);
1991
2158
  const isOptional = IsOptional(type);
1992
- const mapped = FromType(args2, type);
2159
+ const mapped = FromType(args, type);
1993
2160
  return isReadonly && isOptional ? ReadonlyOptional(mapped) : isReadonly && !isOptional ? Readonly(mapped) : !isReadonly && isOptional ? Optional(mapped) : mapped;
1994
2161
  }
1995
- function FromProperties11(args2, properties) {
2162
+ function FromProperties11(args, properties) {
1996
2163
  return globalThis.Object.getOwnPropertyNames(properties).reduce((result, key) => {
1997
- return { ...result, [key]: FromProperty2(args2, properties[key]) };
2164
+ return { ...result, [key]: FromProperty2(args, properties[key]) };
1998
2165
  }, {});
1999
2166
  }
2000
- function FromTypes(args2, types) {
2001
- return types.map((type) => FromType(args2, type));
2167
+ function FromTypes(args, types) {
2168
+ return types.map((type) => FromType(args, type));
2002
2169
  }
2003
- function FromType(args2, type) {
2004
- return IsConstructor(type) ? FromConstructor2(args2, type) : IsFunction2(type) ? FromFunction2(args2, type) : IsIntersect(type) ? FromIntersect5(args2, type) : IsUnion(type) ? FromUnion7(args2, type) : IsTuple(type) ? FromTuple4(args2, type) : IsArray3(type) ? FromArray5(args2, type) : IsAsyncIterator2(type) ? FromAsyncIterator2(args2, type) : IsIterator2(type) ? FromIterator2(args2, type) : IsPromise(type) ? FromPromise3(args2, type) : IsObject3(type) ? FromObject2(args2, type) : IsRecord(type) ? FromRecord2(args2, type) : IsArgument(type) ? FromArgument(args2, type) : type;
2170
+ function FromType(args, type) {
2171
+ return IsConstructor(type) ? FromConstructor2(args, type) : IsFunction2(type) ? FromFunction2(args, type) : IsIntersect(type) ? FromIntersect5(args, type) : IsUnion(type) ? FromUnion7(args, type) : IsTuple(type) ? FromTuple4(args, type) : IsArray3(type) ? FromArray5(args, type) : IsAsyncIterator2(type) ? FromAsyncIterator2(args, type) : IsIterator2(type) ? FromIterator2(args, type) : IsPromise(type) ? FromPromise3(args, type) : IsObject3(type) ? FromObject2(args, type) : IsRecord(type) ? FromRecord2(args, type) : IsArgument(type) ? FromArgument(args, type) : type;
2005
2172
  }
2006
- function Instantiate(type, args2) {
2007
- return FromType(args2, CloneType(type));
2173
+ function Instantiate(type, args) {
2174
+ return FromType(args, CloneType(type));
2008
2175
  }
2009
2176
  function Integer(options) {
2010
2177
  return CreateType({ [Kind]: "Integer", type: "integer" }, options);
@@ -2480,6 +2647,9 @@ function validateStringParam(params, key, required) {
2480
2647
  }
2481
2648
  function validateEnumParam(params, key, values, required) {
2482
2649
  const value = params?.[key];
2650
+ if (required && (value === void 0 || value === null)) {
2651
+ throw new Error(`Missing required parameter: ${key}`);
2652
+ }
2483
2653
  if (value !== void 0 && value !== null && !values.includes(value)) {
2484
2654
  throw new Error(
2485
2655
  `Parameter ${key} must be one of: ${values.join(", ")}. Got: ${value}`
@@ -2663,7 +2833,18 @@ function createWorkerTools(client, options) {
2663
2833
  try {
2664
2834
  const content = validateStringParam(params, "content", true);
2665
2835
  const taskId = validateStringParam(params, "taskId", false);
2836
+ const type = validateEnumParam(params, "type", ["notify", "ask"], true);
2666
2837
  await client.sendMessage({ content, taskId });
2838
+ if (type === "ask") {
2839
+ return {
2840
+ content: [
2841
+ {
2842
+ type: "text",
2843
+ text: "Ask sent \u2014 task is now awaiting human response. Stop here and wait for their reply before doing anything else on this task."
2844
+ }
2845
+ ]
2846
+ };
2847
+ }
2667
2848
  return {
2668
2849
  content: [{ type: "text", text: "Message sent successfully" }]
2669
2850
  };
@@ -2684,7 +2865,7 @@ function createWorkerTools(client, options) {
2684
2865
  }),
2685
2866
  createTool(WORKER_TOOLS.CREATE_FILE, async (params) => {
2686
2867
  try {
2687
- const name = validateStringParam(params, "name", true);
2868
+ const name2 = validateStringParam(params, "name", true);
2688
2869
  const description = validateStringParam(params, "description", false);
2689
2870
  const content = validateStringParam(params, "content", false);
2690
2871
  const contentFormat = validateEnumParam(
@@ -2695,7 +2876,7 @@ function createWorkerTools(client, options) {
2695
2876
  );
2696
2877
  const taskId = validateStringParam(params, "taskId", false);
2697
2878
  const result = await client.createFile({
2698
- name,
2879
+ name: name2,
2699
2880
  description,
2700
2881
  content,
2701
2882
  contentFormat,
@@ -2813,6 +2994,330 @@ function createWorkerTools(client, options) {
2813
2994
  })
2814
2995
  ];
2815
2996
  }
2997
+ function identityBlock(ctx) {
2998
+ 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;
2999
+ return `## About You
3000
+ 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.
3001
+
3002
+ 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}.
3003
+
3004
+ ## Personality
3005
+ 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.
3006
+
3007
+ ## Safety
3008
+ 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.
3009
+
3010
+ 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.
3011
+
3012
+ 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.
3013
+
3014
+ ## Your Runtime
3015
+ - Version: ${ctx.runtimeVersion}
3016
+ - Platform: ${ctx.platform}
3017
+ - Deployment: ${ctx.deploymentType ?? "unknown"}
3018
+ - Provider: ${providerLabel}
3019
+ - Model: ${ctx.model}
3020
+ - Max parallel tasks: ${ctx.maxConcurrentWorkers} (you can work on multiple tasks at once)
3021
+
3022
+ ## Self-Management
3023
+ - To update yourself to the latest version, run \`deskfree-agent restart${ctx.instanceName ? ` --name ${ctx.instanceName}` : ""}\` in a Bash shell. This installs the latest release and restarts the service. You'll be offline for ~30 seconds.
3024
+ - Only do this when you have no active tasks. Let the user know before restarting.
3025
+ - If someone asks about your version or runtime details, you can share the info above.
3026
+
3027
+ ## Operational Limits
3028
+ - Users are rate-limited to 10 messages per minute.
3029
+ - Attachments: max 10 files per message, 10MB each, 50MB total.
3030
+ - Your daily observation logs are retained for 7 days, then pruned during the nightly sleep cycle.
3031
+ - If an API call returns a 409 or 404 error, stop and check state with \`deskfree_state\` \u2014 don't retry blindly.
3032
+ - If an API call returns a 429 (rate limit) or 5xx error, back off \u2014 don't retry immediately.
3033
+ - Prefer fewer, larger file updates over many small sequential writes.
3034
+
3035
+ ## Context Awareness
3036
+ 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.
3037
+
3038
+ ## Working With Humans
3039
+ Human attention is finite. You have unlimited stamina \u2014 they don't. Optimize for their review experience, not just output quality.
3040
+
3041
+ - **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.
3042
+ - **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.
3043
+ - **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.
3044
+ - **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.
3045
+ - **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.`;
3046
+ }
3047
+ function buildAgentDirective(ctx) {
3048
+ return `${identityBlock(ctx)}
3049
+
3050
+ ## How You Work
3051
+
3052
+ **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.
3053
+
3054
+ **The core loop:**
3055
+
3056
+ 1. **Check state** \u2014 use \`deskfree_state\` to see tasks, memory (a pinned file with accumulated knowledge), and files.
3057
+ 2. **Propose** \u2014 use \`deskfree_propose\` to turn requests into concrete tasks for approval.
3058
+ 3. **Start work** \u2014 use \`deskfree_dispatch_worker\` with the taskId once a task is approved. You'll then continue the work in the task thread.
3059
+ 4. **Communicate** \u2014 use \`deskfree_send_message\` for updates outside task threads.
3060
+
3061
+ **Before proposing, qualify the request.** Figure out what kind of thing this is:
3062
+ - **One-off task** ("proofread this") \u2014 propose a task directly.
3063
+ - **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.
3064
+ - Never call \`deskfree_propose\` as your very first action \u2014 qualify first, even if briefly.
3065
+
3066
+ **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.
3067
+
3068
+ In the main thread you propose and coordinate \u2014 the actual work happens in task threads. Use \`deskfree_dispatch_worker\` to start working on approved tasks.
3069
+ - When a human writes in a task thread, decide:
3070
+ - **Continuation of the same task?** \u2192 reopen and pick it back up.
3071
+ - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one).
3072
+ - **Just confirmation or deferred?** \u2192 leave it for now.
3073
+ - Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
3074
+ }
3075
+ function buildWorkerDirective(ctx) {
3076
+ return `${identityBlock(ctx)}
3077
+
3078
+ ## You're In a Task Thread
3079
+ You're the same ${ctx.botName} from the main thread, now focused on a specific task. Same voice, same personality \u2014 just heads-down on the work.
3080
+
3081
+ 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.
3082
+
3083
+ **Context loading:**
3084
+ - If your first message contains \`<task_context>\`, the task is already loaded. Start working immediately \u2014 do NOT call deskfree_start_task.
3085
+ - If your first message contains \`<workspace_state>\`, use it for situational awareness (other tasks, memory, files).
3086
+ - If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to load it.
3087
+ - 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.
3088
+
3089
+ **Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
3090
+
3091
+ 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.
3092
+ 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.")
3093
+ - **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.
3094
+ - **Straightforward execution?** Proceed immediately after the notify \u2014 don't wait for a response.
3095
+ 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.
3096
+ 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.
3097
+
3098
+ **Push back when warranted:**
3099
+ - 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.
3100
+ - 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.
3101
+ - You're a teammate, not a task executor. Have an opinion when you have the context to form one.
3102
+
3103
+ **File rules:**
3104
+ - Create files when your task naturally produces them. Don't be afraid to create multiple files if the work calls for it.
3105
+ - Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
3106
+ - 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.
3107
+
3108
+ **Learnings:**
3109
+ - Use \`deskfree_learning\` to record observations worth remembering. A nightly cycle consolidates these into the Memory file. Record:
3110
+ - **Preferences**: how the human wants things done ("prefers X over Y")
3111
+ - **Corrections**: when the human corrects you ("actually, do X not Y")
3112
+ - **Patterns**: recurring approaches that work ("for this type of task, always...")
3113
+ - **Domain facts**: business-specific knowledge not in project docs
3114
+ - Prefix critical observations with [!] (corrections, constraints, errors).
3115
+ - Prefix notable observations with [~] (preferences, patterns).
3116
+ - Leave routine observations unprefixed.
3117
+ - Do NOT record one-time task details, things in project docs, or obvious/generic knowledge.
3118
+ - If your first message contains \`<daily_observations>\`, these are recent raw observations not yet consolidated into Memory. Use them as additional context.
3119
+
3120
+ **Delegation:**
3121
+ - Your context window is finite. Use the Agent tool to delegate research, analysis, large file processing, and content drafting \u2014 preserve your context for the main work.
3122
+ - Delegated work gets 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.
3123
+ - Use \`run_in_background: true\` for parallel independent work.
3124
+ - During Orient, check Memory for delegation patterns. Inject relevant ones into the prompt alongside the task.
3125
+ - After delegated work completes, reflect: did this reveal a useful pattern? Record via \`deskfree_learning\` so it's consolidated into Memory.
3126
+ - Don't over-delegate: quick reads, simple lookups, and anything requiring DeskFree tools are faster inline.
3127
+
3128
+ **Completing tasks:**
3129
+ - On 409 or 404 errors: STOP. Do not retry. Call deskfree_state to find available tasks.`;
3130
+ }
3131
+ function buildHeartbeatDirective(ctx) {
3132
+ return `${identityBlock(ctx)}
3133
+
3134
+ ## Heartbeat Check
3135
+ On each heartbeat, run through this checklist:
3136
+
3137
+ ### 1. Work the queue
3138
+ - Run \`deskfree_state\` to get the full workspace snapshot.
3139
+ - **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.
3140
+ - Any open tasks with awaiting=bot? Use \`deskfree_dispatch_worker\` to start working on each one. Pass the taskId.
3141
+ - Any open tasks that seem stalled (no recent activity)? Check on them.
3142
+
3143
+ ### 2. Proactive assessment
3144
+ 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.
3145
+
3146
+ **Think through:**
3147
+ - What momentum exists? What was recently accomplished, and what naturally follows?
3148
+ - What is stalled or falling behind? Anything open too long without progress?
3149
+ - What is scheduled soon? Does anything need prep work before it lands?
3150
+ - What is the single highest-leverage thing that could happen next?
3151
+
3152
+ **Then act \u2014 but only if you have something genuinely useful:**
3153
+
3154
+ *Things you can do* \u2014 research, drafts, analysis, prep. Propose as a task via \`deskfree_propose\`. One focused task, not a batch.
3155
+
3156
+ *Things the human should do* \u2014 nudges, reminders, conversation starters. Send via \`deskfree_send_message\`. Keep it brief and genuinely helpful, not nagging.
3157
+
3158
+ **Rules:**
3159
+ - Do not suggest things that are already open or scheduled \u2014 check first.
3160
+ - Do not repeat suggestions the human ignored or rejected recently.
3161
+ - Quality over quantity. One good insight beats five generic nudges.
3162
+ - If everything looks healthy and active, do nothing. Silence is fine.`;
3163
+ }
3164
+ function buildSleepDirective(ctx) {
3165
+ return `${identityBlock(ctx)}
3166
+
3167
+ ## Nightly Sleep Cycle
3168
+ You're running your nightly cycle to reflect, consolidate memory, and prepare for tomorrow.
3169
+
3170
+ Tools available: deskfree_state, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_update_file.
3171
+
3172
+ ---
3173
+
3174
+ ### 1. REFLECT & CONSOLIDATE
3175
+
3176
+ Your primary job: merge daily observations into the Memory file using strength-scored entries.
3177
+
3178
+ 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.
3179
+
3180
+ **Steps:**
3181
+ 1. Review the current memory and daily observations from your prompt.
3182
+ 2. Apply the memory curation rules below.
3183
+ 3. Update the Memory file via \`deskfree_update_file\` using the file ID from your prompt.
3184
+ 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.
3185
+ 5. If nothing meaningful changed, skip the update and send a brief message like "Quiet day \u2014 nothing new to consolidate."
3186
+
3187
+ **Memory Types**
3188
+
3189
+ Each memory item carries a type tag and strength score: \`[type:X, strength:N]\`
3190
+
3191
+ | Type | What it captures | Decay rate |
3192
+ |------|-----------------|------------|
3193
+ | \`correction\` | Explicit "do X not Y" from human | Very slow (\u22121 when strength >=10, else no decay) |
3194
+ | \`preference\` | How the human wants things done | Slow (\u22121 when strength >=6, else no decay) |
3195
+ | \`pattern\` | Approaches/workflows that work | Normal (see decay rules) |
3196
+ | \`domain\` | Business/project-specific facts | Slow (\u22121 when strength >=6, else no decay) |
3197
+ | \`insight\` | Meta-observations from reflection | Normal (see decay rules) |
3198
+
3199
+ Corrections and domain facts are durable \u2014 they rarely become irrelevant.
3200
+ Patterns and insights decay normally \u2014 stale approaches should be forgotten.
3201
+ Preferences sit in between \u2014 slow decay, but they can evolve.
3202
+
3203
+ **Strength Scoring Rules**
3204
+
3205
+ Strength uses Ebbinghaus-inspired logarithmic decay \u2014 strong memories resist forgetting:
3206
+
3207
+ **Reinforcement (observation matches existing memory):**
3208
+ - strength +2
3209
+
3210
+ **Explicit correction ("actually do X not Y"):**
3211
+ - Replace old memory, new one starts at [strength: 3, type: correction]
3212
+
3213
+ **Decay (memory NOT referenced by any daily observation):**
3214
+ - strength >= 10: decay by \u22121 (deeply encoded, slow forgetting)
3215
+ - strength 5-9: decay by \u22122 (moderately encoded)
3216
+ - strength 1-4: decay by \u22123 (weakly encoded, fast forgetting)
3217
+ - EXCEPT: corrections and domain facts with strength >=6 decay at \u22121 max (durable memories)
3218
+ - EXCEPT: preferences with strength >=6 decay at \u22121 max
3219
+
3220
+ **Removal:**
3221
+ - 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]).
3222
+
3223
+ **New observation:**
3224
+ - Assess importance: how consequential is this for future tasks?
3225
+ - Look for [!] prefix (critical) or [~] prefix (notable) as importance signals.
3226
+ - Low importance (casual mention, routine) \u2192 [strength: 1, type: <appropriate>]
3227
+ - Medium importance (useful preference, [~] prefix) \u2192 [strength: 2, type: <appropriate>]
3228
+ - High importance (explicit correction, strong constraint, [!] prefix) \u2192 [strength: 4, type: <appropriate>]
3229
+ - Critical (production error, absolute rule) \u2192 [strength: 6, type: <appropriate>]
3230
+
3231
+ **Consolidation rules:**
3232
+ - Classify each memory with the correct type. Only keep genuinely reusable knowledge.
3233
+ - Discard noise and one-off task details.
3234
+ - 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.
3235
+ - Look for meta-patterns: approaches that consistently work/fail, implicit preferences, recurring corrections.
3236
+ - If you find non-obvious insights, add them as \`[type: insight, strength: 2]\` memories.
3237
+ - Merge near-duplicates, remove items at [strength: 0], trim verbose entries.
3238
+ - Keep total document under ~4000 words.
3239
+ - Use ## headers for sections \u2014 let them emerge organically from content.
3240
+
3241
+ ### 2. CHECK RECURRING COMMITMENTS
3242
+
3243
+ After memory consolidation:
3244
+ 1. Call \`deskfree_state\` to see the board.
3245
+ 2. Cross-reference memory for recurring patterns (daily audits, weekly reviews, etc.).
3246
+ 3. If a recurring commitment has no upcoming scheduled task, propose it via \`deskfree_propose\` with appropriate \`scheduledFor\`.
3247
+ 4. Don't propose things already on the board or that were recently completed/ignored.
3248
+
3249
+ ### 3. PROACTIVE OPPORTUNITIES
3250
+
3251
+ Based on recent work, completed tasks, and patterns in memory \u2014 is there something genuinely useful to propose or a message worth sending?
3252
+ - **Check board load first.** If the human already has 3+ items needing their attention, skip proposals entirely.
3253
+ - One focused proposal max. Skip if nothing merits it.
3254
+ - Quality over quantity. Don't force it.
3255
+
3256
+ ### Rules
3257
+ - Keep main thread messages short (1-2 sentences).
3258
+ - Do NOT propose things the human has previously ignored or rejected.
3259
+ - 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.`;
3260
+ }
3261
+ function buildDuskDirective(ctx) {
3262
+ return `${identityBlock(ctx)}
3263
+
3264
+ ## Evening Dusk Cycle
3265
+ You're running your evening cycle to review the day, propose overnight work, and brief the human.
3266
+
3267
+ Tools available: deskfree_state, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_update_file.
3268
+
3269
+ ---
3270
+
3271
+ ### 1. REVIEW THE DAY
3272
+
3273
+ 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.
3274
+
3275
+ **Steps:**
3276
+ 1. Review the current memory and daily observations from your prompt.
3277
+ 2. Call \`deskfree_state\` to see the current board \u2014 open tasks, recently completed work, files.
3278
+ 3. Build a mental model of what happened today: what was accomplished, what's in progress, what's stalled.
3279
+
3280
+ ### 2. IDENTIFY OVERNIGHT OPPORTUNITIES
3281
+
3282
+ Think about work that can be done autonomously overnight \u2014 WITHOUT human judgment or approval mid-task.
3283
+
3284
+ **Good overnight work:**
3285
+ - Research and analysis (market research, competitive analysis, reading material)
3286
+ - Content drafts (blog posts, reports, documentation)
3287
+ - File updates (updating recurring reports, preparing templates)
3288
+ - Prep work for tomorrow (gathering context, pre-reading for scheduled meetings)
3289
+ - Recurring tasks that are due (weekly reviews, daily audits)
3290
+
3291
+ **NOT good overnight work:**
3292
+ - Anything requiring human decisions mid-task
3293
+ - Tasks that depend on external input not yet received
3294
+ - Creative work where the human has strong opinions on direction
3295
+ - Anything the human explicitly said to wait on
3296
+
3297
+ ### 3. PROPOSE THE PLAN
3298
+
3299
+ If you identified useful overnight work:
3300
+ 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.
3301
+ 2. Use \`deskfree_propose\` with 1-3 well-scoped tasks. Quality over quantity.
3302
+ 3. Each task should be self-contained \u2014 it must be completable without human input.
3303
+ 4. Set \`scheduledFor\` if work should start at a specific time (e.g. early morning).
3304
+ 5. If nothing genuinely useful can be done overnight, skip the proposal entirely. Don't force it.
3305
+
3306
+ ### 4. BRIEF THE HUMAN
3307
+
3308
+ Send a brief main-thread message via \`deskfree_send_message\`:
3309
+ - 2-3 sentence summary: what happened today + what you're proposing for overnight (if anything).
3310
+ - Keep it conversational and useful \u2014 this is the human's "end of day" touchpoint.
3311
+ - If no proposals, still send a brief day summary.
3312
+
3313
+ ### Rules
3314
+ - Do NOT propose things already on the board or recently completed.
3315
+ - Do NOT repeat suggestions the human previously ignored or rejected.
3316
+ - Quality over quantity. One good task beats three mediocre ones.
3317
+ - Keep the briefing message short and actionable.
3318
+ - Cross-reference memory for recurring patterns \u2014 if something is due, propose it.
3319
+ - Use \`deskfree_read_file\` only if you need file content beyond what's in your prompt.`;
3320
+ }
2816
3321
  function setActiveWs(ws) {
2817
3322
  activeWs = ws;
2818
3323
  }
@@ -2940,18 +3445,18 @@ function saveCursor(ctx, cursor, storagePath, log) {
2940
3445
  }
2941
3446
  }
2942
3447
  function validateField(opts) {
2943
- const { value, name, minLength, maxLength, pattern, patternMessage } = opts;
2944
- if (!value) return `${name} is required`;
2945
- if (typeof value !== "string") return `${name} must be a string`;
3448
+ const { value, name: name2, minLength, maxLength, pattern, patternMessage } = opts;
3449
+ if (!value) return `${name2} is required`;
3450
+ if (typeof value !== "string") return `${name2} must be a string`;
2946
3451
  const trimmed = value.trim();
2947
3452
  if (trimmed !== value)
2948
- return `${name} must not have leading or trailing whitespace`;
3453
+ return `${name2} must not have leading or trailing whitespace`;
2949
3454
  if (minLength !== void 0 && trimmed.length < minLength)
2950
- return `${name} appears to be incomplete (minimum ${minLength} characters expected)`;
3455
+ return `${name2} appears to be incomplete (minimum ${minLength} characters expected)`;
2951
3456
  if (maxLength !== void 0 && trimmed.length > maxLength)
2952
- return `${name} appears to be invalid (maximum ${maxLength} characters expected)`;
3457
+ return `${name2} appears to be invalid (maximum ${maxLength} characters expected)`;
2953
3458
  if (pattern !== void 0 && !pattern.test(trimmed))
2954
- return patternMessage ?? `${name} contains invalid characters`;
3459
+ return patternMessage ?? `${name2} contains invalid characters`;
2955
3460
  return null;
2956
3461
  }
2957
3462
  function isLocalDevelopmentHost(hostname) {
@@ -2981,8 +3486,8 @@ function validateBotToken(value) {
2981
3486
  }
2982
3487
  return null;
2983
3488
  }
2984
- function validateUrl(value, name, allowedProtocols, protocolError) {
2985
- const fieldError = validateField({ value, name });
3489
+ function validateUrl(value, name2, allowedProtocols, protocolError) {
3490
+ const fieldError = validateField({ value, name: name2 });
2986
3491
  if (fieldError) return fieldError;
2987
3492
  const trimmed = value.trim();
2988
3493
  let url;
@@ -2990,21 +3495,21 @@ function validateUrl(value, name, allowedProtocols, protocolError) {
2990
3495
  url = new URL(trimmed);
2991
3496
  } catch (err) {
2992
3497
  const message = err instanceof Error ? err.message : "Invalid URL format";
2993
- return `${name} must be a valid URL: ${message}`;
3498
+ return `${name2} must be a valid URL: ${message}`;
2994
3499
  }
2995
3500
  if (!allowedProtocols.includes(url.protocol)) {
2996
3501
  return protocolError;
2997
3502
  }
2998
3503
  if (!url.hostname) {
2999
- return `${name} must have a valid hostname`;
3504
+ return `${name2} must have a valid hostname`;
3000
3505
  }
3001
3506
  if (isLocalDevelopmentHost(url.hostname)) {
3002
3507
  if (process.env.NODE_ENV === "production") {
3003
- return `${name} cannot use localhost or private IP addresses in production. Please use a publicly accessible URL.`;
3508
+ return `${name2} cannot use localhost or private IP addresses in production. Please use a publicly accessible URL.`;
3004
3509
  }
3005
3510
  }
3006
3511
  if (url.hostname.includes("..") || url.hostname.startsWith(".")) {
3007
- return `${name} hostname appears to be malformed. Please check for typos.`;
3512
+ return `${name2} hostname appears to be malformed. Please check for typos.`;
3008
3513
  }
3009
3514
  return null;
3010
3515
  }
@@ -3016,7 +3521,7 @@ function validateApiUrl(value) {
3016
3521
  "API URL must use HTTPS protocol for security. Make sure your DeskFree deployment supports HTTPS."
3017
3522
  );
3018
3523
  }
3019
- 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;
3524
+ 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;
3020
3525
  var init_dist = __esm({
3021
3526
  "../core/dist/index.js"() {
3022
3527
  require2 = createRequire$1(import.meta.url);
@@ -3036,8 +3541,8 @@ var init_dist = __esm({
3036
3541
  return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
3037
3542
  };
3038
3543
  __export2 = (target, all) => {
3039
- for (var name in all)
3040
- __defProp2(target, name, { get: all[name], enumerable: true });
3544
+ for (var name2 in all)
3545
+ __defProp2(target, name2, { get: all[name2], enumerable: true });
3041
3546
  };
3042
3547
  __copyProps2 = (to, from, except, desc) => {
3043
3548
  if (from && typeof from === "object" || typeof from === "function") {
@@ -5075,9 +5580,9 @@ var init_dist = __esm({
5075
5580
  require_extension = __commonJS2({
5076
5581
  "../../node_modules/ws/lib/extension.js"(exports$1, module) {
5077
5582
  var { tokenChars } = require_validation();
5078
- function push(dest, name, elem) {
5079
- if (dest[name] === void 0) dest[name] = [elem];
5080
- else dest[name].push(elem);
5583
+ function push(dest, name2, elem) {
5584
+ if (dest[name2] === void 0) dest[name2] = [elem];
5585
+ else dest[name2].push(elem);
5081
5586
  }
5082
5587
  function parse(header) {
5083
5588
  const offers = /* @__PURE__ */ Object.create(null);
@@ -5103,12 +5608,12 @@ var init_dist = __esm({
5103
5608
  throw new SyntaxError(`Unexpected character at index ${i}`);
5104
5609
  }
5105
5610
  if (end === -1) end = i;
5106
- const name = header.slice(start, end);
5611
+ const name2 = header.slice(start, end);
5107
5612
  if (code === 44) {
5108
- push(offers, name, params);
5613
+ push(offers, name2, params);
5109
5614
  params = /* @__PURE__ */ Object.create(null);
5110
5615
  } else {
5111
- extensionName = name;
5616
+ extensionName = name2;
5112
5617
  }
5113
5618
  start = end = -1;
5114
5619
  } else {
@@ -6818,13 +7323,13 @@ var init_dist = __esm({
6818
7323
  * Validates that a string parameter is non-empty.
6819
7324
  * Catches invalid inputs before they hit the network.
6820
7325
  */
6821
- requireNonEmpty(value, name) {
7326
+ requireNonEmpty(value, name2) {
6822
7327
  if (!value || value.trim() === "") {
6823
7328
  throw new DeskFreeError(
6824
7329
  "client",
6825
- name,
6826
- `${name} is required and cannot be empty`,
6827
- `Missing required parameter: ${name}. Please provide a valid value.`
7330
+ name2,
7331
+ `${name2} is required and cannot be empty`,
7332
+ `Missing required parameter: ${name2}. Please provide a valid value.`
6828
7333
  );
6829
7334
  }
6830
7335
  }
@@ -7520,7 +8025,7 @@ var init_dist = __esm({
7520
8025
  WORKER_TOOLS = {
7521
8026
  START_TASK: {
7522
8027
  name: "deskfree_start_task",
7523
- description: "Claim a pending task and start working. Returns full context (instructions, message history). Use deskfree_read_file to load any relevant files.",
8028
+ description: "Load a task and start working on it. Returns full context (instructions, message history). Use deskfree_read_file to load any relevant files.",
7524
8029
  parameters: Type.Object({
7525
8030
  taskId: Type.String({ description: "Task UUID to claim" })
7526
8031
  })
@@ -7577,8 +8082,8 @@ var init_dist = __esm({
7577
8082
  PROPOSE: SHARED_TOOLS.PROPOSE
7578
8083
  };
7579
8084
  MAX_FULL_MESSAGES = 15;
7580
- DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 Orchestrator
7581
- You are the orchestrator. Your job: turn human intent into approved tasks, then dispatch work.
8085
+ DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 Main Thread
8086
+ You handle the main conversation thread. Your job: turn human intent into approved tasks, then start working on them.
7582
8087
 
7583
8088
  **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.
7584
8089
 
@@ -7586,7 +8091,7 @@ You are the orchestrator. Your job: turn human intent into approved tasks, then
7586
8091
 
7587
8092
  1. **Check state** \u2192 \`deskfree_state\` \u2014 see tasks, memory (a pinned file with accumulated knowledge), and files.
7588
8093
  2. **Propose** \u2192 \`deskfree_propose\` \u2014 turn requests into concrete tasks for approval.
7589
- 3. **Dispatch** \u2192 \`deskfree_dispatch_worker\` with the taskId.
8094
+ 3. **Start work** \u2192 \`deskfree_dispatch_worker\` with the taskId. You'll then continue the work in the task thread.
7590
8095
  4. **Communicate** \u2192 \`deskfree_send_message\` for updates outside task threads.
7591
8096
 
7592
8097
  **Before proposing, qualify the request.** Figure out what kind of thing this is:
@@ -7596,36 +8101,44 @@ You are the orchestrator. Your job: turn human intent into approved tasks, then
7596
8101
 
7597
8102
  **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.
7598
8103
 
7599
- You do NOT claim tasks, complete tasks, or do work directly \u2014 you have no access to deskfree_start_task or deskfree_complete_task. Use \`deskfree_dispatch_worker\` to dispatch a worker for each approved task.
8104
+ In the main thread you propose and coordinate \u2014 the actual work happens in task threads. Use \`deskfree_dispatch_worker\` to start working on approved tasks.
7600
8105
  - When a human writes in a task thread, decide:
7601
- - **Continuation of the same task?** \u2192 reopen and dispatch a worker.
7602
- - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one or do the work yourself).
8106
+ - **Continuation of the same task?** \u2192 reopen and pick it back up.
8107
+ - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one).
7603
8108
  - **Just confirmation or deferred?** \u2192 leave it for now.
7604
8109
  - Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
7605
- DESKFREE_WORKER_DIRECTIVE = `## DeskFree Worker
7606
- You are a worker sub-agent. Your first message contains pre-loaded context \u2014 use it directly.
8110
+ DESKFREE_WORKER_DIRECTIVE = `## DeskFree \u2014 Task Thread
8111
+ You're in a task thread, focused on a specific piece of work. Same you as in the main thread \u2014 same voice, same personality.
8112
+
7607
8113
  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.
7608
8114
 
7609
8115
  **Context loading:**
7610
- - 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.
8116
+ - If your first message contains \`<task_context>\`, the task is already loaded. Start working immediately \u2014 do NOT call deskfree_start_task.
7611
8117
  - If your first message contains \`<workspace_state>\`, use it for situational awareness (other tasks, memory, files).
7612
- - If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to claim and load context.
8118
+ - If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to load it.
7613
8119
  - 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.
7614
8120
 
7615
8121
  **Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
7616
8122
 
7617
8123
  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.
7618
- 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.
7619
- 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.
8124
+ 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.")
8125
+ - **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.
8126
+ - **Straightforward execution?** Proceed immediately after the notify \u2014 don't wait for a response.
8127
+ 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.
7620
8128
  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.
7621
8129
 
8130
+ **Push back when warranted:**
8131
+ - 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.
8132
+ - 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.
8133
+ - You're a teammate, not a task executor. Have an opinion when you have the context to form one.
8134
+
7622
8135
  **File rules:**
7623
8136
  - Create files when your task naturally produces them. Don't be afraid to create multiple files if the work calls for it.
7624
8137
  - Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
7625
8138
  - 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.
7626
8139
 
7627
8140
  **Learnings:**
7628
- - Use \`deskfree_learning\` to record observations worth remembering. A nightly sleep cycle consolidates these into the Memory file. Record:
8141
+ - Use \`deskfree_learning\` to record observations worth remembering. A nightly cycle consolidates these into the Memory file. Record:
7629
8142
  - **Preferences**: how the human wants things done ("prefers X over Y")
7630
8143
  - **Corrections**: when the human corrects you ("actually, do X not Y")
7631
8144
  - **Patterns**: recurring approaches that work ("for this type of task, always...")
@@ -7636,191 +8149,16 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
7636
8149
  - Do NOT record one-time task details, things in project docs, or obvious/generic knowledge.
7637
8150
  - If your first message contains \`<daily_observations>\`, these are recent raw observations not yet consolidated into Memory. Use them as additional context.
7638
8151
 
7639
- **Sub-agents & delegation:**
7640
- - 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.
7641
- - 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.
8152
+ **Delegation:**
8153
+ - Your context window is finite. Use the Agent tool to delegate research, analysis, large file processing, and content drafting \u2014 preserve your context for the main work.
8154
+ - Delegated work gets 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.
7642
8155
  - Use \`run_in_background: true\` for parallel independent work.
7643
- - During Orient, check Memory for sub-agent helper patterns. Inject relevant ones into the sub-agent prompt alongside the task.
7644
- - 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.
8156
+ - During Orient, check Memory for delegation patterns. Inject relevant ones into the prompt alongside the task.
8157
+ - After delegated work completes, reflect: did this reveal a useful pattern? Record via \`deskfree_learning\` so it's consolidated into Memory.
7645
8158
  - Don't over-delegate: quick reads, simple lookups, and anything requiring DeskFree tools are faster inline.
7646
8159
 
7647
8160
  **Completing tasks:**
7648
8161
  - On 409 or 404 errors: STOP. Do not retry. Call deskfree_state to find available tasks.`;
7649
- DESKFREE_HEARTBEAT_DIRECTIVE = `## DeskFree Heartbeat
7650
- On each heartbeat, run through this checklist:
7651
-
7652
- ### 1. Work the queue
7653
- - Run \`deskfree_state\` to get the full workspace snapshot.
7654
- - Any open tasks with awaiting=bot? Use \`deskfree_dispatch_worker\` to dispatch a worker for each. Pass the taskId.
7655
- - Any open tasks that seem stalled (claimed but no recent activity)? Check on them.
7656
-
7657
- ### 2. Proactive assessment
7658
- 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.
7659
-
7660
- **Think through:**
7661
- - What momentum exists? What was recently accomplished, and what naturally follows?
7662
- - What is stalled or falling behind? Anything open too long without progress?
7663
- - What is scheduled soon? Does anything need prep work before it lands?
7664
- - What is the single highest-leverage thing that could happen next?
7665
-
7666
- **Then act \u2014 but only if you have something genuinely useful:**
7667
-
7668
- *Things the bot can do* \u2014 research, drafts, analysis, prep. Propose as a task via \`deskfree_propose\`. One focused task, not a batch.
7669
-
7670
- *Things the human should do* \u2014 nudges, reminders, conversation starters. Send via \`deskfree_send_message\`. Keep it brief and genuinely helpful, not nagging.
7671
-
7672
- **Rules:**
7673
- - Do not suggest things that are already open or scheduled \u2014 check first.
7674
- - Do not repeat suggestions the human ignored or rejected recently.
7675
- - Quality over quantity. One good insight beats five generic nudges.
7676
- - If everything looks healthy and active, do nothing. Silence is fine.`;
7677
- DESKFREE_SLEEP_DIRECTIVE = `## DeskFree \u2014 Nightly Sleep Cycle
7678
- You are the sleep agent. You run once per day to reflect, consolidate memory, and prepare for tomorrow.
7679
-
7680
- Tools available: deskfree_state, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_update_file.
7681
-
7682
- ---
7683
-
7684
- ### 1. REFLECT & CONSOLIDATE
7685
-
7686
- Your primary job: merge daily observations into the Memory file using strength-scored entries.
7687
-
7688
- 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.
7689
-
7690
- **Steps:**
7691
- 1. Review the current memory and daily observations from your prompt.
7692
- 2. Apply the memory curation rules below.
7693
- 3. Update the Memory file via \`deskfree_update_file\` using the file ID from your prompt.
7694
- 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.
7695
- 5. If nothing meaningful changed, skip the update and send a brief message like "Quiet day \u2014 nothing new to consolidate."
7696
-
7697
- **Memory Types**
7698
-
7699
- Each memory item carries a type tag and strength score: \`[type:X, strength:N]\`
7700
-
7701
- | Type | What it captures | Decay rate |
7702
- |------|-----------------|------------|
7703
- | \`correction\` | Explicit "do X not Y" from human | Very slow (\u22121 when strength \u226510, else no decay) |
7704
- | \`preference\` | How the human wants things done | Slow (\u22121 when strength \u22656, else no decay) |
7705
- | \`pattern\` | Approaches/workflows that work | Normal (see decay rules) |
7706
- | \`domain\` | Business/project-specific facts | Slow (\u22121 when strength \u22656, else no decay) |
7707
- | \`insight\` | Meta-observations from reflection | Normal (see decay rules) |
7708
-
7709
- Corrections and domain facts are durable \u2014 they rarely become irrelevant.
7710
- Patterns and insights decay normally \u2014 stale approaches should be forgotten.
7711
- Preferences sit in between \u2014 slow decay, but they can evolve.
7712
-
7713
- **Strength Scoring Rules**
7714
-
7715
- Strength uses Ebbinghaus-inspired logarithmic decay \u2014 strong memories resist forgetting:
7716
-
7717
- **Reinforcement (observation matches existing memory):**
7718
- - strength +2
7719
-
7720
- **Explicit correction ("actually do X not Y"):**
7721
- - Replace old memory, new one starts at [strength: 3, type: correction]
7722
-
7723
- **Decay (memory NOT referenced by any daily observation):**
7724
- - strength \u2265 10: decay by \u22121 (deeply encoded, slow forgetting)
7725
- - strength 5-9: decay by \u22122 (moderately encoded)
7726
- - strength 1-4: decay by \u22123 (weakly encoded, fast forgetting)
7727
- - EXCEPT: corrections and domain facts with strength \u22656 decay at \u22121 max (durable memories)
7728
- - EXCEPT: preferences with strength \u22656 decay at \u22121 max
7729
-
7730
- **Removal:**
7731
- - 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]).
7732
-
7733
- **New observation:**
7734
- - Assess importance: how consequential is this for future tasks?
7735
- - Look for [!] prefix (critical) or [~] prefix (notable) as importance signals from the worker.
7736
- - Low importance (casual mention, routine) \u2192 [strength: 1, type: <appropriate>]
7737
- - Medium importance (useful preference, [~] prefix) \u2192 [strength: 2, type: <appropriate>]
7738
- - High importance (explicit correction, strong constraint, [!] prefix) \u2192 [strength: 4, type: <appropriate>]
7739
- - Critical (production error, absolute rule) \u2192 [strength: 6, type: <appropriate>]
7740
-
7741
- **Consolidation rules:**
7742
- - Classify each memory with the correct type. Only keep genuinely reusable knowledge.
7743
- - Discard noise and one-off task details.
7744
- - 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.
7745
- - Look for meta-patterns: approaches that consistently work/fail, implicit preferences, recurring corrections.
7746
- - If you find non-obvious insights, add them as \`[type: insight, strength: 2]\` memories.
7747
- - Merge near-duplicates, remove items at [strength: 0], trim verbose entries.
7748
- - Keep total document under ~4000 words.
7749
- - Use ## headers for sections \u2014 let them emerge organically from content.
7750
-
7751
- ### 2. CHECK RECURRING COMMITMENTS
7752
-
7753
- After memory consolidation:
7754
- 1. Call \`deskfree_state\` to see the board.
7755
- 2. Cross-reference memory for recurring patterns (daily audits, weekly reviews, etc.).
7756
- 3. If a recurring commitment has no upcoming scheduled task, propose it via \`deskfree_propose\` with appropriate \`scheduledFor\`.
7757
- 4. Don't propose things already on the board or that were recently completed/ignored.
7758
-
7759
- ### 3. PROACTIVE OPPORTUNITIES
7760
-
7761
- Based on recent work, completed tasks, and patterns in memory \u2014 is there something genuinely useful to propose or a message worth sending?
7762
- - One focused proposal max. Skip if nothing merits it.
7763
- - Quality over quantity. Don't force it.
7764
-
7765
- ### Rules
7766
- - Keep main thread messages short (1-2 sentences).
7767
- - Do NOT propose things the human has previously ignored or rejected.
7768
- - 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.`;
7769
- DESKFREE_DUSK_DIRECTIVE = `## DeskFree \u2014 Evening Dusk Cycle
7770
- You are the dusk agent. You run once per day in the evening to review the day, propose overnight work, and brief the human.
7771
-
7772
- Tools available: deskfree_state, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_update_file.
7773
-
7774
- ---
7775
-
7776
- ### 1. REVIEW THE DAY
7777
-
7778
- 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.
7779
-
7780
- **Steps:**
7781
- 1. Review the current memory and daily observations from your prompt.
7782
- 2. Call \`deskfree_state\` to see the current board \u2014 open tasks, recently completed work, files.
7783
- 3. Build a mental model of what happened today: what was accomplished, what's in progress, what's stalled.
7784
-
7785
- ### 2. IDENTIFY OVERNIGHT OPPORTUNITIES
7786
-
7787
- Think about work that can be done autonomously overnight \u2014 WITHOUT human judgment or approval mid-task.
7788
-
7789
- **Good overnight work:**
7790
- - Research and analysis (market research, competitive analysis, reading material)
7791
- - Content drafts (blog posts, reports, documentation)
7792
- - File updates (updating recurring reports, preparing templates)
7793
- - Prep work for tomorrow (gathering context, pre-reading for scheduled meetings)
7794
- - Recurring tasks that are due (weekly reviews, daily audits)
7795
-
7796
- **NOT good overnight work:**
7797
- - Anything requiring human decisions mid-task
7798
- - Tasks that depend on external input not yet received
7799
- - Creative work where the human has strong opinions on direction
7800
- - Anything the human explicitly said to wait on
7801
-
7802
- ### 3. PROPOSE THE PLAN
7803
-
7804
- If you identified useful overnight work:
7805
- 1. Use \`deskfree_propose\` with 1-3 well-scoped tasks. Quality over quantity.
7806
- 2. Each task should be self-contained \u2014 a worker must be able to complete it without human input.
7807
- 3. Set \`scheduledFor\` if work should start at a specific time (e.g. early morning).
7808
- 4. If nothing genuinely useful can be done overnight, skip the proposal entirely. Don't force it.
7809
-
7810
- ### 4. BRIEF THE HUMAN
7811
-
7812
- Send a brief main-thread message via \`deskfree_send_message\`:
7813
- - 2-3 sentence summary: what happened today + what you're proposing for overnight (if anything).
7814
- - Keep it conversational and useful \u2014 this is the human's "end of day" touchpoint.
7815
- - If no proposals, still send a brief day summary.
7816
-
7817
- ### Rules
7818
- - Do NOT propose things already on the board or recently completed.
7819
- - Do NOT repeat suggestions the human previously ignored or rejected.
7820
- - Quality over quantity. One good task beats three mediocre ones.
7821
- - Keep the briefing message short and actionable.
7822
- - Cross-reference memory for recurring patterns \u2014 if something is due, propose it.
7823
- - Use \`deskfree_read_file\` only if you need file content beyond what's in your prompt.`;
7824
8162
  THROTTLE_MS = 300;
7825
8163
  CHAR_BUFFER_SIZE = 256;
7826
8164
  CLOSE_MAX_RETRIES = 3;
@@ -8032,7 +8370,15 @@ Send a brief main-thread message via \`deskfree_send_message\`:
8032
8370
  }
8033
8371
  });
8034
8372
  function runOrchestrator(opts) {
8035
- const { prompt, orchestratorServer, model, sessionId, claudeCodePath } = opts;
8373
+ const {
8374
+ prompt,
8375
+ orchestratorServer,
8376
+ model,
8377
+ sessionId,
8378
+ claudeCodePath,
8379
+ agentContext
8380
+ } = opts;
8381
+ const systemPrompt = agentContext ? buildAgentDirective(agentContext) : DESKFREE_AGENT_DIRECTIVE;
8036
8382
  return query({
8037
8383
  prompt,
8038
8384
  options: {
@@ -8040,7 +8386,7 @@ function runOrchestrator(opts) {
8040
8386
  process.stderr.write(`[orchestrator-sdk] ${data}
8041
8387
  `);
8042
8388
  },
8043
- systemPrompt: DESKFREE_AGENT_DIRECTIVE,
8389
+ systemPrompt,
8044
8390
  model,
8045
8391
  ...claudeCodePath ? { pathToClaudeCodeExecutable: claudeCodePath } : {},
8046
8392
  maxTurns: MAX_ORCHESTRATOR_TURNS,
@@ -8059,11 +8405,12 @@ function runOrchestrator(opts) {
8059
8405
  });
8060
8406
  }
8061
8407
  function runHeartbeat(opts) {
8062
- const { prompt, orchestratorServer, model, claudeCodePath } = opts;
8408
+ const { prompt, orchestratorServer, model, claudeCodePath, agentContext } = opts;
8409
+ const systemPrompt = agentContext ? buildHeartbeatDirective(agentContext) : DESKFREE_AGENT_DIRECTIVE;
8063
8410
  return query({
8064
8411
  prompt,
8065
8412
  options: {
8066
- systemPrompt: DESKFREE_AGENT_DIRECTIVE,
8413
+ systemPrompt,
8067
8414
  model,
8068
8415
  ...claudeCodePath ? { pathToClaudeCodeExecutable: claudeCodePath } : {},
8069
8416
  maxTurns: MAX_ORCHESTRATOR_TURNS,
@@ -8184,10 +8531,6 @@ function loadConfig() {
8184
8531
  };
8185
8532
  }
8186
8533
  function mergeWithRemoteConfig(local, remote) {
8187
- const stateDirOverridden = !!process.env["DESKFREE_STATE_DIR"];
8188
- const toolsDirOverridden = !!process.env["DESKFREE_TOOLS_DIR"];
8189
- const stateDir = stateDirOverridden ? local.stateDir : isDocker ? local.stateDir : `.deskfree/${remote.botId}/state`;
8190
- const toolsDir = toolsDirOverridden ? local.toolsDir : isDocker ? local.toolsDir : `.deskfree/${remote.botId}/tools`;
8191
8534
  let claudeCodePath;
8192
8535
  if (remote.provider === "claude-code") {
8193
8536
  try {
@@ -8202,8 +8545,6 @@ function mergeWithRemoteConfig(local, remote) {
8202
8545
  }
8203
8546
  return {
8204
8547
  ...local,
8205
- stateDir,
8206
- toolsDir,
8207
8548
  claudeCodePath,
8208
8549
  wsUrl: process.env["DESKFREE_WS_URL"] ?? remote.wsUrl,
8209
8550
  model: process.env["DESKFREE_MODEL"] ?? remote.model,
@@ -8215,6 +8556,8 @@ function mergeWithRemoteConfig(local, remote) {
8215
8556
  anthropicApiKey: remote.anthropicApiKey,
8216
8557
  baseUrl: process.env["DESKFREE_BASE_URL"] ?? remote.baseUrl,
8217
8558
  botId: remote.botId,
8559
+ botName: remote.botName,
8560
+ deploymentType: remote.deploymentType,
8218
8561
  memoryFileId: remote.memoryFileId,
8219
8562
  sleepHour: remote.sleepHour,
8220
8563
  duskHour: remote.duskHour,
@@ -8227,8 +8570,18 @@ var init_config = __esm({
8227
8570
  init_dist();
8228
8571
  isDocker = process.env["DOCKER"] === "1" || existsSync("/.dockerenv");
8229
8572
  DEFAULTS = {
8230
- stateDir: isDocker ? "/app/state" : ".deskfree/state",
8231
- toolsDir: isDocker ? "/app/tools" : ".deskfree/tools",
8573
+ stateDir: isDocker ? "/app/state" : join(
8574
+ homedir(),
8575
+ ".deskfree",
8576
+ process.env["DESKFREE_INSTANCE_NAME"] ?? "main",
8577
+ "state"
8578
+ ),
8579
+ toolsDir: isDocker ? "/app/tools" : join(
8580
+ homedir(),
8581
+ ".deskfree",
8582
+ process.env["DESKFREE_INSTANCE_NAME"] ?? "main",
8583
+ "tools"
8584
+ ),
8232
8585
  logLevel: "info",
8233
8586
  healthPort: 3100
8234
8587
  };
@@ -10480,9 +10833,9 @@ var require_event_target2 = __commonJS({
10480
10833
  var require_extension2 = __commonJS({
10481
10834
  "../../node_modules/ws/lib/extension.js"(exports$1, module) {
10482
10835
  var { tokenChars } = require_validation2();
10483
- function push(dest, name, elem) {
10484
- if (dest[name] === void 0) dest[name] = [elem];
10485
- else dest[name].push(elem);
10836
+ function push(dest, name2, elem) {
10837
+ if (dest[name2] === void 0) dest[name2] = [elem];
10838
+ else dest[name2].push(elem);
10486
10839
  }
10487
10840
  function parse(header) {
10488
10841
  const offers = /* @__PURE__ */ Object.create(null);
@@ -10508,12 +10861,12 @@ var require_extension2 = __commonJS({
10508
10861
  throw new SyntaxError(`Unexpected character at index ${i}`);
10509
10862
  }
10510
10863
  if (end === -1) end = i;
10511
- const name = header.slice(start, end);
10864
+ const name2 = header.slice(start, end);
10512
10865
  if (code === 44) {
10513
- push(offers, name, params);
10866
+ push(offers, name2, params);
10514
10867
  params = /* @__PURE__ */ Object.create(null);
10515
10868
  } else {
10516
- extensionName = name;
10869
+ extensionName = name2;
10517
10870
  }
10518
10871
  start = end = -1;
10519
10872
  } else {
@@ -12594,9 +12947,9 @@ function adaptTool(deskfreeTool) {
12594
12947
  deskfreeTool.name,
12595
12948
  deskfreeTool.description,
12596
12949
  zodShape,
12597
- async (args2) => {
12950
+ async (args) => {
12598
12951
  const result = await deskfreeTool.execute(
12599
- args2
12952
+ args
12600
12953
  );
12601
12954
  return {
12602
12955
  content: result.content,
@@ -12627,7 +12980,7 @@ function createOrchestratorMcpServer(client, customTools = [], workerManager) {
12627
12980
  function createDispatchWorkerTool(workerManager) {
12628
12981
  return {
12629
12982
  name: "deskfree_dispatch_worker",
12630
- description: "Dispatch a long-lived worker to claim and execute a task. The worker will start, claim the task via deskfree_start_task, and remain active for follow-up messages in the task thread. Pass the taskId of the approved task.",
12983
+ description: "Start working on an approved task in its thread. You will pick up the task, load context, and handle follow-up messages there. Pass the taskId of the approved task.",
12631
12984
  parameters: {
12632
12985
  type: "object",
12633
12986
  properties: {
@@ -12695,15 +13048,15 @@ function containsInjectionPattern(content) {
12695
13048
  return INJECTION_PATTERNS.some((pattern) => pattern.test(content));
12696
13049
  }
12697
13050
  function withContentScan(execute, extractContent, scanner) {
12698
- return async (args2) => {
12699
- const content = extractContent(args2);
13051
+ return async (args) => {
13052
+ const content = extractContent(args);
12700
13053
  if (content) {
12701
13054
  const result = await scanner.scan(content);
12702
13055
  if (!result.safe) {
12703
13056
  return `Security check failed: ${result.reason ?? "content rejected"}. File not updated.`;
12704
13057
  }
12705
13058
  }
12706
- return execute(args2);
13059
+ return execute(args);
12707
13060
  };
12708
13061
  }
12709
13062
  function validateDownloadUrl(url) {
@@ -12838,7 +13191,7 @@ function createWorkerMcpServer(client, customTools = [], contentScanner, dailyLo
12838
13191
  if ((t.name === "deskfree_update_file" || t.name === "deskfree_create_file") && contentScanner) {
12839
13192
  const wrappedExecute = withContentScan(
12840
13193
  t.execute,
12841
- (args2) => typeof args2["content"] === "string" ? args2["content"] : null,
13194
+ (args) => typeof args["content"] === "string" ? args["content"] : null,
12842
13195
  contentScanner
12843
13196
  );
12844
13197
  return {
@@ -13374,7 +13727,8 @@ async function routeMessage(message, client, deps, sessionStore, config) {
13374
13727
  orchestratorServer: deps.createOrchestratorServer(),
13375
13728
  model: deps.model,
13376
13729
  sessionId: existingSessionId,
13377
- claudeCodePath: deps.claudeCodePath
13730
+ claudeCodePath: deps.claudeCodePath,
13731
+ agentContext: deps.agentContext
13378
13732
  });
13379
13733
  let fullText = "";
13380
13734
  let capturedSessionId = null;
@@ -13557,7 +13911,8 @@ var init_sessions = __esm({
13557
13911
  }
13558
13912
  });
13559
13913
  function runWorker(opts) {
13560
- const { prompt, workerServer, model, sessionId } = opts;
13914
+ const { prompt, workerServer, model, sessionId, agentContext } = opts;
13915
+ const systemPrompt = agentContext ? buildWorkerDirective(agentContext) : DESKFREE_WORKER_DIRECTIVE;
13561
13916
  return query({
13562
13917
  prompt,
13563
13918
  options: {
@@ -13565,7 +13920,7 @@ function runWorker(opts) {
13565
13920
  process.stderr.write(`[worker-sdk] ${data}
13566
13921
  `);
13567
13922
  },
13568
- systemPrompt: DESKFREE_WORKER_DIRECTIVE,
13923
+ systemPrompt,
13569
13924
  model,
13570
13925
  maxTurns: MAX_WORKER_TURNS,
13571
13926
  permissionMode: "bypassPermissions",
@@ -13877,7 +14232,8 @@ ${userMessage}
13877
14232
  prompt: channel,
13878
14233
  workerServer,
13879
14234
  model,
13880
- sessionId: previousSessionId
14235
+ sessionId: previousSessionId,
14236
+ agentContext: this.deps.agentContext
13881
14237
  });
13882
14238
  const idleTimer = this.startIdleTimer(taskId);
13883
14239
  const drainPromise = this.drainLoop(
@@ -14123,7 +14479,7 @@ function startHealthServer(port, log) {
14123
14479
  }
14124
14480
  };
14125
14481
  }
14126
- function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, claudeCodePath) {
14482
+ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, claudeCodePath, agentContext) {
14127
14483
  if (intervalMs <= 0) return;
14128
14484
  let running = false;
14129
14485
  async function tick() {
@@ -14131,11 +14487,13 @@ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, cla
14131
14487
  running = true;
14132
14488
  try {
14133
14489
  log.debug("Heartbeat tick: checking for pending work...");
14490
+ const heartbeatPrompt = agentContext ? buildHeartbeatDirective(agentContext) : "Run your heartbeat check now.";
14134
14491
  const result = runHeartbeat({
14135
- prompt: DESKFREE_HEARTBEAT_DIRECTIVE,
14492
+ prompt: heartbeatPrompt,
14136
14493
  orchestratorServer: createOrchServer(),
14137
14494
  model,
14138
- claudeCodePath
14495
+ claudeCodePath,
14496
+ agentContext
14139
14497
  });
14140
14498
  for await (const _ of result) {
14141
14499
  }
@@ -14176,6 +14534,19 @@ async function startAgent(opts) {
14176
14534
  const msg = err instanceof Error ? err.message : String(err);
14177
14535
  throw new Error(`Failed to bootstrap config from API: ${msg}`);
14178
14536
  }
14537
+ const isDocker2 = process.env["DOCKER"] === "1" || (await import('fs')).existsSync("/.dockerenv");
14538
+ const runtimeVersion = process.env["npm_package_version"] ?? "unknown";
14539
+ const agentContext = {
14540
+ botName: config.botName,
14541
+ deploymentType: config.deploymentType,
14542
+ provider: config.provider,
14543
+ model: config.model,
14544
+ platform: isDocker2 ? "Docker" : process.platform === "darwin" ? "macOS" : "Linux",
14545
+ runtimeVersion,
14546
+ maxConcurrentWorkers: 5,
14547
+ // updated after WorkerManager is created
14548
+ instanceName: process.env["DESKFREE_INSTANCE_NAME"] || void 0
14549
+ };
14179
14550
  mkdirSync(config.stateDir, { recursive: true });
14180
14551
  mkdirSync(config.toolsDir, { recursive: true });
14181
14552
  const logFile = join(config.stateDir, "runtime.log");
@@ -14234,8 +14605,10 @@ async function startAgent(opts) {
14234
14605
  "memory",
14235
14606
  config.botId,
14236
14607
  "session-history.json"
14237
- )
14608
+ ),
14609
+ agentContext
14238
14610
  });
14611
+ agentContext.maxConcurrentWorkers = workerManager.maxConcurrentWorkers;
14239
14612
  const createOrchServer = () => createOrchestratorMcpServer(client, customTools, workerManager);
14240
14613
  const healthServer = startHealthServer(config.healthPort, log);
14241
14614
  const sessionStore = new SessionStore();
@@ -14262,7 +14635,8 @@ async function startAgent(opts) {
14262
14635
  createOrchestratorServer: createOrchServer,
14263
14636
  workerManager,
14264
14637
  model: config.model,
14265
- claudeCodePath: config.claudeCodePath
14638
+ claudeCodePath: config.claudeCodePath,
14639
+ agentContext
14266
14640
  },
14267
14641
  sessionStore,
14268
14642
  {
@@ -14278,7 +14652,8 @@ async function startAgent(opts) {
14278
14652
  config.heartbeatIntervalMs,
14279
14653
  abortController.signal,
14280
14654
  log,
14281
- config.claudeCodePath
14655
+ config.claudeCodePath,
14656
+ agentContext
14282
14657
  );
14283
14658
  if (config.memoryFileId && config.sleepHour !== null && config.timezone) {
14284
14659
  const memoryFileId = config.memoryFileId;
@@ -14315,7 +14690,7 @@ async function startAgent(opts) {
14315
14690
  const workerServer = createWorkServer();
14316
14691
  const result = runOneShotWorker({
14317
14692
  prompt,
14318
- systemPrompt: DESKFREE_SLEEP_DIRECTIVE,
14693
+ systemPrompt: buildSleepDirective(agentContext),
14319
14694
  workerServer,
14320
14695
  model: config.model
14321
14696
  });
@@ -14361,7 +14736,7 @@ async function startAgent(opts) {
14361
14736
  const workerServer = createWorkServer();
14362
14737
  const result = runOneShotWorker({
14363
14738
  prompt,
14364
- systemPrompt: DESKFREE_DUSK_DIRECTIVE,
14739
+ systemPrompt: buildDuskDirective(agentContext),
14365
14740
  workerServer,
14366
14741
  model: config.model
14367
14742
  });
@@ -14408,22 +14783,46 @@ var init_entrypoint = __esm({
14408
14783
  });
14409
14784
 
14410
14785
  // src/bin.ts
14411
- var args = process.argv.slice(2);
14412
- var command = args[0];
14786
+ init_paths();
14787
+ var [name, cleanArgs] = parseName(process.argv.slice(2));
14788
+ var command = cleanArgs[0];
14413
14789
  if (command === "install") {
14414
- const token = args[1];
14790
+ let token = cleanArgs[1];
14415
14791
  if (!token) {
14416
- console.error("Usage: deskfree-agent install <token>");
14417
- process.exit(1);
14792
+ const { createInterface } = await import('readline');
14793
+ const rl = createInterface({
14794
+ input: process.stdin,
14795
+ output: process.stdout
14796
+ });
14797
+ token = await new Promise((resolve) => {
14798
+ rl.question(
14799
+ "Paste your bot token (from the DeskFree dashboard):\n> ",
14800
+ (answer) => {
14801
+ rl.close();
14802
+ resolve(answer.trim());
14803
+ }
14804
+ );
14805
+ });
14806
+ if (!token) {
14807
+ console.error("No token provided.");
14808
+ process.exit(1);
14809
+ }
14418
14810
  }
14419
14811
  const { install: install2 } = await Promise.resolve().then(() => (init_install(), install_exports));
14420
- install2(token);
14812
+ install2(token, name);
14421
14813
  } else if (command === "uninstall") {
14422
14814
  const { uninstall: uninstall2 } = await Promise.resolve().then(() => (init_uninstall(), uninstall_exports));
14423
- uninstall2();
14815
+ uninstall2(name);
14424
14816
  } else if (command === "status") {
14425
14817
  const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
14426
- status2();
14818
+ status2(name);
14819
+ } else if (command === "restart") {
14820
+ const { restart: restart2 } = await Promise.resolve().then(() => (init_restart(), restart_exports));
14821
+ restart2(name);
14822
+ } else if (command === "logs") {
14823
+ const follow = cleanArgs.includes("-f") || cleanArgs.includes("--follow");
14824
+ const { logs: logs2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
14825
+ logs2(follow, name);
14427
14826
  } else {
14428
14827
  let handleShutdown = function(signal) {
14429
14828
  log.info(`Received ${signal} \u2014 shutting down...`);
@@ -14438,7 +14837,7 @@ if (command === "install") {
14438
14837
  };
14439
14838
  let token;
14440
14839
  if (command === "start") {
14441
- token = args[1];
14840
+ token = cleanArgs[1];
14442
14841
  } else if (command && !command.startsWith("-")) {
14443
14842
  token = command;
14444
14843
  }