@questionbase/deskfree 0.6.1 → 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 +467 -179
- package/dist/bin.js.map +1 -1
- package/dist/cli/install.d.ts +1 -1
- package/dist/cli/install.js +11 -19
- package/dist/cli/install.js.map +1 -1
- package/dist/index.d.ts +39 -18
- package/dist/index.js +10293 -9930
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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';
|
|
@@ -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
|
|
@@ -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]
|
|
@@ -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);
|
|
@@ -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);
|
|
@@ -2867,18 +2877,7 @@ function createWorkerTools(client, options) {
|
|
|
2867
2877
|
try {
|
|
2868
2878
|
const content = validateStringParam(params, "content", true);
|
|
2869
2879
|
const taskId = validateStringParam(params, "taskId", false);
|
|
2870
|
-
const type = validateEnumParam(params, "type", ["notify", "ask"], true);
|
|
2871
2880
|
await client.sendMessage({ content, taskId });
|
|
2872
|
-
if (type === "ask") {
|
|
2873
|
-
return {
|
|
2874
|
-
content: [
|
|
2875
|
-
{
|
|
2876
|
-
type: "text",
|
|
2877
|
-
text: "Ask sent \u2014 task is now awaiting human response. Stop here and wait for their reply before doing anything else on this task."
|
|
2878
|
-
}
|
|
2879
|
-
]
|
|
2880
|
-
};
|
|
2881
|
-
}
|
|
2882
2881
|
return {
|
|
2883
2882
|
content: [{ type: "text", text: "Message sent successfully" }]
|
|
2884
2883
|
};
|
|
@@ -3025,22 +3024,42 @@ function createWorkerTools(client, options) {
|
|
|
3025
3024
|
return errorResult(err);
|
|
3026
3025
|
}
|
|
3027
3026
|
}),
|
|
3028
|
-
createTool(WORKER_TOOLS.
|
|
3027
|
+
createTool(WORKER_TOOLS.UPDATE_TASK_STATUS, async (params) => {
|
|
3029
3028
|
try {
|
|
3030
3029
|
const taskId = validateStringParam(params, "taskId", true);
|
|
3031
|
-
const
|
|
3032
|
-
|
|
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({
|
|
3033
3044
|
taskId,
|
|
3034
|
-
|
|
3045
|
+
status: status2,
|
|
3046
|
+
awaiting,
|
|
3047
|
+
reason
|
|
3035
3048
|
});
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3049
|
+
if (status2 === "done") {
|
|
3050
|
+
try {
|
|
3051
|
+
options?.onTaskCompleted?.(taskId);
|
|
3052
|
+
} catch {
|
|
3053
|
+
}
|
|
3039
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}`);
|
|
3040
3059
|
return formatTaskResponse(
|
|
3041
3060
|
result,
|
|
3042
|
-
`Task "${result.title}"
|
|
3043
|
-
|
|
3061
|
+
`Task "${result.title}" updated`,
|
|
3062
|
+
details
|
|
3044
3063
|
);
|
|
3045
3064
|
} catch (err) {
|
|
3046
3065
|
return errorResult(err);
|
|
@@ -3099,7 +3118,6 @@ Human attention is finite. You have unlimited stamina \u2014 they don't. Optimiz
|
|
|
3099
3118
|
|
|
3100
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.
|
|
3101
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.
|
|
3102
|
-
- **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.
|
|
3103
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.
|
|
3104
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.`;
|
|
3105
3123
|
}
|
|
@@ -3151,7 +3169,7 @@ function buildWorkerDirective(ctx) {
|
|
|
3151
3169
|
## You're In a Task Thread
|
|
3152
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.
|
|
3153
3171
|
|
|
3154
|
-
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.
|
|
3155
3173
|
|
|
3156
3174
|
**Context loading:**
|
|
3157
3175
|
- If your first message contains \`<task_context>\`, the task is already loaded. Start working immediately \u2014 do NOT call deskfree_start_task.
|
|
@@ -3165,15 +3183,15 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
3165
3183
|
**Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
|
|
3166
3184
|
|
|
3167
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.
|
|
3168
|
-
2. **Align** \u2014 Send a brief
|
|
3169
|
-
- **Judgment calls or creative direction?** State your assumptions and approach,
|
|
3170
|
-
- **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.
|
|
3171
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.
|
|
3172
|
-
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.
|
|
3173
3191
|
|
|
3174
3192
|
**Push back when warranted:**
|
|
3175
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.
|
|
3176
|
-
- 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.
|
|
3177
3195
|
- You're a teammate, not a task executor. Have an opinion when you have the context to form one.
|
|
3178
3196
|
|
|
3179
3197
|
**File rules:**
|
|
@@ -3540,31 +3558,25 @@ function validateField(opts) {
|
|
|
3540
3558
|
return patternMessage ?? `${name2} contains invalid characters`;
|
|
3541
3559
|
return null;
|
|
3542
3560
|
}
|
|
3543
|
-
function isLocalDevelopmentHost(
|
|
3544
|
-
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);
|
|
3545
3563
|
}
|
|
3546
|
-
function
|
|
3547
|
-
const fieldError = validateField({ value, name: "Bot
|
|
3564
|
+
function validateBotId(value) {
|
|
3565
|
+
const fieldError = validateField({ value, name: "Bot ID" });
|
|
3548
3566
|
if (fieldError) return fieldError;
|
|
3549
3567
|
const trimmed = value.trim();
|
|
3550
|
-
if (
|
|
3551
|
-
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.";
|
|
3552
3570
|
}
|
|
3553
3571
|
const patternError = validateField({
|
|
3554
3572
|
value,
|
|
3555
|
-
name: "Bot
|
|
3573
|
+
name: "Bot ID",
|
|
3556
3574
|
minLength: 10,
|
|
3557
|
-
maxLength:
|
|
3558
|
-
pattern: /^
|
|
3559
|
-
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."
|
|
3560
3578
|
});
|
|
3561
3579
|
if (patternError) return patternError;
|
|
3562
|
-
if (trimmed.includes(" ") || trimmed.includes("\n") || trimmed.includes(" ")) {
|
|
3563
|
-
return "Bot token contains whitespace characters. Please copy the token exactly as shown in DeskFree.";
|
|
3564
|
-
}
|
|
3565
|
-
if (trimmed === "bot_your_token_here" || trimmed === "bot_example") {
|
|
3566
|
-
return "Please replace the placeholder with your actual bot token from DeskFree";
|
|
3567
|
-
}
|
|
3568
3580
|
return null;
|
|
3569
3581
|
}
|
|
3570
3582
|
function validateUrl(value, name2, allowedProtocols, protocolError) {
|
|
@@ -7351,13 +7363,13 @@ var init_dist = __esm({
|
|
|
7351
7363
|
}
|
|
7352
7364
|
};
|
|
7353
7365
|
DeskFreeClient = class {
|
|
7354
|
-
|
|
7366
|
+
getToken;
|
|
7355
7367
|
apiUrl;
|
|
7356
7368
|
requestTimeoutMs;
|
|
7357
|
-
constructor(
|
|
7358
|
-
this.
|
|
7359
|
-
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
7360
|
-
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;
|
|
7361
7373
|
}
|
|
7362
7374
|
async request(method, procedure, input) {
|
|
7363
7375
|
const url = method === "GET" && input ? `${this.apiUrl}/${procedure}?input=${encodeURIComponent(JSON.stringify(input))}` : `${this.apiUrl}/${procedure}`;
|
|
@@ -7367,7 +7379,7 @@ var init_dist = __esm({
|
|
|
7367
7379
|
const response = await fetch(url, {
|
|
7368
7380
|
method,
|
|
7369
7381
|
headers: {
|
|
7370
|
-
Authorization: `
|
|
7382
|
+
Authorization: `Bearer ${this.getToken()}`,
|
|
7371
7383
|
"Content-Type": "application/json"
|
|
7372
7384
|
},
|
|
7373
7385
|
body: method === "POST" ? JSON.stringify(input) : void 0,
|
|
@@ -7498,15 +7510,10 @@ var init_dist = __esm({
|
|
|
7498
7510
|
this.requireNonEmpty(input.taskId, "taskId");
|
|
7499
7511
|
return this.request("POST", "tasks.reportUsage", input);
|
|
7500
7512
|
}
|
|
7501
|
-
/**
|
|
7502
|
-
async
|
|
7513
|
+
/** Update task status and/or awaiting state. */
|
|
7514
|
+
async updateTaskStatus(input) {
|
|
7503
7515
|
this.requireNonEmpty(input.taskId, "taskId");
|
|
7504
|
-
return this.request("POST", "tasks.
|
|
7505
|
-
}
|
|
7506
|
-
/** Reopen a completed/human task back to bot status for further work. */
|
|
7507
|
-
async reopenTask(input) {
|
|
7508
|
-
this.requireNonEmpty(input.taskId, "taskId");
|
|
7509
|
-
return this.request("POST", "tasks.reopen", input);
|
|
7516
|
+
return this.request("POST", "tasks.updateStatus", input);
|
|
7510
7517
|
}
|
|
7511
7518
|
/** Snooze a task until a specified time. Task is hidden from active queues until then. */
|
|
7512
7519
|
async snoozeTask(input) {
|
|
@@ -7580,7 +7587,7 @@ var init_dist = __esm({
|
|
|
7580
7587
|
{
|
|
7581
7588
|
method: "GET",
|
|
7582
7589
|
headers: {
|
|
7583
|
-
Authorization: `
|
|
7590
|
+
Authorization: `Bearer ${this.getToken()}`,
|
|
7584
7591
|
"Content-Type": "application/json"
|
|
7585
7592
|
},
|
|
7586
7593
|
signal: controller.signal
|
|
@@ -7922,14 +7929,24 @@ var init_dist = __esm({
|
|
|
7922
7929
|
})
|
|
7923
7930
|
})
|
|
7924
7931
|
},
|
|
7925
|
-
|
|
7926
|
-
name: "
|
|
7927
|
-
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.",
|
|
7928
7935
|
parameters: Type.Object({
|
|
7929
|
-
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
|
+
),
|
|
7930
7947
|
reason: Type.Optional(
|
|
7931
7948
|
Type.String({
|
|
7932
|
-
description: "
|
|
7949
|
+
description: "Brief explanation (shown in task thread as system message)"
|
|
7933
7950
|
})
|
|
7934
7951
|
)
|
|
7935
7952
|
})
|
|
@@ -7938,9 +7955,6 @@ var init_dist = __esm({
|
|
|
7938
7955
|
name: "deskfree_send_message",
|
|
7939
7956
|
description: "Send a message to the human. Keep it short \u2014 1-3 sentences. No walls of text.",
|
|
7940
7957
|
parameters: Type.Object({
|
|
7941
|
-
type: Type.Union([Type.Literal("notify"), Type.Literal("ask")], {
|
|
7942
|
-
description: "notify = general update (quiet). ask = needs human attention (surfaces prominently)."
|
|
7943
|
-
}),
|
|
7944
7958
|
content: Type.String({
|
|
7945
7959
|
description: "Message content."
|
|
7946
7960
|
}),
|
|
@@ -8072,23 +8086,10 @@ var init_dist = __esm({
|
|
|
8072
8086
|
)
|
|
8073
8087
|
})
|
|
8074
8088
|
},
|
|
8075
|
-
COMPLETE_TASK: {
|
|
8076
|
-
name: "deskfree_complete_task",
|
|
8077
|
-
description: "Mark a task as done. Only call when truly finished and human confirmed.",
|
|
8078
|
-
parameters: Type.Object({
|
|
8079
|
-
taskId: Type.String({ description: "Task UUID" }),
|
|
8080
|
-
humanApproved: Type.Boolean({
|
|
8081
|
-
description: "Must be true. Confirms the human reviewed and approved completion. Backend validates a human message exists after your last ask."
|
|
8082
|
-
})
|
|
8083
|
-
})
|
|
8084
|
-
},
|
|
8085
8089
|
SEND_MESSAGE: {
|
|
8086
8090
|
name: "deskfree_send_message",
|
|
8087
8091
|
description: "Send a message in the task thread. Keep it short \u2014 1-3 sentences.",
|
|
8088
8092
|
parameters: Type.Object({
|
|
8089
|
-
type: Type.Union([Type.Literal("notify"), Type.Literal("ask")], {
|
|
8090
|
-
description: "notify = progress update (quiet, collapsible). ask = needs human attention (surfaces to main thread). Terminate after sending an ask."
|
|
8091
|
-
}),
|
|
8092
8093
|
content: Type.String({
|
|
8093
8094
|
description: "Message content."
|
|
8094
8095
|
}),
|
|
@@ -8229,7 +8230,28 @@ var init_dist = __esm({
|
|
|
8229
8230
|
)
|
|
8230
8231
|
})
|
|
8231
8232
|
},
|
|
8232
|
-
|
|
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
|
+
},
|
|
8233
8255
|
SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
|
|
8234
8256
|
PROPOSE: SHARED_TOOLS.PROPOSE
|
|
8235
8257
|
};
|
|
@@ -8276,7 +8298,7 @@ Record immediately when: the human corrects you, expresses a preference, shares
|
|
|
8276
8298
|
DESKFREE_WORKER_DIRECTIVE = `## DeskFree \u2014 Task Thread
|
|
8277
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.
|
|
8278
8300
|
|
|
8279
|
-
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.
|
|
8280
8302
|
|
|
8281
8303
|
**Context loading:**
|
|
8282
8304
|
- If your first message contains \`<task_context>\`, the task is already loaded. Start working immediately \u2014 do NOT call deskfree_start_task.
|
|
@@ -8290,15 +8312,15 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
8290
8312
|
**Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
|
|
8291
8313
|
|
|
8292
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.
|
|
8293
|
-
2. **Align** \u2014 Send a brief
|
|
8294
|
-
- **Judgment calls or creative direction?** State your assumptions and approach,
|
|
8295
|
-
- **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.
|
|
8296
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.
|
|
8297
|
-
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.
|
|
8298
8320
|
|
|
8299
8321
|
**Push back when warranted:**
|
|
8300
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.
|
|
8301
|
-
- 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.
|
|
8302
8324
|
- You're a teammate, not a task executor. Have an opinion when you have the context to form one.
|
|
8303
8325
|
|
|
8304
8326
|
**File rules:**
|
|
@@ -8644,43 +8666,27 @@ var init_orchestrator = __esm({
|
|
|
8644
8666
|
];
|
|
8645
8667
|
}
|
|
8646
8668
|
});
|
|
8647
|
-
function
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
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`;
|
|
8653
8677
|
}
|
|
8654
8678
|
function loadConfig() {
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
const decoded = JSON.parse(
|
|
8661
|
-
Buffer.from(launch, "base64").toString("utf8")
|
|
8662
|
-
);
|
|
8663
|
-
botToken = decoded.botToken;
|
|
8664
|
-
apiUrl = decoded.apiUrl;
|
|
8665
|
-
if (!botToken || !apiUrl) {
|
|
8666
|
-
throw new Error(
|
|
8667
|
-
"Missing botToken or apiUrl in DESKFREE_LAUNCH payload"
|
|
8668
|
-
);
|
|
8669
|
-
}
|
|
8670
|
-
} catch (err) {
|
|
8671
|
-
if (err instanceof SyntaxError) {
|
|
8672
|
-
throw new Error("DESKFREE_LAUNCH is not valid base64-encoded JSON");
|
|
8673
|
-
}
|
|
8674
|
-
throw err;
|
|
8675
|
-
}
|
|
8676
|
-
} else {
|
|
8677
|
-
botToken = requireEnv("DESKFREE_BOT_TOKEN");
|
|
8678
|
-
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
|
+
);
|
|
8679
8684
|
}
|
|
8680
|
-
const
|
|
8681
|
-
if (
|
|
8682
|
-
throw new Error(`Invalid bot
|
|
8685
|
+
const idError = validateBotId(botId);
|
|
8686
|
+
if (idError !== null) {
|
|
8687
|
+
throw new Error(`Invalid bot ID: ${idError}`);
|
|
8683
8688
|
}
|
|
8689
|
+
const apiUrl = process.env["DESKFREE_API_URL"] ?? deriveApiUrl();
|
|
8684
8690
|
const apiUrlError = validateApiUrl(apiUrl);
|
|
8685
8691
|
if (apiUrlError !== null) {
|
|
8686
8692
|
throw new Error(`Invalid API URL: ${apiUrlError}`);
|
|
@@ -8696,7 +8702,7 @@ function loadConfig() {
|
|
|
8696
8702
|
throw new Error(`Invalid HEALTH_PORT: ${healthPortRaw}`);
|
|
8697
8703
|
}
|
|
8698
8704
|
return {
|
|
8699
|
-
|
|
8705
|
+
botId,
|
|
8700
8706
|
apiUrl,
|
|
8701
8707
|
stateDir: process.env["DESKFREE_STATE_DIR"] ?? DEFAULTS.stateDir,
|
|
8702
8708
|
toolsDir: process.env["DESKFREE_TOOLS_DIR"] ?? DEFAULTS.toolsDir,
|
|
@@ -12584,6 +12590,25 @@ var init_wrapper = __esm({
|
|
|
12584
12590
|
wrapper_default2 = import_websocket2.default;
|
|
12585
12591
|
}
|
|
12586
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
|
+
}
|
|
12587
12612
|
function nextBackoff(state2) {
|
|
12588
12613
|
const delay = Math.min(
|
|
12589
12614
|
BACKOFF_INITIAL_MS * Math.pow(BACKOFF_FACTOR, state2.attempt),
|
|
@@ -12609,6 +12634,55 @@ function sleepWithAbort(ms, signal) {
|
|
|
12609
12634
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
12610
12635
|
});
|
|
12611
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
|
+
}
|
|
12612
12686
|
async function startGateway(config) {
|
|
12613
12687
|
const { client, accountId, stateDir, log, abortSignal } = config;
|
|
12614
12688
|
const ctx = { accountId };
|
|
@@ -12625,7 +12699,20 @@ async function startGateway(config) {
|
|
|
12625
12699
|
});
|
|
12626
12700
|
while (!abortSignal.aborted) {
|
|
12627
12701
|
try {
|
|
12628
|
-
|
|
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
|
+
}
|
|
12629
12716
|
resetBackoff(backoff);
|
|
12630
12717
|
if (totalReconnects > 0) {
|
|
12631
12718
|
log.info(
|
|
@@ -12647,17 +12734,18 @@ async function startGateway(config) {
|
|
|
12647
12734
|
abortSignal,
|
|
12648
12735
|
onMessage: config.onMessage,
|
|
12649
12736
|
getWorkerStatus: config.getWorkerStatus,
|
|
12650
|
-
onConsolidate: config.onConsolidate
|
|
12737
|
+
onConsolidate: config.onConsolidate,
|
|
12738
|
+
isV2: true
|
|
12651
12739
|
});
|
|
12652
12740
|
totalReconnects++;
|
|
12653
12741
|
} catch (err) {
|
|
12654
12742
|
totalReconnects++;
|
|
12655
12743
|
const message = err instanceof Error ? err.message : String(err);
|
|
12656
|
-
if (message.includes("
|
|
12744
|
+
if (message.includes("bots.connect failed") || message.includes("server error") || message.includes("Connection rejected")) {
|
|
12657
12745
|
log.warn(
|
|
12658
|
-
`
|
|
12746
|
+
`Connection setup failed (attempt #${totalReconnects}): ${message}. Falling back to polling.`
|
|
12659
12747
|
);
|
|
12660
|
-
reportError("error", `
|
|
12748
|
+
reportError("error", `Connection setup failed: ${message}`, {
|
|
12661
12749
|
component: "gateway",
|
|
12662
12750
|
event: "ticket_fetch_failed",
|
|
12663
12751
|
attempt: totalReconnects
|
|
@@ -12702,6 +12790,7 @@ async function runWebSocketConnection(opts) {
|
|
|
12702
12790
|
let connectionTimer;
|
|
12703
12791
|
let pongTimer;
|
|
12704
12792
|
let notifyDebounceTimer;
|
|
12793
|
+
let proactiveReconnectTimer;
|
|
12705
12794
|
let isConnected = false;
|
|
12706
12795
|
const cleanup = () => {
|
|
12707
12796
|
if (pingInterval !== void 0) {
|
|
@@ -12720,6 +12809,10 @@ async function runWebSocketConnection(opts) {
|
|
|
12720
12809
|
clearTimeout(notifyDebounceTimer);
|
|
12721
12810
|
notifyDebounceTimer = void 0;
|
|
12722
12811
|
}
|
|
12812
|
+
if (proactiveReconnectTimer !== void 0) {
|
|
12813
|
+
clearTimeout(proactiveReconnectTimer);
|
|
12814
|
+
proactiveReconnectTimer = void 0;
|
|
12815
|
+
}
|
|
12723
12816
|
};
|
|
12724
12817
|
connectionTimer = setTimeout(() => {
|
|
12725
12818
|
if (!isConnected) {
|
|
@@ -12748,7 +12841,11 @@ async function runWebSocketConnection(opts) {
|
|
|
12748
12841
|
pingInterval = setInterval(() => {
|
|
12749
12842
|
if (ws.readyState === wrapper_default2.OPEN) {
|
|
12750
12843
|
try {
|
|
12751
|
-
|
|
12844
|
+
const pingPayload = { action: "ping" };
|
|
12845
|
+
if (currentRotationToken) {
|
|
12846
|
+
pingPayload.token = currentRotationToken;
|
|
12847
|
+
}
|
|
12848
|
+
ws.send(JSON.stringify(pingPayload));
|
|
12752
12849
|
pongTimer = setTimeout(() => {
|
|
12753
12850
|
log.warn("Pong timeout \u2014 closing WebSocket");
|
|
12754
12851
|
try {
|
|
@@ -12762,6 +12859,13 @@ async function runWebSocketConnection(opts) {
|
|
|
12762
12859
|
}
|
|
12763
12860
|
}
|
|
12764
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);
|
|
12765
12869
|
if (opts.getWorkerStatus) {
|
|
12766
12870
|
try {
|
|
12767
12871
|
const status2 = opts.getWorkerStatus();
|
|
@@ -12834,7 +12938,21 @@ async function runWebSocketConnection(opts) {
|
|
|
12834
12938
|
clearTimeout(pongTimer);
|
|
12835
12939
|
pongTimer = void 0;
|
|
12836
12940
|
}
|
|
12837
|
-
|
|
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
|
+
}
|
|
12838
12956
|
} else if (msg.action === "heartbeatRequest") {
|
|
12839
12957
|
if (opts.getWorkerStatus && ws.readyState === wrapper_default2.OPEN) {
|
|
12840
12958
|
try {
|
|
@@ -13042,7 +13160,7 @@ async function runPollingFallback(opts) {
|
|
|
13042
13160
|
}
|
|
13043
13161
|
return cursor;
|
|
13044
13162
|
}
|
|
13045
|
-
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;
|
|
13046
13164
|
var init_ws_gateway = __esm({
|
|
13047
13165
|
"src/gateway/ws-gateway.ts"() {
|
|
13048
13166
|
init_health_state();
|
|
@@ -13058,9 +13176,12 @@ var init_ws_gateway = __esm({
|
|
|
13058
13176
|
HEALTH_LOG_INTERVAL_MS = 30 * 60 * 1e3;
|
|
13059
13177
|
MAX_POLLING_ITERATIONS = 10;
|
|
13060
13178
|
MAX_CONSECUTIVE_POLL_FAILURES = 5;
|
|
13179
|
+
CONNECT_POLL_INTERVAL_MS = 3e4;
|
|
13180
|
+
PROACTIVE_RECONNECT_MS = 110 * 60 * 1e3;
|
|
13061
13181
|
BACKOFF_INITIAL_MS = 2e3;
|
|
13062
13182
|
BACKOFF_MAX_MS = 3e4;
|
|
13063
13183
|
BACKOFF_FACTOR = 1.8;
|
|
13184
|
+
currentRotationToken = null;
|
|
13064
13185
|
}
|
|
13065
13186
|
});
|
|
13066
13187
|
function jsonSchemaPropertyToZod(prop) {
|
|
@@ -13274,13 +13395,13 @@ function validateDownloadUrl(url) {
|
|
|
13274
13395
|
if (parsed.protocol !== "https:") {
|
|
13275
13396
|
throw new Error("Only HTTPS URLs are allowed");
|
|
13276
13397
|
}
|
|
13277
|
-
const
|
|
13278
|
-
const bareHostname =
|
|
13398
|
+
const hostname2 = parsed.hostname.toLowerCase();
|
|
13399
|
+
const bareHostname = hostname2.replace(/^\[|\]$/g, "");
|
|
13279
13400
|
if (bareHostname === "localhost" || bareHostname === "127.0.0.1" || bareHostname === "::1") {
|
|
13280
13401
|
throw new Error("Local URLs are not allowed");
|
|
13281
13402
|
}
|
|
13282
13403
|
for (const pattern of PRIVATE_IPV4_PATTERNS) {
|
|
13283
|
-
if (pattern.test(
|
|
13404
|
+
if (pattern.test(hostname2)) {
|
|
13284
13405
|
throw new Error("Private IP addresses are not allowed");
|
|
13285
13406
|
}
|
|
13286
13407
|
}
|
|
@@ -13850,8 +13971,9 @@ async function routeMessage(message, client, deps, sessionStore, config) {
|
|
|
13850
13971
|
`Auto-reopening task ${message.taskId} (attachment on ${task.status} task)`
|
|
13851
13972
|
);
|
|
13852
13973
|
try {
|
|
13853
|
-
await client.
|
|
13974
|
+
await client.updateTaskStatus({
|
|
13854
13975
|
taskId: message.taskId,
|
|
13976
|
+
status: "open",
|
|
13855
13977
|
reason: "Human sent an attachment \u2014 reopening for further work"
|
|
13856
13978
|
});
|
|
13857
13979
|
routingTarget = "orchestrator";
|
|
@@ -14659,6 +14781,59 @@ ${userMessage}
|
|
|
14659
14781
|
}
|
|
14660
14782
|
});
|
|
14661
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
|
+
|
|
14662
14837
|
// src/service/entrypoint.ts
|
|
14663
14838
|
var entrypoint_exports = {};
|
|
14664
14839
|
__export(entrypoint_exports, {
|
|
@@ -14733,12 +14908,75 @@ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, cla
|
|
|
14733
14908
|
setTimeout(() => void tick(), intervalMs);
|
|
14734
14909
|
log.info(`Heartbeat scheduled every ${Math.round(intervalMs / 1e3)}s`);
|
|
14735
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
|
+
}
|
|
14736
14956
|
async function startAgent(opts) {
|
|
14737
14957
|
const localConfig = loadConfig();
|
|
14738
14958
|
const log = opts?.log ?? createLogger("agent", localConfig.logLevel);
|
|
14739
14959
|
const abortController = new AbortController();
|
|
14740
14960
|
log.info("DeskFree Agent Runtime starting...");
|
|
14741
|
-
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
|
+
});
|
|
14742
14980
|
const errorReporter = initErrorReporter(client, log);
|
|
14743
14981
|
initializeHealth("unknown");
|
|
14744
14982
|
log.info("Bootstrapping from API...", { apiUrl: localConfig.apiUrl });
|
|
@@ -14758,7 +14996,6 @@ async function startAgent(opts) {
|
|
|
14758
14996
|
throw new Error(`Failed to bootstrap config from API: ${msg}`);
|
|
14759
14997
|
}
|
|
14760
14998
|
const isDocker2 = process.env["DOCKER"] === "1" || (await import('fs')).existsSync("/.dockerenv");
|
|
14761
|
-
const runtimeVersion = process.env["npm_package_version"] ?? "unknown";
|
|
14762
14999
|
const agentContext = {
|
|
14763
15000
|
botName: config.botName,
|
|
14764
15001
|
deploymentType: config.deploymentType,
|
|
@@ -14838,6 +15075,13 @@ async function startAgent(opts) {
|
|
|
14838
15075
|
stateDir: config.stateDir,
|
|
14839
15076
|
log,
|
|
14840
15077
|
abortSignal: abortController.signal,
|
|
15078
|
+
botId: localConfig.botId,
|
|
15079
|
+
publicApiUrl,
|
|
15080
|
+
fingerprint,
|
|
15081
|
+
initialTicket: {
|
|
15082
|
+
ticket: connectResult.ticket,
|
|
15083
|
+
wsUrl: connectResult.wsUrl
|
|
15084
|
+
},
|
|
14841
15085
|
getWorkerStatus: () => ({
|
|
14842
15086
|
activeWorkers: workerManager.activeCount,
|
|
14843
15087
|
queuedTasks: workerManager.queuedCount,
|
|
@@ -14926,13 +15170,44 @@ async function startAgent(opts) {
|
|
|
14926
15170
|
`Sleep cycle: invoking sleep agent (${orientResult.entries.length} unconsolidated entries)...`
|
|
14927
15171
|
);
|
|
14928
15172
|
const workerServer = createWorkServer();
|
|
14929
|
-
const
|
|
15173
|
+
const agentStream = runOneShotWorker({
|
|
14930
15174
|
prompt,
|
|
14931
15175
|
systemPrompt: buildSleepDirective(agentContext),
|
|
14932
15176
|
workerServer,
|
|
14933
15177
|
model: config.model
|
|
14934
15178
|
});
|
|
14935
|
-
|
|
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
|
+
}
|
|
15187
|
+
}
|
|
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
|
+
});
|
|
15200
|
+
log.info(
|
|
15201
|
+
`Sleep cycle: consolidation applied (${consolidateResult.opsApplied} ops, ${consolidateResult.entriesArchived} archived)`
|
|
15202
|
+
);
|
|
15203
|
+
} catch (parseErr) {
|
|
15204
|
+
const parseMsg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
15205
|
+
log.warn(`Sleep cycle: failed to submit consolidation: ${parseMsg}`);
|
|
15206
|
+
}
|
|
15207
|
+
} else {
|
|
15208
|
+
log.warn(
|
|
15209
|
+
"Sleep cycle: agent did not produce <consolidation_result> tags"
|
|
15210
|
+
);
|
|
14936
15211
|
}
|
|
14937
15212
|
return "success";
|
|
14938
15213
|
} finally {
|
|
@@ -15007,6 +15282,7 @@ async function startAgent(opts) {
|
|
|
15007
15282
|
log.info("Shutdown complete.");
|
|
15008
15283
|
};
|
|
15009
15284
|
}
|
|
15285
|
+
var CONNECT_POLL_INTERVAL_MS2;
|
|
15010
15286
|
var init_entrypoint = __esm({
|
|
15011
15287
|
"src/service/entrypoint.ts"() {
|
|
15012
15288
|
init_orchestrator();
|
|
@@ -15023,6 +15299,7 @@ var init_entrypoint = __esm({
|
|
|
15023
15299
|
init_sessions();
|
|
15024
15300
|
init_worker_manager();
|
|
15025
15301
|
init_dist();
|
|
15302
|
+
CONNECT_POLL_INTERVAL_MS2 = 3e4;
|
|
15026
15303
|
}
|
|
15027
15304
|
});
|
|
15028
15305
|
|
|
@@ -15031,29 +15308,42 @@ init_paths();
|
|
|
15031
15308
|
var [name, cleanArgs] = parseName(process.argv.slice(2));
|
|
15032
15309
|
var command = cleanArgs[0];
|
|
15033
15310
|
if (command === "install") {
|
|
15034
|
-
|
|
15035
|
-
|
|
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) {
|
|
15036
15326
|
const { createInterface } = await import('readline');
|
|
15037
15327
|
const rl = createInterface({
|
|
15038
15328
|
input: process.stdin,
|
|
15039
15329
|
output: process.stdout
|
|
15040
15330
|
});
|
|
15041
|
-
|
|
15331
|
+
botId = await new Promise((resolve) => {
|
|
15042
15332
|
rl.question(
|
|
15043
|
-
"Paste your bot
|
|
15333
|
+
"Paste your bot ID (from the DeskFree dashboard):\n> ",
|
|
15044
15334
|
(answer) => {
|
|
15045
15335
|
rl.close();
|
|
15046
15336
|
resolve(answer.trim());
|
|
15047
15337
|
}
|
|
15048
15338
|
);
|
|
15049
15339
|
});
|
|
15050
|
-
if (!
|
|
15051
|
-
console.error("No
|
|
15340
|
+
if (!botId) {
|
|
15341
|
+
console.error("No bot ID provided.");
|
|
15052
15342
|
process.exit(1);
|
|
15053
15343
|
}
|
|
15054
15344
|
}
|
|
15055
15345
|
const { install: install2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
15056
|
-
install2(
|
|
15346
|
+
install2(botId, name, stage);
|
|
15057
15347
|
} else if (command === "uninstall") {
|
|
15058
15348
|
const { uninstall: uninstall2 } = await Promise.resolve().then(() => (init_uninstall(), uninstall_exports));
|
|
15059
15349
|
uninstall2(name);
|
|
@@ -15079,14 +15369,12 @@ if (command === "install") {
|
|
|
15079
15369
|
process.exit(1);
|
|
15080
15370
|
}, 5e3).unref();
|
|
15081
15371
|
};
|
|
15082
|
-
|
|
15083
|
-
if (
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
15088
|
-
if (token && !process.env["DESKFREE_LAUNCH"]) {
|
|
15089
|
-
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];
|
|
15090
15378
|
}
|
|
15091
15379
|
const { startAgent: startAgent2 } = await Promise.resolve().then(() => (init_entrypoint(), entrypoint_exports));
|
|
15092
15380
|
const { createLogger: createLogger2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
|