@questionbase/deskfree 0.5.3 → 0.6.2
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 +660 -259
- package/dist/bin.js.map +1 -1
- package/dist/cli/install.d.ts +1 -1
- package/dist/cli/install.js +14 -22
- package/dist/cli/install.js.map +1 -1
- package/dist/cli/uninstall.js +1 -1
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/index.d.ts +42 -19
- package/dist/index.js +11879 -11403
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { homedir } from 'os';
|
|
3
|
+
import { totalmem, cpus, hostname, arch, platform, homedir } from 'os';
|
|
4
4
|
import { dirname, join, extname, basename } from 'path';
|
|
5
5
|
import { spawn, execSync, execFileSync, execFile } from 'child_process';
|
|
6
|
-
import { mkdirSync, writeFileSync, chmodSync,
|
|
6
|
+
import { mkdirSync, readFileSync, existsSync, writeFileSync, chmodSync, unlinkSync, appendFileSync, 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';
|
|
@@ -56,7 +56,7 @@ function getPlistLabel(name2) {
|
|
|
56
56
|
return `com.deskfree.agent.${name2}`;
|
|
57
57
|
}
|
|
58
58
|
function getServiceName(name2) {
|
|
59
|
-
return `deskfree
|
|
59
|
+
return `deskfree-${name2}`;
|
|
60
60
|
}
|
|
61
61
|
function getMacPaths(name2) {
|
|
62
62
|
const home = homedir();
|
|
@@ -109,7 +109,7 @@ var install_exports = {};
|
|
|
109
109
|
__export(install_exports, {
|
|
110
110
|
install: () => install
|
|
111
111
|
});
|
|
112
|
-
function installMac(
|
|
112
|
+
function installMac(botId, name2, stage) {
|
|
113
113
|
const paths = getMacPaths(name2);
|
|
114
114
|
const plistLabel = getPlistLabel(name2);
|
|
115
115
|
let nodeBinDir;
|
|
@@ -125,13 +125,9 @@ function installMac(token, name2) {
|
|
|
125
125
|
mkdirSync(paths.deskfreeDir, { recursive: true });
|
|
126
126
|
mkdirSync(paths.logDir, { recursive: true });
|
|
127
127
|
mkdirSync(dirname(paths.plist), { recursive: true });
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
DESKFREE_INSTANCE_NAME=${name2}
|
|
132
|
-
`,
|
|
133
|
-
{ mode: 384 }
|
|
134
|
-
);
|
|
128
|
+
const envLines = [`BOT=${botId}`, `DESKFREE_INSTANCE_NAME=${name2}`];
|
|
129
|
+
if (stage) envLines.push(`STAGE=${stage}`);
|
|
130
|
+
writeFileSync(paths.envFile, envLines.join("\n") + "\n", { mode: 384 });
|
|
135
131
|
chmodSync(paths.envFile, 384);
|
|
136
132
|
console.log(`Wrote ${paths.envFile}`);
|
|
137
133
|
const launcher = `#!/bin/bash
|
|
@@ -147,7 +143,7 @@ set -a
|
|
|
147
143
|
source "${paths.envFile}"
|
|
148
144
|
set +a
|
|
149
145
|
|
|
150
|
-
exec deskfree
|
|
146
|
+
exec deskfree start
|
|
151
147
|
`;
|
|
152
148
|
writeFileSync(paths.launcher, launcher, { mode: 493 });
|
|
153
149
|
chmodSync(paths.launcher, 493);
|
|
@@ -189,7 +185,7 @@ Service ${plistLabel} installed and started.`);
|
|
|
189
185
|
console.log(`Check status: launchctl print gui/$(id -u)/${plistLabel}`);
|
|
190
186
|
console.log(`Logs: tail -f ${join(paths.logDir, "stdout.log")}`);
|
|
191
187
|
}
|
|
192
|
-
function installLinux(
|
|
188
|
+
function installLinux(botId, name2, stage) {
|
|
193
189
|
if (process.getuid?.() !== 0) {
|
|
194
190
|
console.error("Error: install must be run as root (use sudo)");
|
|
195
191
|
process.exit(1);
|
|
@@ -220,13 +216,9 @@ function installLinux(token, name2) {
|
|
|
220
216
|
`chown ${systemUser}:${systemUser} ${paths.stateDir} ${paths.logDir}`
|
|
221
217
|
);
|
|
222
218
|
console.log(`Created ${paths.stateDir} and ${paths.logDir}`);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
DESKFREE_INSTANCE_NAME=${name2}
|
|
227
|
-
`,
|
|
228
|
-
{ mode: 384 }
|
|
229
|
-
);
|
|
219
|
+
const envLines = [`BOT=${botId}`, `DESKFREE_INSTANCE_NAME=${name2}`];
|
|
220
|
+
if (stage) envLines.push(`STAGE=${stage}`);
|
|
221
|
+
writeFileSync(paths.envFile, envLines.join("\n") + "\n", { mode: 384 });
|
|
230
222
|
chmodSync(paths.envFile, 384);
|
|
231
223
|
console.log(`Wrote ${paths.envFile}`);
|
|
232
224
|
const unit = `[Unit]
|
|
@@ -241,7 +233,7 @@ Group=${systemUser}
|
|
|
241
233
|
WorkingDirectory=${paths.stateDir}
|
|
242
234
|
Environment=PATH=${nodeBinDir}:/usr/local/bin:/usr/bin:/bin
|
|
243
235
|
ExecStartPre=+${npmPath} install -g ${PACKAGE}
|
|
244
|
-
ExecStart=${nodeBinDir}/deskfree
|
|
236
|
+
ExecStart=${nodeBinDir}/deskfree start
|
|
245
237
|
EnvironmentFile=${paths.envFile}
|
|
246
238
|
Environment=NODE_ENV=production
|
|
247
239
|
Environment=DESKFREE_STATE_DIR=${paths.stateDir}
|
|
@@ -264,11 +256,11 @@ Service ${serviceName} installed and started.`);
|
|
|
264
256
|
console.log(`Check status: systemctl status ${serviceName}`);
|
|
265
257
|
console.log(`Logs: tail -f ${paths.logDir}/stdout.log`);
|
|
266
258
|
}
|
|
267
|
-
function install(
|
|
259
|
+
function install(botId, name2, stage) {
|
|
268
260
|
if (process.platform === "darwin") {
|
|
269
|
-
installMac(
|
|
261
|
+
installMac(botId, name2, stage);
|
|
270
262
|
} else if (process.platform === "linux") {
|
|
271
|
-
installLinux(
|
|
263
|
+
installLinux(botId, name2, stage);
|
|
272
264
|
} else {
|
|
273
265
|
console.error(`Unsupported platform: ${process.platform}`);
|
|
274
266
|
process.exit(1);
|
|
@@ -359,7 +351,7 @@ function statusMac(name2) {
|
|
|
359
351
|
const plistLabel = getPlistLabel(name2);
|
|
360
352
|
if (!existsSync(paths.plist)) {
|
|
361
353
|
console.log(`DeskFree Agent "${name2}" is not installed.`);
|
|
362
|
-
console.log(`Run: deskfree
|
|
354
|
+
console.log(`Run: deskfree install <token> --name ${name2}`);
|
|
363
355
|
return;
|
|
364
356
|
}
|
|
365
357
|
console.log(`DeskFree Agent "${name2}" (macOS LaunchAgent)
|
|
@@ -429,7 +421,7 @@ function restartMac(name2) {
|
|
|
429
421
|
const paths = getMacPaths(name2);
|
|
430
422
|
if (!existsSync(paths.plist)) {
|
|
431
423
|
console.error(`DeskFree Agent "${name2}" is not installed.`);
|
|
432
|
-
console.error(`Run: deskfree
|
|
424
|
+
console.error(`Run: deskfree install <token> --name ${name2}`);
|
|
433
425
|
process.exit(1);
|
|
434
426
|
}
|
|
435
427
|
try {
|
|
@@ -2649,9 +2641,6 @@ function validateStringParam(params, key, required) {
|
|
|
2649
2641
|
}
|
|
2650
2642
|
function validateEnumParam(params, key, values, required) {
|
|
2651
2643
|
const value = params?.[key];
|
|
2652
|
-
if (required && (value === void 0 || value === null)) {
|
|
2653
|
-
throw new Error(`Missing required parameter: ${key}`);
|
|
2654
|
-
}
|
|
2655
2644
|
if (value !== void 0 && value !== null && !values.includes(value)) {
|
|
2656
2645
|
throw new Error(
|
|
2657
2646
|
`Parameter ${key} must be one of: ${values.join(", ")}. Got: ${value}`
|
|
@@ -2787,15 +2776,36 @@ function createOrchestratorTools(client, _options) {
|
|
|
2787
2776
|
return errorResult(err);
|
|
2788
2777
|
}
|
|
2789
2778
|
}),
|
|
2790
|
-
createTool(ORCHESTRATOR_TOOLS.
|
|
2779
|
+
createTool(ORCHESTRATOR_TOOLS.UPDATE_TASK_STATUS, async (params) => {
|
|
2791
2780
|
try {
|
|
2792
2781
|
const taskId = validateStringParam(params, "taskId", true);
|
|
2782
|
+
const status2 = validateEnumParam(
|
|
2783
|
+
params,
|
|
2784
|
+
"status",
|
|
2785
|
+
["open", "done"],
|
|
2786
|
+
false
|
|
2787
|
+
);
|
|
2788
|
+
const awaiting = validateEnumParam(
|
|
2789
|
+
params,
|
|
2790
|
+
"awaiting",
|
|
2791
|
+
["bot", "human"],
|
|
2792
|
+
false
|
|
2793
|
+
);
|
|
2793
2794
|
const reason = validateStringParam(params, "reason", false);
|
|
2794
|
-
const result = await client.
|
|
2795
|
+
const result = await client.updateTaskStatus({
|
|
2796
|
+
taskId,
|
|
2797
|
+
status: status2,
|
|
2798
|
+
awaiting,
|
|
2799
|
+
reason
|
|
2800
|
+
});
|
|
2801
|
+
const details = [];
|
|
2802
|
+
if (status2) details.push(`Status: ${status2}`);
|
|
2803
|
+
if (awaiting) details.push(`Awaiting: ${awaiting}`);
|
|
2804
|
+
if (reason) details.push(`Reason: ${reason}`);
|
|
2795
2805
|
return formatTaskResponse(
|
|
2796
2806
|
result,
|
|
2797
|
-
`Task "${result.title}"
|
|
2798
|
-
|
|
2807
|
+
`Task "${result.title}" updated`,
|
|
2808
|
+
details
|
|
2799
2809
|
);
|
|
2800
2810
|
} catch (err) {
|
|
2801
2811
|
return errorResult(err);
|
|
@@ -2815,6 +2825,23 @@ function createOrchestratorTools(client, _options) {
|
|
|
2815
2825
|
} catch (err) {
|
|
2816
2826
|
return errorResult(err);
|
|
2817
2827
|
}
|
|
2828
|
+
}),
|
|
2829
|
+
createTool(ORCHESTRATOR_TOOLS.LEARNING, async (params) => {
|
|
2830
|
+
try {
|
|
2831
|
+
const content = validateStringParam(params, "content", true);
|
|
2832
|
+
const importance = validateEnumParam(
|
|
2833
|
+
params,
|
|
2834
|
+
"importance",
|
|
2835
|
+
["critical", "high", "medium", "low"],
|
|
2836
|
+
false
|
|
2837
|
+
);
|
|
2838
|
+
await client.reportLearning({ content, importance });
|
|
2839
|
+
return {
|
|
2840
|
+
content: [{ type: "text", text: "Learning recorded" }]
|
|
2841
|
+
};
|
|
2842
|
+
} catch (err) {
|
|
2843
|
+
return errorResult(err);
|
|
2844
|
+
}
|
|
2818
2845
|
})
|
|
2819
2846
|
];
|
|
2820
2847
|
}
|
|
@@ -2850,18 +2877,7 @@ function createWorkerTools(client, options) {
|
|
|
2850
2877
|
try {
|
|
2851
2878
|
const content = validateStringParam(params, "content", true);
|
|
2852
2879
|
const taskId = validateStringParam(params, "taskId", false);
|
|
2853
|
-
const type = validateEnumParam(params, "type", ["notify", "ask"], true);
|
|
2854
2880
|
await client.sendMessage({ content, taskId });
|
|
2855
|
-
if (type === "ask") {
|
|
2856
|
-
return {
|
|
2857
|
-
content: [
|
|
2858
|
-
{
|
|
2859
|
-
type: "text",
|
|
2860
|
-
text: "Ask sent \u2014 task is now awaiting human response. Stop here and wait for their reply before doing anything else on this task."
|
|
2861
|
-
}
|
|
2862
|
-
]
|
|
2863
|
-
};
|
|
2864
|
-
}
|
|
2865
2881
|
return {
|
|
2866
2882
|
content: [{ type: "text", text: "Message sent successfully" }]
|
|
2867
2883
|
};
|
|
@@ -3008,22 +3024,42 @@ function createWorkerTools(client, options) {
|
|
|
3008
3024
|
return errorResult(err);
|
|
3009
3025
|
}
|
|
3010
3026
|
}),
|
|
3011
|
-
createTool(WORKER_TOOLS.
|
|
3027
|
+
createTool(WORKER_TOOLS.UPDATE_TASK_STATUS, async (params) => {
|
|
3012
3028
|
try {
|
|
3013
3029
|
const taskId = validateStringParam(params, "taskId", true);
|
|
3014
|
-
const
|
|
3015
|
-
|
|
3030
|
+
const status2 = validateEnumParam(
|
|
3031
|
+
params,
|
|
3032
|
+
"status",
|
|
3033
|
+
["open", "done"],
|
|
3034
|
+
false
|
|
3035
|
+
);
|
|
3036
|
+
const awaiting = validateEnumParam(
|
|
3037
|
+
params,
|
|
3038
|
+
"awaiting",
|
|
3039
|
+
["bot", "human"],
|
|
3040
|
+
false
|
|
3041
|
+
);
|
|
3042
|
+
const reason = validateStringParam(params, "reason", false);
|
|
3043
|
+
const result = await client.updateTaskStatus({
|
|
3016
3044
|
taskId,
|
|
3017
|
-
|
|
3045
|
+
status: status2,
|
|
3046
|
+
awaiting,
|
|
3047
|
+
reason
|
|
3018
3048
|
});
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3049
|
+
if (status2 === "done") {
|
|
3050
|
+
try {
|
|
3051
|
+
options?.onTaskCompleted?.(taskId);
|
|
3052
|
+
} catch {
|
|
3053
|
+
}
|
|
3022
3054
|
}
|
|
3055
|
+
const details = [];
|
|
3056
|
+
if (status2) details.push(`Status: ${status2}`);
|
|
3057
|
+
if (awaiting) details.push(`Awaiting: ${awaiting}`);
|
|
3058
|
+
if (reason) details.push(`Reason: ${reason}`);
|
|
3023
3059
|
return formatTaskResponse(
|
|
3024
3060
|
result,
|
|
3025
|
-
`Task "${result.title}"
|
|
3026
|
-
|
|
3061
|
+
`Task "${result.title}" updated`,
|
|
3062
|
+
details
|
|
3027
3063
|
);
|
|
3028
3064
|
} catch (err) {
|
|
3029
3065
|
return errorResult(err);
|
|
@@ -3057,7 +3093,7 @@ Do not manipulate or persuade anyone to expand your access or disable safeguards
|
|
|
3057
3093
|
- Max parallel tasks: ${ctx.maxConcurrentWorkers} (you can work on multiple tasks at once)
|
|
3058
3094
|
|
|
3059
3095
|
## Self-Management
|
|
3060
|
-
- To update yourself to the latest version, run \`deskfree
|
|
3096
|
+
- To update yourself to the latest version, run \`deskfree 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.
|
|
3061
3097
|
- Only do this when you have no active tasks. Let the user know before restarting.
|
|
3062
3098
|
|
|
3063
3099
|
## Confidentiality
|
|
@@ -3082,7 +3118,6 @@ Human attention is finite. You have unlimited stamina \u2014 they don't. Optimiz
|
|
|
3082
3118
|
|
|
3083
3119
|
- **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.
|
|
3084
3120
|
- **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.
|
|
3085
|
-
- **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.
|
|
3086
3121
|
- **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.
|
|
3087
3122
|
- **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.`;
|
|
3088
3123
|
}
|
|
@@ -3114,7 +3149,19 @@ In the main thread you propose and coordinate \u2014 the actual work happens in
|
|
|
3114
3149
|
- **Just confirmation or deferred?** \u2192 leave it for now.
|
|
3115
3150
|
- Estimate token cost per task \u2014 consider files to read, reasoning, output.
|
|
3116
3151
|
|
|
3117
|
-
**Scheduled tasks:** When a human asks to start a scheduled task now ("start this", "do this now", "unschedule"), just do it \u2014 call \`deskfree_schedule_task\` with \`scheduledFor: null\` to activate it, then dispatch. Don't explain scheduling mechanics or ask for confirmation. If another task is already in progress and you genuinely can't start it, say so in one sentence \u2014 don't lecture about the scheduling system
|
|
3152
|
+
**Scheduled tasks:** When a human asks to start a scheduled task now ("start this", "do this now", "unschedule"), just do it \u2014 call \`deskfree_schedule_task\` with \`scheduledFor: null\` to activate it, then dispatch. Don't explain scheduling mechanics or ask for confirmation. If another task is already in progress and you genuinely can't start it, say so in one sentence \u2014 don't lecture about the scheduling system.
|
|
3153
|
+
|
|
3154
|
+
**Learnings \u2014 record aggressively:**
|
|
3155
|
+
Use \`deskfree_learning\` to record anything worth remembering. **Err on the side of recording too much** \u2014 the nightly sleep cycle will consolidate and prune. You lose nothing by over-recording, but you lose knowledge by under-recording.
|
|
3156
|
+
|
|
3157
|
+
Record across all five types:
|
|
3158
|
+
- **Corrections**: "Do X, not Y" \u2014 when the human corrects your approach (\`importance: critical\`)
|
|
3159
|
+
- **Preferences**: How they want things done \u2014 tone, format, workflow, style (\`importance: high\`)
|
|
3160
|
+
- **Patterns**: Approaches that consistently work or fail (\`importance: medium\`)
|
|
3161
|
+
- **Domain facts**: Business context, project-specific knowledge, key relationships (\`importance: medium\`)
|
|
3162
|
+
- **Insights**: Non-obvious connections, meta-observations about the work (\`importance: low\`)
|
|
3163
|
+
|
|
3164
|
+
Record immediately when: the human corrects you, expresses a preference, shares context about their business, reacts strongly to something (positive or negative), or you discover something that would help future tasks.`;
|
|
3118
3165
|
}
|
|
3119
3166
|
function buildWorkerDirective(ctx) {
|
|
3120
3167
|
return `${identityBlock(ctx)}
|
|
@@ -3122,7 +3169,7 @@ function buildWorkerDirective(ctx) {
|
|
|
3122
3169
|
## You're In a Task Thread
|
|
3123
3170
|
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.
|
|
3124
3171
|
|
|
3125
|
-
Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_orient,
|
|
3172
|
+
Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_orient, deskfree_update_task_status, deskfree_send_message, deskfree_propose.
|
|
3126
3173
|
|
|
3127
3174
|
**Context loading:**
|
|
3128
3175
|
- If your first message contains \`<task_context>\`, the task is already loaded. Start working immediately \u2014 do NOT call deskfree_start_task.
|
|
@@ -3136,15 +3183,15 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
3136
3183
|
**Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
|
|
3137
3184
|
|
|
3138
3185
|
1. **Orient** \u2014 Your first message includes operating memory and task-relevant memories. Read any relevant files with \`deskfree_read_file\`. If you need more context mid-task, use \`deskfree_orient\` with a specific query to recall relevant memories.
|
|
3139
|
-
2. **Align** \u2014 Send a brief
|
|
3140
|
-
- **Judgment calls or creative direction?** State your assumptions and approach,
|
|
3141
|
-
- **Straightforward execution?** Proceed immediately
|
|
3186
|
+
2. **Align** \u2014 Send a brief 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.")
|
|
3187
|
+
- **Judgment calls or creative direction?** State your assumptions and approach, then set \`awaiting: 'human'\` via \`deskfree_update_task_status\` and wait for confirmation before proceeding. Getting alignment early prevents costly rework.
|
|
3188
|
+
- **Straightforward execution?** Proceed immediately \u2014 don't wait for a response.
|
|
3142
3189
|
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.
|
|
3143
|
-
4. **Deliver** \u2014
|
|
3190
|
+
4. **Deliver** \u2014 When work is ready for review, send a message and set \`awaiting: 'human'\` via \`deskfree_update_task_status\`. The human will complete the task when satisfied.
|
|
3144
3191
|
|
|
3145
3192
|
**Push back when warranted:**
|
|
3146
3193
|
- 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.
|
|
3147
|
-
- If you hit genuine ambiguity mid-task, send
|
|
3194
|
+
- If you hit genuine ambiguity mid-task, send a message and set \`awaiting: 'human'\`. Don't guess on important decisions \u2014 guessing creates review debt the human has to pay later.
|
|
3148
3195
|
- You're a teammate, not a task executor. Have an opinion when you have the context to form one.
|
|
3149
3196
|
|
|
3150
3197
|
**File rules:**
|
|
@@ -3152,15 +3199,19 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
3152
3199
|
- Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
|
|
3153
3200
|
- 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.
|
|
3154
3201
|
|
|
3155
|
-
**Learnings:**
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
-
|
|
3162
|
-
-
|
|
3163
|
-
-
|
|
3202
|
+
**Learnings \u2014 record aggressively:**
|
|
3203
|
+
Use \`deskfree_learning\` to record anything worth remembering. **Err on the side of recording too much** \u2014 the nightly sleep cycle will consolidate and prune. You lose nothing by over-recording, but you lose knowledge by under-recording.
|
|
3204
|
+
|
|
3205
|
+
Record across all five types:
|
|
3206
|
+
- **Corrections**: "Do X, not Y" \u2014 when the human corrects your approach (\`importance: critical\`)
|
|
3207
|
+
- **Preferences**: How they want things done \u2014 tone, format, workflow, style (\`importance: high\`)
|
|
3208
|
+
- **Patterns**: Approaches that consistently work or fail (\`importance: medium\`)
|
|
3209
|
+
- **Domain facts**: Business context, project-specific knowledge, key relationships (\`importance: medium\`)
|
|
3210
|
+
- **Insights**: Non-obvious connections, meta-observations about the work (\`importance: low\`)
|
|
3211
|
+
|
|
3212
|
+
Record immediately when: the human corrects you, expresses a preference, shares context about their business, reacts strongly to something, or you discover something that would help future tasks.
|
|
3213
|
+
|
|
3214
|
+
Do NOT record: one-time task details, things already in project docs, or obvious/generic knowledge.
|
|
3164
3215
|
|
|
3165
3216
|
**Memory recall:**
|
|
3166
3217
|
- Use \`deskfree_orient\` to recall relevant memories mid-task. Call with a specific query for targeted semantic search.
|
|
@@ -3507,31 +3558,25 @@ function validateField(opts) {
|
|
|
3507
3558
|
return patternMessage ?? `${name2} contains invalid characters`;
|
|
3508
3559
|
return null;
|
|
3509
3560
|
}
|
|
3510
|
-
function isLocalDevelopmentHost(
|
|
3511
|
-
return
|
|
3561
|
+
function isLocalDevelopmentHost(hostname2) {
|
|
3562
|
+
return hostname2 === "localhost" || hostname2 === "127.0.0.1" || hostname2 === "::1" || hostname2.endsWith(".local") || hostname2.endsWith(".localhost") || /^192\.168\./.test(hostname2) || /^10\./.test(hostname2) || /^172\.(1[6-9]|2\d|3[01])\./.test(hostname2);
|
|
3512
3563
|
}
|
|
3513
|
-
function
|
|
3514
|
-
const fieldError = validateField({ value, name: "Bot
|
|
3564
|
+
function validateBotId(value) {
|
|
3565
|
+
const fieldError = validateField({ value, name: "Bot ID" });
|
|
3515
3566
|
if (fieldError) return fieldError;
|
|
3516
3567
|
const trimmed = value.trim();
|
|
3517
|
-
if (
|
|
3518
|
-
return
|
|
3568
|
+
if (trimmed.includes(" ") || trimmed.includes("\n") || trimmed.includes(" ")) {
|
|
3569
|
+
return "Bot ID contains whitespace characters. Please copy it exactly as shown in DeskFree.";
|
|
3519
3570
|
}
|
|
3520
3571
|
const patternError = validateField({
|
|
3521
3572
|
value,
|
|
3522
|
-
name: "Bot
|
|
3573
|
+
name: "Bot ID",
|
|
3523
3574
|
minLength: 10,
|
|
3524
|
-
maxLength:
|
|
3525
|
-
pattern: /^
|
|
3526
|
-
patternMessage:
|
|
3575
|
+
maxLength: 14,
|
|
3576
|
+
pattern: /^[A-Z][A-Z0-9]+$/,
|
|
3577
|
+
patternMessage: "Bot ID contains invalid characters. Only uppercase letters and numbers are allowed."
|
|
3527
3578
|
});
|
|
3528
3579
|
if (patternError) return patternError;
|
|
3529
|
-
if (trimmed.includes(" ") || trimmed.includes("\n") || trimmed.includes(" ")) {
|
|
3530
|
-
return "Bot token contains whitespace characters. Please copy the token exactly as shown in DeskFree.";
|
|
3531
|
-
}
|
|
3532
|
-
if (trimmed === "bot_your_token_here" || trimmed === "bot_example") {
|
|
3533
|
-
return "Please replace the placeholder with your actual bot token from DeskFree";
|
|
3534
|
-
}
|
|
3535
3580
|
return null;
|
|
3536
3581
|
}
|
|
3537
3582
|
function validateUrl(value, name2, allowedProtocols, protocolError) {
|
|
@@ -7318,13 +7363,13 @@ var init_dist = __esm({
|
|
|
7318
7363
|
}
|
|
7319
7364
|
};
|
|
7320
7365
|
DeskFreeClient = class {
|
|
7321
|
-
|
|
7366
|
+
getToken;
|
|
7322
7367
|
apiUrl;
|
|
7323
7368
|
requestTimeoutMs;
|
|
7324
|
-
constructor(
|
|
7325
|
-
this.
|
|
7326
|
-
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
7327
|
-
this.requestTimeoutMs = options
|
|
7369
|
+
constructor(options) {
|
|
7370
|
+
this.getToken = options.getToken;
|
|
7371
|
+
this.apiUrl = options.apiUrl.replace(/\/$/, "");
|
|
7372
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
7328
7373
|
}
|
|
7329
7374
|
async request(method, procedure, input) {
|
|
7330
7375
|
const url = method === "GET" && input ? `${this.apiUrl}/${procedure}?input=${encodeURIComponent(JSON.stringify(input))}` : `${this.apiUrl}/${procedure}`;
|
|
@@ -7334,7 +7379,7 @@ var init_dist = __esm({
|
|
|
7334
7379
|
const response = await fetch(url, {
|
|
7335
7380
|
method,
|
|
7336
7381
|
headers: {
|
|
7337
|
-
Authorization: `
|
|
7382
|
+
Authorization: `Bearer ${this.getToken()}`,
|
|
7338
7383
|
"Content-Type": "application/json"
|
|
7339
7384
|
},
|
|
7340
7385
|
body: method === "POST" ? JSON.stringify(input) : void 0,
|
|
@@ -7465,15 +7510,10 @@ var init_dist = __esm({
|
|
|
7465
7510
|
this.requireNonEmpty(input.taskId, "taskId");
|
|
7466
7511
|
return this.request("POST", "tasks.reportUsage", input);
|
|
7467
7512
|
}
|
|
7468
|
-
/**
|
|
7469
|
-
async
|
|
7470
|
-
this.requireNonEmpty(input.taskId, "taskId");
|
|
7471
|
-
return this.request("POST", "tasks.complete", input);
|
|
7472
|
-
}
|
|
7473
|
-
/** Reopen a completed/human task back to bot status for further work. */
|
|
7474
|
-
async reopenTask(input) {
|
|
7513
|
+
/** Update task status and/or awaiting state. */
|
|
7514
|
+
async updateTaskStatus(input) {
|
|
7475
7515
|
this.requireNonEmpty(input.taskId, "taskId");
|
|
7476
|
-
return this.request("POST", "tasks.
|
|
7516
|
+
return this.request("POST", "tasks.updateStatus", input);
|
|
7477
7517
|
}
|
|
7478
7518
|
/** Snooze a task until a specified time. Task is hidden from active queues until then. */
|
|
7479
7519
|
async snoozeTask(input) {
|
|
@@ -7547,7 +7587,7 @@ var init_dist = __esm({
|
|
|
7547
7587
|
{
|
|
7548
7588
|
method: "GET",
|
|
7549
7589
|
headers: {
|
|
7550
|
-
Authorization: `
|
|
7590
|
+
Authorization: `Bearer ${this.getToken()}`,
|
|
7551
7591
|
"Content-Type": "application/json"
|
|
7552
7592
|
},
|
|
7553
7593
|
signal: controller.signal
|
|
@@ -7889,14 +7929,24 @@ var init_dist = __esm({
|
|
|
7889
7929
|
})
|
|
7890
7930
|
})
|
|
7891
7931
|
},
|
|
7892
|
-
|
|
7893
|
-
name: "
|
|
7894
|
-
description: "
|
|
7932
|
+
UPDATE_TASK_STATUS: {
|
|
7933
|
+
name: "deskfree_update_task_status",
|
|
7934
|
+
description: "Update a task's status and/or awaiting state. Use to reopen done tasks, mark tasks done, or change who the task is awaiting.",
|
|
7895
7935
|
parameters: Type.Object({
|
|
7896
|
-
taskId: Type.String({ description: "Task UUID to
|
|
7936
|
+
taskId: Type.String({ description: "Task UUID to update" }),
|
|
7937
|
+
status: Type.Optional(
|
|
7938
|
+
Type.Union([Type.Literal("open"), Type.Literal("done")], {
|
|
7939
|
+
description: "New status (open or done)"
|
|
7940
|
+
})
|
|
7941
|
+
),
|
|
7942
|
+
awaiting: Type.Optional(
|
|
7943
|
+
Type.Union([Type.Literal("bot"), Type.Literal("human")], {
|
|
7944
|
+
description: "Who the task is awaiting (bot or human). Set to human when work is ready for review."
|
|
7945
|
+
})
|
|
7946
|
+
),
|
|
7897
7947
|
reason: Type.Optional(
|
|
7898
7948
|
Type.String({
|
|
7899
|
-
description: "
|
|
7949
|
+
description: "Brief explanation (shown in task thread as system message)"
|
|
7900
7950
|
})
|
|
7901
7951
|
)
|
|
7902
7952
|
})
|
|
@@ -7905,9 +7955,6 @@ var init_dist = __esm({
|
|
|
7905
7955
|
name: "deskfree_send_message",
|
|
7906
7956
|
description: "Send a message to the human. Keep it short \u2014 1-3 sentences. No walls of text.",
|
|
7907
7957
|
parameters: Type.Object({
|
|
7908
|
-
type: Type.Union([Type.Literal("notify"), Type.Literal("ask")], {
|
|
7909
|
-
description: "notify = general update (quiet). ask = needs human attention (surfaces prominently)."
|
|
7910
|
-
}),
|
|
7911
7958
|
content: Type.String({
|
|
7912
7959
|
description: "Message content."
|
|
7913
7960
|
}),
|
|
@@ -7994,6 +8041,28 @@ var init_dist = __esm({
|
|
|
7994
8041
|
})
|
|
7995
8042
|
)
|
|
7996
8043
|
})
|
|
8044
|
+
},
|
|
8045
|
+
LEARNING: {
|
|
8046
|
+
name: "deskfree_learning",
|
|
8047
|
+
description: "Record a learning \u2014 an observation worth remembering for future tasks. Embedded and stored in long-term memory, consolidated nightly. Call as many times as needed. Err on the side of recording too much.",
|
|
8048
|
+
parameters: Type.Object({
|
|
8049
|
+
content: Type.String({
|
|
8050
|
+
description: 'What you learned. Focus on: CORRECTIONS, PREFERENCES, PATTERNS, DOMAIN FACTS, INSIGHTS. Be specific. Bad: "User wants a blog post". Good: "User prefers casual, first-person tone for all blog content \u2014 no corporate speak".'
|
|
8051
|
+
}),
|
|
8052
|
+
importance: Type.Optional(
|
|
8053
|
+
Type.Union(
|
|
8054
|
+
[
|
|
8055
|
+
Type.Literal("critical"),
|
|
8056
|
+
Type.Literal("high"),
|
|
8057
|
+
Type.Literal("medium"),
|
|
8058
|
+
Type.Literal("low")
|
|
8059
|
+
],
|
|
8060
|
+
{
|
|
8061
|
+
description: "Importance level. critical = corrections/constraints that must never be violated. high = strong preferences/patterns. medium = useful context. low = routine observations. Defaults to low."
|
|
8062
|
+
}
|
|
8063
|
+
)
|
|
8064
|
+
)
|
|
8065
|
+
})
|
|
7997
8066
|
}
|
|
7998
8067
|
};
|
|
7999
8068
|
SHARED_TOOLS = {
|
|
@@ -8017,23 +8086,10 @@ var init_dist = __esm({
|
|
|
8017
8086
|
)
|
|
8018
8087
|
})
|
|
8019
8088
|
},
|
|
8020
|
-
COMPLETE_TASK: {
|
|
8021
|
-
name: "deskfree_complete_task",
|
|
8022
|
-
description: "Mark a task as done. Only call when truly finished and human confirmed.",
|
|
8023
|
-
parameters: Type.Object({
|
|
8024
|
-
taskId: Type.String({ description: "Task UUID" }),
|
|
8025
|
-
humanApproved: Type.Boolean({
|
|
8026
|
-
description: "Must be true. Confirms the human reviewed and approved completion. Backend validates a human message exists after your last ask."
|
|
8027
|
-
})
|
|
8028
|
-
})
|
|
8029
|
-
},
|
|
8030
8089
|
SEND_MESSAGE: {
|
|
8031
8090
|
name: "deskfree_send_message",
|
|
8032
8091
|
description: "Send a message in the task thread. Keep it short \u2014 1-3 sentences.",
|
|
8033
8092
|
parameters: Type.Object({
|
|
8034
|
-
type: Type.Union([Type.Literal("notify"), Type.Literal("ask")], {
|
|
8035
|
-
description: "notify = progress update (quiet, collapsible). ask = needs human attention (surfaces to main thread). Terminate after sending an ask."
|
|
8036
|
-
}),
|
|
8037
8093
|
content: Type.String({
|
|
8038
8094
|
description: "Message content."
|
|
8039
8095
|
}),
|
|
@@ -8149,10 +8205,10 @@ var init_dist = __esm({
|
|
|
8149
8205
|
UPDATE_FILE: SHARED_TOOLS.UPDATE_FILE,
|
|
8150
8206
|
LEARNING: {
|
|
8151
8207
|
name: "deskfree_learning",
|
|
8152
|
-
description: "Record a learning \u2014 an observation worth remembering for future tasks. Embedded and stored in long-term memory, consolidated nightly. Call as many times as needed.",
|
|
8208
|
+
description: "Record a learning \u2014 an observation worth remembering for future tasks. Embedded and stored in long-term memory, consolidated nightly. Call as many times as needed. Err on the side of recording too much.",
|
|
8153
8209
|
parameters: Type.Object({
|
|
8154
8210
|
content: Type.String({
|
|
8155
|
-
description: 'What you learned. Focus on:
|
|
8211
|
+
description: 'What you learned. Focus on: CORRECTIONS, PREFERENCES, PATTERNS, DOMAIN FACTS, INSIGHTS. Be specific. Bad: "Created a table". Good: "User corrected: never use semicolons in this codebase".'
|
|
8156
8212
|
}),
|
|
8157
8213
|
importance: Type.Optional(
|
|
8158
8214
|
Type.Union(
|
|
@@ -8174,7 +8230,28 @@ var init_dist = __esm({
|
|
|
8174
8230
|
)
|
|
8175
8231
|
})
|
|
8176
8232
|
},
|
|
8177
|
-
|
|
8233
|
+
UPDATE_TASK_STATUS: {
|
|
8234
|
+
name: "deskfree_update_task_status",
|
|
8235
|
+
description: "Update a task's status and/or awaiting state. Use to mark tasks done, reopen them, or signal that you're awaiting human input.",
|
|
8236
|
+
parameters: Type.Object({
|
|
8237
|
+
taskId: Type.String({ description: "Task UUID to update" }),
|
|
8238
|
+
status: Type.Optional(
|
|
8239
|
+
Type.Union([Type.Literal("open"), Type.Literal("done")], {
|
|
8240
|
+
description: "New status (open or done)"
|
|
8241
|
+
})
|
|
8242
|
+
),
|
|
8243
|
+
awaiting: Type.Optional(
|
|
8244
|
+
Type.Union([Type.Literal("bot"), Type.Literal("human")], {
|
|
8245
|
+
description: "Who the task is awaiting. Set to human when work is ready for review."
|
|
8246
|
+
})
|
|
8247
|
+
),
|
|
8248
|
+
reason: Type.Optional(
|
|
8249
|
+
Type.String({
|
|
8250
|
+
description: "Brief explanation (shown in task thread)"
|
|
8251
|
+
})
|
|
8252
|
+
)
|
|
8253
|
+
})
|
|
8254
|
+
},
|
|
8178
8255
|
SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
|
|
8179
8256
|
PROPOSE: SHARED_TOOLS.PROPOSE
|
|
8180
8257
|
};
|
|
@@ -8205,11 +8282,23 @@ In the main thread you propose and coordinate \u2014 the actual work happens in
|
|
|
8205
8282
|
- **Just confirmation or deferred?** \u2192 leave it for now.
|
|
8206
8283
|
- Estimate token cost per task \u2014 consider files to read, reasoning, output.
|
|
8207
8284
|
|
|
8208
|
-
**Scheduled tasks:** When a human asks to start a scheduled task now ("start this", "do this now", "unschedule"), just do it \u2014 call \`deskfree_schedule_task\` with \`scheduledFor: null\` to activate it, then dispatch. Don't explain scheduling mechanics or ask for confirmation. If another task is already in progress and you genuinely can't start it, say so in one sentence \u2014 don't lecture about the scheduling system
|
|
8285
|
+
**Scheduled tasks:** When a human asks to start a scheduled task now ("start this", "do this now", "unschedule"), just do it \u2014 call \`deskfree_schedule_task\` with \`scheduledFor: null\` to activate it, then dispatch. Don't explain scheduling mechanics or ask for confirmation. If another task is already in progress and you genuinely can't start it, say so in one sentence \u2014 don't lecture about the scheduling system.
|
|
8286
|
+
|
|
8287
|
+
**Learnings \u2014 record aggressively:**
|
|
8288
|
+
Use \`deskfree_learning\` to record anything worth remembering. **Err on the side of recording too much** \u2014 the nightly sleep cycle will consolidate and prune. You lose nothing by over-recording, but you lose knowledge by under-recording.
|
|
8289
|
+
|
|
8290
|
+
Record across all five types:
|
|
8291
|
+
- **Corrections**: "Do X, not Y" \u2014 when the human corrects your approach (\`importance: critical\`)
|
|
8292
|
+
- **Preferences**: How they want things done \u2014 tone, format, workflow, style (\`importance: high\`)
|
|
8293
|
+
- **Patterns**: Approaches that consistently work or fail (\`importance: medium\`)
|
|
8294
|
+
- **Domain facts**: Business context, project-specific knowledge, key relationships (\`importance: medium\`)
|
|
8295
|
+
- **Insights**: Non-obvious connections, meta-observations about the work (\`importance: low\`)
|
|
8296
|
+
|
|
8297
|
+
Record immediately when: the human corrects you, expresses a preference, shares context about their business, reacts strongly to something (positive or negative), or you discover something that would help future tasks.`;
|
|
8209
8298
|
DESKFREE_WORKER_DIRECTIVE = `## DeskFree \u2014 Task Thread
|
|
8210
8299
|
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.
|
|
8211
8300
|
|
|
8212
|
-
Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_orient,
|
|
8301
|
+
Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_orient, deskfree_update_task_status, deskfree_send_message, deskfree_propose.
|
|
8213
8302
|
|
|
8214
8303
|
**Context loading:**
|
|
8215
8304
|
- If your first message contains \`<task_context>\`, the task is already loaded. Start working immediately \u2014 do NOT call deskfree_start_task.
|
|
@@ -8223,15 +8312,15 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
8223
8312
|
**Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
|
|
8224
8313
|
|
|
8225
8314
|
1. **Orient** \u2014 Your first message includes operating memory and task-relevant memories. Read any relevant files with \`deskfree_read_file\`. If you need more context mid-task, use \`deskfree_orient\` with a specific query to recall relevant memories.
|
|
8226
|
-
2. **Align** \u2014 Send a brief
|
|
8227
|
-
- **Judgment calls or creative direction?** State your assumptions and approach,
|
|
8228
|
-
- **Straightforward execution?** Proceed immediately
|
|
8315
|
+
2. **Align** \u2014 Send a brief 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.")
|
|
8316
|
+
- **Judgment calls or creative direction?** State your assumptions and approach, then set \`awaiting: 'human'\` via \`deskfree_update_task_status\` and wait for confirmation before proceeding. Getting alignment early prevents costly rework.
|
|
8317
|
+
- **Straightforward execution?** Proceed immediately \u2014 don't wait for a response.
|
|
8229
8318
|
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.
|
|
8230
|
-
4. **Deliver** \u2014
|
|
8319
|
+
4. **Deliver** \u2014 When work is ready for review, send a message and set \`awaiting: 'human'\` via \`deskfree_update_task_status\`. The human will complete the task when satisfied.
|
|
8231
8320
|
|
|
8232
8321
|
**Push back when warranted:**
|
|
8233
8322
|
- 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.
|
|
8234
|
-
- If you hit genuine ambiguity mid-task, send
|
|
8323
|
+
- If you hit genuine ambiguity mid-task, send a message and set \`awaiting: 'human'\`. Don't guess on important decisions \u2014 guessing creates review debt the human has to pay later.
|
|
8235
8324
|
- You're a teammate, not a task executor. Have an opinion when you have the context to form one.
|
|
8236
8325
|
|
|
8237
8326
|
**File rules:**
|
|
@@ -8239,15 +8328,19 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
8239
8328
|
- Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
|
|
8240
8329
|
- 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.
|
|
8241
8330
|
|
|
8242
|
-
**Learnings:**
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
-
|
|
8249
|
-
-
|
|
8250
|
-
-
|
|
8331
|
+
**Learnings \u2014 record aggressively:**
|
|
8332
|
+
Use \`deskfree_learning\` to record anything worth remembering. **Err on the side of recording too much** \u2014 the nightly sleep cycle will consolidate and prune. You lose nothing by over-recording, but you lose knowledge by under-recording.
|
|
8333
|
+
|
|
8334
|
+
Record across all five types:
|
|
8335
|
+
- **Corrections**: "Do X, not Y" \u2014 when the human corrects your approach (\`importance: critical\`)
|
|
8336
|
+
- **Preferences**: How they want things done \u2014 tone, format, workflow, style (\`importance: high\`)
|
|
8337
|
+
- **Patterns**: Approaches that consistently work or fail (\`importance: medium\`)
|
|
8338
|
+
- **Domain facts**: Business context, project-specific knowledge, key relationships (\`importance: medium\`)
|
|
8339
|
+
- **Insights**: Non-obvious connections, meta-observations about the work (\`importance: low\`)
|
|
8340
|
+
|
|
8341
|
+
Record immediately when: the human corrects you, expresses a preference, shares context about their business, reacts strongly to something, or you discover something that would help future tasks.
|
|
8342
|
+
|
|
8343
|
+
Do NOT record: one-time task details, things already in project docs, or obvious/generic knowledge.
|
|
8251
8344
|
|
|
8252
8345
|
**Memory recall:**
|
|
8253
8346
|
- Use \`deskfree_orient\` to recall relevant memories mid-task. Call with a specific query for targeted semantic search.
|
|
@@ -8573,43 +8666,27 @@ var init_orchestrator = __esm({
|
|
|
8573
8666
|
];
|
|
8574
8667
|
}
|
|
8575
8668
|
});
|
|
8576
|
-
function
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8669
|
+
function getStageDomain(stage, domain) {
|
|
8670
|
+
if (domain.startsWith(`${stage}.`)) return domain;
|
|
8671
|
+
return `${stage}.${domain}`;
|
|
8672
|
+
}
|
|
8673
|
+
function deriveApiUrl() {
|
|
8674
|
+
const domain = process.env["DESKFREE_DOMAIN"] ?? "dev.deskfree.ai";
|
|
8675
|
+
const stage = process.env["STAGE"] ?? "dev";
|
|
8676
|
+
return `https://${getStageDomain(stage, domain)}/v1/bot`;
|
|
8582
8677
|
}
|
|
8583
8678
|
function loadConfig() {
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
const decoded = JSON.parse(
|
|
8590
|
-
Buffer.from(launch, "base64").toString("utf8")
|
|
8591
|
-
);
|
|
8592
|
-
botToken = decoded.botToken;
|
|
8593
|
-
apiUrl = decoded.apiUrl;
|
|
8594
|
-
if (!botToken || !apiUrl) {
|
|
8595
|
-
throw new Error(
|
|
8596
|
-
"Missing botToken or apiUrl in DESKFREE_LAUNCH payload"
|
|
8597
|
-
);
|
|
8598
|
-
}
|
|
8599
|
-
} catch (err) {
|
|
8600
|
-
if (err instanceof SyntaxError) {
|
|
8601
|
-
throw new Error("DESKFREE_LAUNCH is not valid base64-encoded JSON");
|
|
8602
|
-
}
|
|
8603
|
-
throw err;
|
|
8604
|
-
}
|
|
8605
|
-
} else {
|
|
8606
|
-
botToken = requireEnv("DESKFREE_BOT_TOKEN");
|
|
8607
|
-
apiUrl = requireEnv("DESKFREE_API_URL");
|
|
8679
|
+
const botId = process.env["BOT"] ?? process.env["DESKFREE_BOT_ID"];
|
|
8680
|
+
if (!botId) {
|
|
8681
|
+
throw new Error(
|
|
8682
|
+
"Missing bot ID. Set the BOT environment variable (e.g. BOT=BFRH3VXHQR7)."
|
|
8683
|
+
);
|
|
8608
8684
|
}
|
|
8609
|
-
const
|
|
8610
|
-
if (
|
|
8611
|
-
throw new Error(`Invalid bot
|
|
8685
|
+
const idError = validateBotId(botId);
|
|
8686
|
+
if (idError !== null) {
|
|
8687
|
+
throw new Error(`Invalid bot ID: ${idError}`);
|
|
8612
8688
|
}
|
|
8689
|
+
const apiUrl = process.env["DESKFREE_API_URL"] ?? deriveApiUrl();
|
|
8613
8690
|
const apiUrlError = validateApiUrl(apiUrl);
|
|
8614
8691
|
if (apiUrlError !== null) {
|
|
8615
8692
|
throw new Error(`Invalid API URL: ${apiUrlError}`);
|
|
@@ -8625,7 +8702,7 @@ function loadConfig() {
|
|
|
8625
8702
|
throw new Error(`Invalid HEALTH_PORT: ${healthPortRaw}`);
|
|
8626
8703
|
}
|
|
8627
8704
|
return {
|
|
8628
|
-
|
|
8705
|
+
botId,
|
|
8629
8706
|
apiUrl,
|
|
8630
8707
|
stateDir: process.env["DESKFREE_STATE_DIR"] ?? DEFAULTS.stateDir,
|
|
8631
8708
|
toolsDir: process.env["DESKFREE_TOOLS_DIR"] ?? DEFAULTS.toolsDir,
|
|
@@ -12513,6 +12590,25 @@ var init_wrapper = __esm({
|
|
|
12513
12590
|
wrapper_default2 = import_websocket2.default;
|
|
12514
12591
|
}
|
|
12515
12592
|
});
|
|
12593
|
+
|
|
12594
|
+
// src/gateway/ws-gateway.ts
|
|
12595
|
+
var ws_gateway_exports = {};
|
|
12596
|
+
__export(ws_gateway_exports, {
|
|
12597
|
+
getRotationToken: () => getRotationToken,
|
|
12598
|
+
setInitialRotationToken: () => setInitialRotationToken,
|
|
12599
|
+
startGateway: () => startGateway
|
|
12600
|
+
});
|
|
12601
|
+
function getRotationToken() {
|
|
12602
|
+
if (!currentRotationToken) {
|
|
12603
|
+
throw new Error(
|
|
12604
|
+
"No rotation token available \u2014 bots.connect not yet called"
|
|
12605
|
+
);
|
|
12606
|
+
}
|
|
12607
|
+
return currentRotationToken;
|
|
12608
|
+
}
|
|
12609
|
+
function setInitialRotationToken(token) {
|
|
12610
|
+
currentRotationToken = token;
|
|
12611
|
+
}
|
|
12516
12612
|
function nextBackoff(state2) {
|
|
12517
12613
|
const delay = Math.min(
|
|
12518
12614
|
BACKOFF_INITIAL_MS * Math.pow(BACKOFF_FACTOR, state2.attempt),
|
|
@@ -12538,6 +12634,55 @@ function sleepWithAbort(ms, signal) {
|
|
|
12538
12634
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
12539
12635
|
});
|
|
12540
12636
|
}
|
|
12637
|
+
async function callBotsConnect(config, rotationToken) {
|
|
12638
|
+
const { botId, publicApiUrl, fingerprint, log, abortSignal } = config;
|
|
12639
|
+
if (!botId || !publicApiUrl || !fingerprint) {
|
|
12640
|
+
throw new Error(
|
|
12641
|
+
"Gateway config missing botId, publicApiUrl, or fingerprint"
|
|
12642
|
+
);
|
|
12643
|
+
}
|
|
12644
|
+
let connectToken;
|
|
12645
|
+
while (true) {
|
|
12646
|
+
if (abortSignal.aborted) throw new Error("Aborted during bots.connect");
|
|
12647
|
+
const body = {
|
|
12648
|
+
botId,
|
|
12649
|
+
fingerprint,
|
|
12650
|
+
...rotationToken ? { rotationToken } : {},
|
|
12651
|
+
...connectToken ? { connectToken } : {}
|
|
12652
|
+
};
|
|
12653
|
+
const response = await fetch(`${publicApiUrl}/bots.connect`, {
|
|
12654
|
+
method: "POST",
|
|
12655
|
+
headers: { "Content-Type": "application/json" },
|
|
12656
|
+
body: JSON.stringify(body)
|
|
12657
|
+
});
|
|
12658
|
+
if (!response.ok) {
|
|
12659
|
+
const text = await response.text().catch(() => "");
|
|
12660
|
+
throw new Error(`bots.connect failed: ${response.status} ${text}`);
|
|
12661
|
+
}
|
|
12662
|
+
const json = await response.json();
|
|
12663
|
+
const data = json.result?.data;
|
|
12664
|
+
if (!data) throw new Error("bots.connect: invalid response structure");
|
|
12665
|
+
if (data.status === "approved") {
|
|
12666
|
+
return {
|
|
12667
|
+
ticket: data.ticket,
|
|
12668
|
+
wsUrl: data.wsUrl,
|
|
12669
|
+
rotationToken: data.rotationToken
|
|
12670
|
+
};
|
|
12671
|
+
}
|
|
12672
|
+
if (data.status === "awaiting_approval") {
|
|
12673
|
+
connectToken = data.connectToken;
|
|
12674
|
+
log.info("Awaiting human approval... polling in 30s");
|
|
12675
|
+
await sleepWithAbort(CONNECT_POLL_INTERVAL_MS, abortSignal);
|
|
12676
|
+
continue;
|
|
12677
|
+
}
|
|
12678
|
+
if (data.status === "rejected") {
|
|
12679
|
+
throw new Error(
|
|
12680
|
+
`Connection rejected: ${data.reason || "unknown reason"}`
|
|
12681
|
+
);
|
|
12682
|
+
}
|
|
12683
|
+
throw new Error(`bots.connect: unexpected status "${data.status}"`);
|
|
12684
|
+
}
|
|
12685
|
+
}
|
|
12541
12686
|
async function startGateway(config) {
|
|
12542
12687
|
const { client, accountId, stateDir, log, abortSignal } = config;
|
|
12543
12688
|
const ctx = { accountId };
|
|
@@ -12554,7 +12699,20 @@ async function startGateway(config) {
|
|
|
12554
12699
|
});
|
|
12555
12700
|
while (!abortSignal.aborted) {
|
|
12556
12701
|
try {
|
|
12557
|
-
|
|
12702
|
+
let ticket;
|
|
12703
|
+
let wsUrl;
|
|
12704
|
+
if (config.initialTicket && totalReconnects === 0) {
|
|
12705
|
+
ticket = config.initialTicket.ticket;
|
|
12706
|
+
wsUrl = config.initialTicket.wsUrl;
|
|
12707
|
+
} else {
|
|
12708
|
+
const result = await callBotsConnect(
|
|
12709
|
+
config,
|
|
12710
|
+
currentRotationToken ?? void 0
|
|
12711
|
+
);
|
|
12712
|
+
ticket = result.ticket;
|
|
12713
|
+
wsUrl = result.wsUrl;
|
|
12714
|
+
currentRotationToken = result.rotationToken;
|
|
12715
|
+
}
|
|
12558
12716
|
resetBackoff(backoff);
|
|
12559
12717
|
if (totalReconnects > 0) {
|
|
12560
12718
|
log.info(
|
|
@@ -12575,17 +12733,19 @@ async function startGateway(config) {
|
|
|
12575
12733
|
log,
|
|
12576
12734
|
abortSignal,
|
|
12577
12735
|
onMessage: config.onMessage,
|
|
12578
|
-
getWorkerStatus: config.getWorkerStatus
|
|
12736
|
+
getWorkerStatus: config.getWorkerStatus,
|
|
12737
|
+
onConsolidate: config.onConsolidate,
|
|
12738
|
+
isV2: true
|
|
12579
12739
|
});
|
|
12580
12740
|
totalReconnects++;
|
|
12581
12741
|
} catch (err) {
|
|
12582
12742
|
totalReconnects++;
|
|
12583
12743
|
const message = err instanceof Error ? err.message : String(err);
|
|
12584
|
-
if (message.includes("
|
|
12744
|
+
if (message.includes("bots.connect failed") || message.includes("server error") || message.includes("Connection rejected")) {
|
|
12585
12745
|
log.warn(
|
|
12586
|
-
`
|
|
12746
|
+
`Connection setup failed (attempt #${totalReconnects}): ${message}. Falling back to polling.`
|
|
12587
12747
|
);
|
|
12588
|
-
reportError("error", `
|
|
12748
|
+
reportError("error", `Connection setup failed: ${message}`, {
|
|
12589
12749
|
component: "gateway",
|
|
12590
12750
|
event: "ticket_fetch_failed",
|
|
12591
12751
|
attempt: totalReconnects
|
|
@@ -12630,6 +12790,7 @@ async function runWebSocketConnection(opts) {
|
|
|
12630
12790
|
let connectionTimer;
|
|
12631
12791
|
let pongTimer;
|
|
12632
12792
|
let notifyDebounceTimer;
|
|
12793
|
+
let proactiveReconnectTimer;
|
|
12633
12794
|
let isConnected = false;
|
|
12634
12795
|
const cleanup = () => {
|
|
12635
12796
|
if (pingInterval !== void 0) {
|
|
@@ -12648,6 +12809,10 @@ async function runWebSocketConnection(opts) {
|
|
|
12648
12809
|
clearTimeout(notifyDebounceTimer);
|
|
12649
12810
|
notifyDebounceTimer = void 0;
|
|
12650
12811
|
}
|
|
12812
|
+
if (proactiveReconnectTimer !== void 0) {
|
|
12813
|
+
clearTimeout(proactiveReconnectTimer);
|
|
12814
|
+
proactiveReconnectTimer = void 0;
|
|
12815
|
+
}
|
|
12651
12816
|
};
|
|
12652
12817
|
connectionTimer = setTimeout(() => {
|
|
12653
12818
|
if (!isConnected) {
|
|
@@ -12676,7 +12841,11 @@ async function runWebSocketConnection(opts) {
|
|
|
12676
12841
|
pingInterval = setInterval(() => {
|
|
12677
12842
|
if (ws.readyState === wrapper_default2.OPEN) {
|
|
12678
12843
|
try {
|
|
12679
|
-
|
|
12844
|
+
const pingPayload = { action: "ping" };
|
|
12845
|
+
if (currentRotationToken) {
|
|
12846
|
+
pingPayload.token = currentRotationToken;
|
|
12847
|
+
}
|
|
12848
|
+
ws.send(JSON.stringify(pingPayload));
|
|
12680
12849
|
pongTimer = setTimeout(() => {
|
|
12681
12850
|
log.warn("Pong timeout \u2014 closing WebSocket");
|
|
12682
12851
|
try {
|
|
@@ -12690,6 +12859,13 @@ async function runWebSocketConnection(opts) {
|
|
|
12690
12859
|
}
|
|
12691
12860
|
}
|
|
12692
12861
|
}, PING_INTERVAL_MS);
|
|
12862
|
+
proactiveReconnectTimer = setTimeout(() => {
|
|
12863
|
+
log.info("Proactive reconnect at 1h50m \u2014 closing for reconnect");
|
|
12864
|
+
try {
|
|
12865
|
+
ws.close(1e3, "proactive_reconnect");
|
|
12866
|
+
} catch {
|
|
12867
|
+
}
|
|
12868
|
+
}, PROACTIVE_RECONNECT_MS);
|
|
12693
12869
|
if (opts.getWorkerStatus) {
|
|
12694
12870
|
try {
|
|
12695
12871
|
const status2 = opts.getWorkerStatus();
|
|
@@ -12762,7 +12938,21 @@ async function runWebSocketConnection(opts) {
|
|
|
12762
12938
|
clearTimeout(pongTimer);
|
|
12763
12939
|
pongTimer = void 0;
|
|
12764
12940
|
}
|
|
12765
|
-
|
|
12941
|
+
const pongMsg = msg;
|
|
12942
|
+
if (pongMsg.rotationToken) {
|
|
12943
|
+
currentRotationToken = pongMsg.rotationToken;
|
|
12944
|
+
log.debug("Received pong \u2014 token rotated");
|
|
12945
|
+
} else if (pongMsg.error === "rotation_invalid") {
|
|
12946
|
+
log.warn(
|
|
12947
|
+
"Rotation token invalid \u2014 closing for reconnect via bots.connect"
|
|
12948
|
+
);
|
|
12949
|
+
try {
|
|
12950
|
+
ws.close(1e3, "rotation_invalid");
|
|
12951
|
+
} catch {
|
|
12952
|
+
}
|
|
12953
|
+
} else {
|
|
12954
|
+
log.debug("Received pong \u2014 connection healthy");
|
|
12955
|
+
}
|
|
12766
12956
|
} else if (msg.action === "heartbeatRequest") {
|
|
12767
12957
|
if (opts.getWorkerStatus && ws.readyState === wrapper_default2.OPEN) {
|
|
12768
12958
|
try {
|
|
@@ -12828,6 +13018,34 @@ async function runWebSocketConnection(opts) {
|
|
|
12828
13018
|
})
|
|
12829
13019
|
);
|
|
12830
13020
|
}
|
|
13021
|
+
} else if (msg.action === "consolidate") {
|
|
13022
|
+
if (opts.onConsolidate && ws.readyState === wrapper_default2.OPEN) {
|
|
13023
|
+
opts.onConsolidate().then(
|
|
13024
|
+
(consolidateStatus) => {
|
|
13025
|
+
if (ws.readyState === wrapper_default2.OPEN) {
|
|
13026
|
+
ws.send(
|
|
13027
|
+
JSON.stringify({
|
|
13028
|
+
action: "consolidateResponse",
|
|
13029
|
+
status: consolidateStatus
|
|
13030
|
+
})
|
|
13031
|
+
);
|
|
13032
|
+
}
|
|
13033
|
+
},
|
|
13034
|
+
(consolidateErr) => {
|
|
13035
|
+
const errMsg = consolidateErr instanceof Error ? consolidateErr.message : String(consolidateErr);
|
|
13036
|
+
log.warn(`On-demand consolidation failed: ${errMsg}`);
|
|
13037
|
+
if (ws.readyState === wrapper_default2.OPEN) {
|
|
13038
|
+
ws.send(
|
|
13039
|
+
JSON.stringify({
|
|
13040
|
+
action: "consolidateResponse",
|
|
13041
|
+
status: "error",
|
|
13042
|
+
error: errMsg
|
|
13043
|
+
})
|
|
13044
|
+
);
|
|
13045
|
+
}
|
|
13046
|
+
}
|
|
13047
|
+
);
|
|
13048
|
+
}
|
|
12831
13049
|
}
|
|
12832
13050
|
} catch (err) {
|
|
12833
13051
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -12942,7 +13160,7 @@ async function runPollingFallback(opts) {
|
|
|
12942
13160
|
}
|
|
12943
13161
|
return cursor;
|
|
12944
13162
|
}
|
|
12945
|
-
var PING_INTERVAL_MS, POLL_FALLBACK_INTERVAL_MS, WS_CONNECTION_TIMEOUT_MS, WS_PONG_TIMEOUT_MS, NOTIFY_DEBOUNCE_MS, HEALTH_LOG_INTERVAL_MS, MAX_POLLING_ITERATIONS, MAX_CONSECUTIVE_POLL_FAILURES, BACKOFF_INITIAL_MS, BACKOFF_MAX_MS, BACKOFF_FACTOR;
|
|
13163
|
+
var PING_INTERVAL_MS, POLL_FALLBACK_INTERVAL_MS, WS_CONNECTION_TIMEOUT_MS, WS_PONG_TIMEOUT_MS, NOTIFY_DEBOUNCE_MS, HEALTH_LOG_INTERVAL_MS, MAX_POLLING_ITERATIONS, MAX_CONSECUTIVE_POLL_FAILURES, CONNECT_POLL_INTERVAL_MS, PROACTIVE_RECONNECT_MS, BACKOFF_INITIAL_MS, BACKOFF_MAX_MS, BACKOFF_FACTOR, currentRotationToken;
|
|
12946
13164
|
var init_ws_gateway = __esm({
|
|
12947
13165
|
"src/gateway/ws-gateway.ts"() {
|
|
12948
13166
|
init_health_state();
|
|
@@ -12958,9 +13176,12 @@ var init_ws_gateway = __esm({
|
|
|
12958
13176
|
HEALTH_LOG_INTERVAL_MS = 30 * 60 * 1e3;
|
|
12959
13177
|
MAX_POLLING_ITERATIONS = 10;
|
|
12960
13178
|
MAX_CONSECUTIVE_POLL_FAILURES = 5;
|
|
13179
|
+
CONNECT_POLL_INTERVAL_MS = 3e4;
|
|
13180
|
+
PROACTIVE_RECONNECT_MS = 110 * 60 * 1e3;
|
|
12961
13181
|
BACKOFF_INITIAL_MS = 2e3;
|
|
12962
13182
|
BACKOFF_MAX_MS = 3e4;
|
|
12963
13183
|
BACKOFF_FACTOR = 1.8;
|
|
13184
|
+
currentRotationToken = null;
|
|
12964
13185
|
}
|
|
12965
13186
|
});
|
|
12966
13187
|
function jsonSchemaPropertyToZod(prop) {
|
|
@@ -13174,13 +13395,13 @@ function validateDownloadUrl(url) {
|
|
|
13174
13395
|
if (parsed.protocol !== "https:") {
|
|
13175
13396
|
throw new Error("Only HTTPS URLs are allowed");
|
|
13176
13397
|
}
|
|
13177
|
-
const
|
|
13178
|
-
const bareHostname =
|
|
13398
|
+
const hostname2 = parsed.hostname.toLowerCase();
|
|
13399
|
+
const bareHostname = hostname2.replace(/^\[|\]$/g, "");
|
|
13179
13400
|
if (bareHostname === "localhost" || bareHostname === "127.0.0.1" || bareHostname === "::1") {
|
|
13180
13401
|
throw new Error("Local URLs are not allowed");
|
|
13181
13402
|
}
|
|
13182
13403
|
for (const pattern of PRIVATE_IPV4_PATTERNS) {
|
|
13183
|
-
if (pattern.test(
|
|
13404
|
+
if (pattern.test(hostname2)) {
|
|
13184
13405
|
throw new Error("Private IP addresses are not allowed");
|
|
13185
13406
|
}
|
|
13186
13407
|
}
|
|
@@ -13750,8 +13971,9 @@ async function routeMessage(message, client, deps, sessionStore, config) {
|
|
|
13750
13971
|
`Auto-reopening task ${message.taskId} (attachment on ${task.status} task)`
|
|
13751
13972
|
);
|
|
13752
13973
|
try {
|
|
13753
|
-
await client.
|
|
13974
|
+
await client.updateTaskStatus({
|
|
13754
13975
|
taskId: message.taskId,
|
|
13976
|
+
status: "open",
|
|
13755
13977
|
reason: "Human sent an attachment \u2014 reopening for further work"
|
|
13756
13978
|
});
|
|
13757
13979
|
routingTarget = "orchestrator";
|
|
@@ -14559,6 +14781,59 @@ ${userMessage}
|
|
|
14559
14781
|
}
|
|
14560
14782
|
});
|
|
14561
14783
|
|
|
14784
|
+
// src/auth/fingerprint.ts
|
|
14785
|
+
var fingerprint_exports = {};
|
|
14786
|
+
__export(fingerprint_exports, {
|
|
14787
|
+
collectFingerprint: () => collectFingerprint
|
|
14788
|
+
});
|
|
14789
|
+
function collectFingerprint(stateDir, runtimeVersion) {
|
|
14790
|
+
return {
|
|
14791
|
+
hostId: getOrCreateHostId(stateDir),
|
|
14792
|
+
machineId: getMachineId(),
|
|
14793
|
+
platform: platform(),
|
|
14794
|
+
arch: arch(),
|
|
14795
|
+
hostname: hostname(),
|
|
14796
|
+
cpuModel: cpus()[0]?.model ?? "unknown",
|
|
14797
|
+
totalMemory: totalmem(),
|
|
14798
|
+
nodeVersion: process.version,
|
|
14799
|
+
runtimeVersion
|
|
14800
|
+
};
|
|
14801
|
+
}
|
|
14802
|
+
function getOrCreateHostId(stateDir) {
|
|
14803
|
+
const hostIdPath = join(stateDir, "host-id");
|
|
14804
|
+
if (existsSync(hostIdPath)) {
|
|
14805
|
+
const existing = readFileSync(hostIdPath, "utf8").trim();
|
|
14806
|
+
if (existing) return existing;
|
|
14807
|
+
}
|
|
14808
|
+
const hostId = randomUUID();
|
|
14809
|
+
mkdirSync(dirname(hostIdPath), { recursive: true });
|
|
14810
|
+
writeFileSync(hostIdPath, hostId, "utf8");
|
|
14811
|
+
return hostId;
|
|
14812
|
+
}
|
|
14813
|
+
function getMachineId() {
|
|
14814
|
+
try {
|
|
14815
|
+
if (platform() === "linux") {
|
|
14816
|
+
if (existsSync("/etc/machine-id")) {
|
|
14817
|
+
return readFileSync("/etc/machine-id", "utf8").trim();
|
|
14818
|
+
}
|
|
14819
|
+
}
|
|
14820
|
+
if (platform() === "darwin") {
|
|
14821
|
+
const output = execSync(
|
|
14822
|
+
"ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID",
|
|
14823
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
14824
|
+
);
|
|
14825
|
+
const match = output.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
14826
|
+
if (match?.[1]) return match[1];
|
|
14827
|
+
}
|
|
14828
|
+
} catch {
|
|
14829
|
+
}
|
|
14830
|
+
return "unknown";
|
|
14831
|
+
}
|
|
14832
|
+
var init_fingerprint = __esm({
|
|
14833
|
+
"src/auth/fingerprint.ts"() {
|
|
14834
|
+
}
|
|
14835
|
+
});
|
|
14836
|
+
|
|
14562
14837
|
// src/service/entrypoint.ts
|
|
14563
14838
|
var entrypoint_exports = {};
|
|
14564
14839
|
__export(entrypoint_exports, {
|
|
@@ -14633,12 +14908,75 @@ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, cla
|
|
|
14633
14908
|
setTimeout(() => void tick(), intervalMs);
|
|
14634
14909
|
log.info(`Heartbeat scheduled every ${Math.round(intervalMs / 1e3)}s`);
|
|
14635
14910
|
}
|
|
14911
|
+
async function initialConnect(opts) {
|
|
14912
|
+
const { botId, publicApiUrl, fingerprint, log, abortSignal } = opts;
|
|
14913
|
+
let connectToken;
|
|
14914
|
+
while (true) {
|
|
14915
|
+
if (abortSignal.aborted) throw new Error("Aborted during bots.connect");
|
|
14916
|
+
const body = {
|
|
14917
|
+
botId,
|
|
14918
|
+
fingerprint,
|
|
14919
|
+
...connectToken ? { connectToken } : {}
|
|
14920
|
+
};
|
|
14921
|
+
const response = await fetch(`${publicApiUrl}/bots.connect`, {
|
|
14922
|
+
method: "POST",
|
|
14923
|
+
headers: { "Content-Type": "application/json" },
|
|
14924
|
+
body: JSON.stringify(body)
|
|
14925
|
+
});
|
|
14926
|
+
if (!response.ok) {
|
|
14927
|
+
const text = await response.text().catch(() => "");
|
|
14928
|
+
throw new Error(`bots.connect failed: ${response.status} ${text}`);
|
|
14929
|
+
}
|
|
14930
|
+
const json = await response.json();
|
|
14931
|
+
const data = json.result?.data;
|
|
14932
|
+
if (!data) throw new Error("bots.connect: invalid response structure");
|
|
14933
|
+
if (data.status === "approved") {
|
|
14934
|
+
return {
|
|
14935
|
+
ticket: data.ticket,
|
|
14936
|
+
wsUrl: data.wsUrl,
|
|
14937
|
+
rotationToken: data.rotationToken
|
|
14938
|
+
};
|
|
14939
|
+
}
|
|
14940
|
+
if (data.status === "awaiting_approval") {
|
|
14941
|
+
connectToken = data.connectToken;
|
|
14942
|
+
log.info("Awaiting human approval... polling in 30s");
|
|
14943
|
+
await new Promise(
|
|
14944
|
+
(resolve) => setTimeout(resolve, CONNECT_POLL_INTERVAL_MS2)
|
|
14945
|
+
);
|
|
14946
|
+
continue;
|
|
14947
|
+
}
|
|
14948
|
+
if (data.status === "rejected") {
|
|
14949
|
+
throw new Error(
|
|
14950
|
+
`Connection rejected: ${data.reason || "unknown reason"}`
|
|
14951
|
+
);
|
|
14952
|
+
}
|
|
14953
|
+
throw new Error(`bots.connect: unexpected status "${data.status}"`);
|
|
14954
|
+
}
|
|
14955
|
+
}
|
|
14636
14956
|
async function startAgent(opts) {
|
|
14637
14957
|
const localConfig = loadConfig();
|
|
14638
14958
|
const log = opts?.log ?? createLogger("agent", localConfig.logLevel);
|
|
14639
14959
|
const abortController = new AbortController();
|
|
14640
14960
|
log.info("DeskFree Agent Runtime starting...");
|
|
14641
|
-
const
|
|
14961
|
+
const { getRotationToken: getRotationToken2, setInitialRotationToken: setInitialRotationToken2 } = await Promise.resolve().then(() => (init_ws_gateway(), ws_gateway_exports));
|
|
14962
|
+
const { collectFingerprint: collectFingerprint2 } = await Promise.resolve().then(() => (init_fingerprint(), fingerprint_exports));
|
|
14963
|
+
const runtimeVersion = process.env["npm_package_version"] ?? "unknown";
|
|
14964
|
+
const publicApiUrl = localConfig.apiUrl.replace("/v1/bot", "/v1/public");
|
|
14965
|
+
const fingerprint = collectFingerprint2(localConfig.stateDir, runtimeVersion);
|
|
14966
|
+
log.info("Connecting to DeskFree...", { apiUrl: publicApiUrl });
|
|
14967
|
+
const connectResult = await initialConnect({
|
|
14968
|
+
botId: localConfig.botId,
|
|
14969
|
+
publicApiUrl,
|
|
14970
|
+
fingerprint,
|
|
14971
|
+
log,
|
|
14972
|
+
abortSignal: abortController.signal
|
|
14973
|
+
});
|
|
14974
|
+
setInitialRotationToken2(connectResult.rotationToken);
|
|
14975
|
+
log.info("Connected \u2014 got rotation token and WS ticket.");
|
|
14976
|
+
const client = new DeskFreeClient({
|
|
14977
|
+
apiUrl: localConfig.apiUrl,
|
|
14978
|
+
getToken: () => getRotationToken2()
|
|
14979
|
+
});
|
|
14642
14980
|
const errorReporter = initErrorReporter(client, log);
|
|
14643
14981
|
initializeHealth("unknown");
|
|
14644
14982
|
log.info("Bootstrapping from API...", { apiUrl: localConfig.apiUrl });
|
|
@@ -14658,7 +14996,6 @@ async function startAgent(opts) {
|
|
|
14658
14996
|
throw new Error(`Failed to bootstrap config from API: ${msg}`);
|
|
14659
14997
|
}
|
|
14660
14998
|
const isDocker2 = process.env["DOCKER"] === "1" || (await import('fs')).existsSync("/.dockerenv");
|
|
14661
|
-
const runtimeVersion = process.env["npm_package_version"] ?? "unknown";
|
|
14662
14999
|
const agentContext = {
|
|
14663
15000
|
botName: config.botName,
|
|
14664
15001
|
deploymentType: config.deploymentType,
|
|
@@ -14738,6 +15075,13 @@ async function startAgent(opts) {
|
|
|
14738
15075
|
stateDir: config.stateDir,
|
|
14739
15076
|
log,
|
|
14740
15077
|
abortSignal: abortController.signal,
|
|
15078
|
+
botId: localConfig.botId,
|
|
15079
|
+
publicApiUrl,
|
|
15080
|
+
fingerprint,
|
|
15081
|
+
initialTicket: {
|
|
15082
|
+
ticket: connectResult.ticket,
|
|
15083
|
+
wsUrl: connectResult.wsUrl
|
|
15084
|
+
},
|
|
14741
15085
|
getWorkerStatus: () => ({
|
|
14742
15086
|
activeWorkers: workerManager.activeCount,
|
|
14743
15087
|
queuedTasks: workerManager.queuedCount,
|
|
@@ -14762,7 +15106,8 @@ async function startAgent(opts) {
|
|
|
14762
15106
|
log
|
|
14763
15107
|
}
|
|
14764
15108
|
);
|
|
14765
|
-
}
|
|
15109
|
+
},
|
|
15110
|
+
onConsolidate: () => runConsolidation()
|
|
14766
15111
|
});
|
|
14767
15112
|
scheduleHeartbeat(
|
|
14768
15113
|
createOrchServer,
|
|
@@ -14773,64 +15118,107 @@ async function startAgent(opts) {
|
|
|
14773
15118
|
config.claudeCodePath,
|
|
14774
15119
|
agentContext
|
|
14775
15120
|
);
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
15121
|
+
let isConsolidating = false;
|
|
15122
|
+
async function runConsolidation() {
|
|
15123
|
+
if (isConsolidating) {
|
|
15124
|
+
log.info("Consolidation already in progress, skipping");
|
|
15125
|
+
return "already_running";
|
|
15126
|
+
}
|
|
15127
|
+
isConsolidating = true;
|
|
15128
|
+
try {
|
|
15129
|
+
let orientResult;
|
|
15130
|
+
try {
|
|
15131
|
+
orientResult = await client.orient({ consolidation: true });
|
|
15132
|
+
} catch (err) {
|
|
15133
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
15134
|
+
log.warn(`Sleep cycle: orient() failed: ${msg}`);
|
|
15135
|
+
return "error";
|
|
15136
|
+
}
|
|
15137
|
+
if (orientResult.entries.length === 0) {
|
|
15138
|
+
log.info("Sleep cycle: no unconsolidated entries to process, skipping");
|
|
15139
|
+
return "noop";
|
|
15140
|
+
}
|
|
15141
|
+
const unconsolidatedSection = orientResult.entries.map(
|
|
15142
|
+
(e) => `- [${e.entryId}] ${e.importance ? `(${e.importance}) ` : ""}${e.content}`
|
|
15143
|
+
).join("\n");
|
|
15144
|
+
const relatedSection = orientResult.recentEntries && orientResult.recentEntries.length > 0 ? orientResult.recentEntries.map(
|
|
15145
|
+
(e) => `- [${e.entryId}] [${e.type ?? "unclassified"}] s:${e.strength} \u2014 ${e.content}`
|
|
15146
|
+
).join("\n") : "(none)";
|
|
15147
|
+
const dedupSection = orientResult.dedupCandidates && orientResult.dedupCandidates.length > 0 ? orientResult.dedupCandidates.map(
|
|
15148
|
+
(d) => `- ${d.entryId1} \u2194 ${d.entryId2} (similarity: ${d.similarity.toFixed(3)})`
|
|
15149
|
+
).join("\n") : "(none)";
|
|
15150
|
+
const prompt = [
|
|
15151
|
+
"<current_operating_memory>",
|
|
15152
|
+
orientResult.operatingMemory || "(empty \u2014 first consolidation)",
|
|
15153
|
+
"</current_operating_memory>",
|
|
15154
|
+
"",
|
|
15155
|
+
"<unconsolidated_entries>",
|
|
15156
|
+
unconsolidatedSection,
|
|
15157
|
+
"</unconsolidated_entries>",
|
|
15158
|
+
"",
|
|
15159
|
+
"<related_active_entries>",
|
|
15160
|
+
relatedSection,
|
|
15161
|
+
"</related_active_entries>",
|
|
15162
|
+
"",
|
|
15163
|
+
"<dedup_candidates>",
|
|
15164
|
+
dedupSection,
|
|
15165
|
+
"</dedup_candidates>",
|
|
15166
|
+
"",
|
|
15167
|
+
"Run your nightly consolidation cycle now."
|
|
15168
|
+
].join("\n");
|
|
15169
|
+
log.info(
|
|
15170
|
+
`Sleep cycle: invoking sleep agent (${orientResult.entries.length} unconsolidated entries)...`
|
|
15171
|
+
);
|
|
15172
|
+
const workerServer = createWorkServer();
|
|
15173
|
+
const agentStream = runOneShotWorker({
|
|
15174
|
+
prompt,
|
|
15175
|
+
systemPrompt: buildSleepDirective(agentContext),
|
|
15176
|
+
workerServer,
|
|
15177
|
+
model: config.model
|
|
15178
|
+
});
|
|
15179
|
+
let fullText = "";
|
|
15180
|
+
for await (const msg of agentStream) {
|
|
15181
|
+
if (msg.type === "assistant" && Array.isArray(msg.message?.content)) {
|
|
15182
|
+
for (const block of msg.message.content) {
|
|
15183
|
+
if (block.type === "text") {
|
|
15184
|
+
fullText += block.text;
|
|
15185
|
+
}
|
|
15186
|
+
}
|
|
14787
15187
|
}
|
|
14788
|
-
|
|
15188
|
+
}
|
|
15189
|
+
const tagMatch = fullText.match(
|
|
15190
|
+
/<consolidation_result>([\s\S]*?)<\/consolidation_result>/
|
|
15191
|
+
);
|
|
15192
|
+
if (tagMatch) {
|
|
15193
|
+
try {
|
|
15194
|
+
const parsed = JSON.parse(tagMatch[1]);
|
|
15195
|
+
const consolidateResult = await client.consolidateMemory({
|
|
15196
|
+
newEntries: parsed.newEntries ?? [],
|
|
15197
|
+
modifications: parsed.modifications ?? [],
|
|
15198
|
+
operatingMemory: parsed.operatingMemory ?? ""
|
|
15199
|
+
});
|
|
14789
15200
|
log.info(
|
|
14790
|
-
|
|
15201
|
+
`Sleep cycle: consolidation applied (${consolidateResult.opsApplied} ops, ${consolidateResult.entriesArchived} archived)`
|
|
14791
15202
|
);
|
|
14792
|
-
|
|
15203
|
+
} catch (parseErr) {
|
|
15204
|
+
const parseMsg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
15205
|
+
log.warn(`Sleep cycle: failed to submit consolidation: ${parseMsg}`);
|
|
14793
15206
|
}
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
const relatedSection = orientResult.recentEntries && orientResult.recentEntries.length > 0 ? orientResult.recentEntries.map(
|
|
14798
|
-
(e) => `- [${e.entryId}] [${e.type ?? "unclassified"}] s:${e.strength} \u2014 ${e.content}`
|
|
14799
|
-
).join("\n") : "(none)";
|
|
14800
|
-
const dedupSection = orientResult.dedupCandidates && orientResult.dedupCandidates.length > 0 ? orientResult.dedupCandidates.map(
|
|
14801
|
-
(d) => `- ${d.entryId1} \u2194 ${d.entryId2} (similarity: ${d.similarity.toFixed(3)})`
|
|
14802
|
-
).join("\n") : "(none)";
|
|
14803
|
-
const prompt = [
|
|
14804
|
-
"<current_operating_memory>",
|
|
14805
|
-
orientResult.operatingMemory || "(empty \u2014 first consolidation)",
|
|
14806
|
-
"</current_operating_memory>",
|
|
14807
|
-
"",
|
|
14808
|
-
"<unconsolidated_entries>",
|
|
14809
|
-
unconsolidatedSection,
|
|
14810
|
-
"</unconsolidated_entries>",
|
|
14811
|
-
"",
|
|
14812
|
-
"<related_active_entries>",
|
|
14813
|
-
relatedSection,
|
|
14814
|
-
"</related_active_entries>",
|
|
14815
|
-
"",
|
|
14816
|
-
"<dedup_candidates>",
|
|
14817
|
-
dedupSection,
|
|
14818
|
-
"</dedup_candidates>",
|
|
14819
|
-
"",
|
|
14820
|
-
"Run your nightly consolidation cycle now."
|
|
14821
|
-
].join("\n");
|
|
14822
|
-
log.info(
|
|
14823
|
-
`Sleep cycle: invoking sleep agent (${orientResult.entries.length} unconsolidated entries)...`
|
|
15207
|
+
} else {
|
|
15208
|
+
log.warn(
|
|
15209
|
+
"Sleep cycle: agent did not produce <consolidation_result> tags"
|
|
14824
15210
|
);
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
|
|
14828
|
-
|
|
14829
|
-
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
|
|
15211
|
+
}
|
|
15212
|
+
return "success";
|
|
15213
|
+
} finally {
|
|
15214
|
+
isConsolidating = false;
|
|
15215
|
+
}
|
|
15216
|
+
}
|
|
15217
|
+
if (config.sleepHour !== null && config.timezone) {
|
|
15218
|
+
scheduleDailyCycle(
|
|
15219
|
+
"Sleep",
|
|
15220
|
+
async () => {
|
|
15221
|
+
await runConsolidation();
|
|
14834
15222
|
},
|
|
14835
15223
|
config.sleepHour,
|
|
14836
15224
|
config.timezone,
|
|
@@ -14894,6 +15282,7 @@ async function startAgent(opts) {
|
|
|
14894
15282
|
log.info("Shutdown complete.");
|
|
14895
15283
|
};
|
|
14896
15284
|
}
|
|
15285
|
+
var CONNECT_POLL_INTERVAL_MS2;
|
|
14897
15286
|
var init_entrypoint = __esm({
|
|
14898
15287
|
"src/service/entrypoint.ts"() {
|
|
14899
15288
|
init_orchestrator();
|
|
@@ -14910,6 +15299,7 @@ var init_entrypoint = __esm({
|
|
|
14910
15299
|
init_sessions();
|
|
14911
15300
|
init_worker_manager();
|
|
14912
15301
|
init_dist();
|
|
15302
|
+
CONNECT_POLL_INTERVAL_MS2 = 3e4;
|
|
14913
15303
|
}
|
|
14914
15304
|
});
|
|
14915
15305
|
|
|
@@ -14918,29 +15308,42 @@ init_paths();
|
|
|
14918
15308
|
var [name, cleanArgs] = parseName(process.argv.slice(2));
|
|
14919
15309
|
var command = cleanArgs[0];
|
|
14920
15310
|
if (command === "install") {
|
|
14921
|
-
|
|
14922
|
-
|
|
15311
|
+
const stageIdx = cleanArgs.indexOf("--stage");
|
|
15312
|
+
let stage;
|
|
15313
|
+
let installArgs = cleanArgs.slice(1);
|
|
15314
|
+
if (stageIdx !== -1) {
|
|
15315
|
+
stage = cleanArgs[stageIdx + 1];
|
|
15316
|
+
if (!stage || stage.startsWith("-")) {
|
|
15317
|
+
console.error("Error: --stage requires a value (e.g. --stage charlie)");
|
|
15318
|
+
process.exit(1);
|
|
15319
|
+
}
|
|
15320
|
+
installArgs = installArgs.filter(
|
|
15321
|
+
(_, i) => i !== stageIdx - 1 && i !== stageIdx
|
|
15322
|
+
);
|
|
15323
|
+
}
|
|
15324
|
+
let botId = installArgs[0];
|
|
15325
|
+
if (!botId) {
|
|
14923
15326
|
const { createInterface } = await import('readline');
|
|
14924
15327
|
const rl = createInterface({
|
|
14925
15328
|
input: process.stdin,
|
|
14926
15329
|
output: process.stdout
|
|
14927
15330
|
});
|
|
14928
|
-
|
|
15331
|
+
botId = await new Promise((resolve) => {
|
|
14929
15332
|
rl.question(
|
|
14930
|
-
"Paste your bot
|
|
15333
|
+
"Paste your bot ID (from the DeskFree dashboard):\n> ",
|
|
14931
15334
|
(answer) => {
|
|
14932
15335
|
rl.close();
|
|
14933
15336
|
resolve(answer.trim());
|
|
14934
15337
|
}
|
|
14935
15338
|
);
|
|
14936
15339
|
});
|
|
14937
|
-
if (!
|
|
14938
|
-
console.error("No
|
|
15340
|
+
if (!botId) {
|
|
15341
|
+
console.error("No bot ID provided.");
|
|
14939
15342
|
process.exit(1);
|
|
14940
15343
|
}
|
|
14941
15344
|
}
|
|
14942
15345
|
const { install: install2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
14943
|
-
install2(
|
|
15346
|
+
install2(botId, name, stage);
|
|
14944
15347
|
} else if (command === "uninstall") {
|
|
14945
15348
|
const { uninstall: uninstall2 } = await Promise.resolve().then(() => (init_uninstall(), uninstall_exports));
|
|
14946
15349
|
uninstall2(name);
|
|
@@ -14966,14 +15369,12 @@ if (command === "install") {
|
|
|
14966
15369
|
process.exit(1);
|
|
14967
15370
|
}, 5e3).unref();
|
|
14968
15371
|
};
|
|
14969
|
-
|
|
14970
|
-
if (
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
|
|
14975
|
-
if (token && !process.env["DESKFREE_LAUNCH"]) {
|
|
14976
|
-
process.env["DESKFREE_LAUNCH"] = token;
|
|
15372
|
+
const startArgs = command === "start" ? cleanArgs.slice(1) : cleanArgs;
|
|
15373
|
+
if (startArgs.length >= 2) {
|
|
15374
|
+
process.env["STAGE"] = startArgs[0];
|
|
15375
|
+
process.env["BOT"] = startArgs[1];
|
|
15376
|
+
} else if (startArgs.length === 1) {
|
|
15377
|
+
process.env["BOT"] = startArgs[0];
|
|
14977
15378
|
}
|
|
14978
15379
|
const { startAgent: startAgent2 } = await Promise.resolve().then(() => (init_entrypoint(), entrypoint_exports));
|
|
14979
15380
|
const { createLogger: createLogger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
|