@questionbase/deskfree 0.6.2 → 0.6.4
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 +468 -490
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +15 -26
- package/dist/index.js +467 -489
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/bin.js
CHANGED
|
@@ -2729,34 +2729,33 @@ function createOrchestratorTools(client, _options) {
|
|
|
2729
2729
|
return errorResult(err);
|
|
2730
2730
|
}
|
|
2731
2731
|
}),
|
|
2732
|
-
createTool(ORCHESTRATOR_TOOLS.
|
|
2732
|
+
createTool(ORCHESTRATOR_TOOLS.CREATE_TASK, async (params) => {
|
|
2733
2733
|
try {
|
|
2734
|
-
const
|
|
2735
|
-
const
|
|
2736
|
-
const
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
taskId
|
|
2734
|
+
const title = validateStringParam(params, "title", true);
|
|
2735
|
+
const instructions = validateStringParam(params, "instructions", false);
|
|
2736
|
+
const suggestedByTaskId = validateStringParam(
|
|
2737
|
+
params,
|
|
2738
|
+
"suggestedByTaskId",
|
|
2739
|
+
false
|
|
2740
|
+
);
|
|
2741
|
+
const inputFileIds = Array.isArray(params.inputFileIds) ? params.inputFileIds : void 0;
|
|
2742
|
+
const outputFileIds = Array.isArray(params.outputFileIds) ? params.outputFileIds : void 0;
|
|
2743
|
+
const scheduledFor = validateStringParam(params, "scheduledFor", false);
|
|
2744
|
+
const estimatedTokens = typeof params.estimatedTokens === "number" ? params.estimatedTokens : void 0;
|
|
2745
|
+
const result = await client.createTask({
|
|
2746
|
+
title,
|
|
2747
|
+
instructions,
|
|
2748
|
+
estimatedTokens,
|
|
2749
|
+
scheduledFor,
|
|
2750
|
+
inputFileIds,
|
|
2751
|
+
outputFileIds,
|
|
2752
|
+
suggestedByTaskId
|
|
2754
2753
|
});
|
|
2755
2754
|
return {
|
|
2756
2755
|
content: [
|
|
2757
2756
|
{
|
|
2758
2757
|
type: "text",
|
|
2759
|
-
text: `
|
|
2758
|
+
text: `Task created: "${result.title}" (${result.taskId})`
|
|
2760
2759
|
}
|
|
2761
2760
|
]
|
|
2762
2761
|
};
|
|
@@ -2974,34 +2973,33 @@ function createWorkerTools(client, options) {
|
|
|
2974
2973
|
return errorResult(err);
|
|
2975
2974
|
}
|
|
2976
2975
|
}),
|
|
2977
|
-
createTool(WORKER_TOOLS.
|
|
2976
|
+
createTool(WORKER_TOOLS.CREATE_TASK, async (params) => {
|
|
2978
2977
|
try {
|
|
2979
|
-
const
|
|
2980
|
-
const
|
|
2981
|
-
const
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
taskId
|
|
2978
|
+
const title = validateStringParam(params, "title", true);
|
|
2979
|
+
const instructions = validateStringParam(params, "instructions", false);
|
|
2980
|
+
const suggestedByTaskId = validateStringParam(
|
|
2981
|
+
params,
|
|
2982
|
+
"suggestedByTaskId",
|
|
2983
|
+
false
|
|
2984
|
+
);
|
|
2985
|
+
const inputFileIds = Array.isArray(params.inputFileIds) ? params.inputFileIds : void 0;
|
|
2986
|
+
const outputFileIds = Array.isArray(params.outputFileIds) ? params.outputFileIds : void 0;
|
|
2987
|
+
const scheduledFor = validateStringParam(params, "scheduledFor", false);
|
|
2988
|
+
const estimatedTokens = typeof params.estimatedTokens === "number" ? params.estimatedTokens : void 0;
|
|
2989
|
+
const result = await client.createTask({
|
|
2990
|
+
title,
|
|
2991
|
+
instructions,
|
|
2992
|
+
estimatedTokens,
|
|
2993
|
+
scheduledFor,
|
|
2994
|
+
inputFileIds,
|
|
2995
|
+
outputFileIds,
|
|
2996
|
+
suggestedByTaskId
|
|
2999
2997
|
});
|
|
3000
2998
|
return {
|
|
3001
2999
|
content: [
|
|
3002
3000
|
{
|
|
3003
3001
|
type: "text",
|
|
3004
|
-
text: `
|
|
3002
|
+
text: `Task created: "${result.title}" (${result.taskId})`
|
|
3005
3003
|
}
|
|
3006
3004
|
]
|
|
3007
3005
|
};
|
|
@@ -3126,26 +3124,26 @@ function buildAgentDirective(ctx) {
|
|
|
3126
3124
|
|
|
3127
3125
|
## How You Work
|
|
3128
3126
|
|
|
3129
|
-
**Main thread = short and snappy.** Keep responses to 1-3 sentences. Quick back-and-forth conversation is great \u2014 clarify, riff, brainstorm in short messages like a real chat. But if something needs deep research, multiple rounds of clarification, or a deliverable \u2014
|
|
3127
|
+
**Main thread = short and snappy.** Keep responses to 1-3 sentences. Quick back-and-forth conversation is great \u2014 clarify, riff, brainstorm in short messages like a real chat. But if something needs deep research, multiple rounds of clarification, or a deliverable \u2014 create a task and move the work to a thread.
|
|
3130
3128
|
|
|
3131
3129
|
**The core loop:**
|
|
3132
3130
|
|
|
3133
3131
|
1. **Check state** \u2014 use \`deskfree_state\` to see tasks and files. Use \`deskfree_orient\` to recall relevant memories.
|
|
3134
|
-
2. **
|
|
3135
|
-
3. **Start work** \u2014 use \`deskfree_dispatch_worker\` with the taskId
|
|
3132
|
+
2. **Create tasks** \u2014 use \`deskfree_create_task\` to turn requests into concrete tasks.
|
|
3133
|
+
3. **Start work** \u2014 use \`deskfree_dispatch_worker\` with the taskId to work on the task in a thread.
|
|
3136
3134
|
4. **Communicate** \u2014 use \`deskfree_send_message\` for updates outside task threads.
|
|
3137
3135
|
|
|
3138
|
-
**Before
|
|
3139
|
-
- **One-off task** ("proofread this") \u2014
|
|
3140
|
-
- **New aspiration** ("I want to start posting on LinkedIn") \u2014 don't rush to
|
|
3141
|
-
- Never call \`
|
|
3136
|
+
**Before creating a task, qualify the request.** Figure out what kind of thing this is:
|
|
3137
|
+
- **One-off task** ("proofread this") \u2014 create a task directly.
|
|
3138
|
+
- **New aspiration** ("I want to start posting on LinkedIn") \u2014 don't rush to create a task. Ask 1-2 short qualifying questions to understand the real goal.
|
|
3139
|
+
- Never call \`deskfree_create_task\` as your very first action \u2014 qualify first, even if briefly.
|
|
3142
3140
|
|
|
3143
3141
|
**Match the human's energy.** Short message \u2192 short reply. Casual tone \u2192 casual response. Don't over-explain, don't lecture, don't pad responses.
|
|
3144
3142
|
|
|
3145
|
-
In the main thread you
|
|
3143
|
+
In the main thread you create tasks and coordinate \u2014 the actual work happens in task threads. Use \`deskfree_dispatch_worker\` to start working on tasks.
|
|
3146
3144
|
- When a human writes in a task thread, decide:
|
|
3147
3145
|
- **Continuation of the same task?** \u2192 reopen and pick it back up.
|
|
3148
|
-
- **New/different work request?** \u2192
|
|
3146
|
+
- **New/different work request?** \u2192 create it as a new task (don't reopen the old one).
|
|
3149
3147
|
- **Just confirmation or deferred?** \u2192 leave it for now.
|
|
3150
3148
|
- Estimate token cost per task \u2014 consider files to read, reasoning, output.
|
|
3151
3149
|
|
|
@@ -3169,7 +3167,7 @@ function buildWorkerDirective(ctx) {
|
|
|
3169
3167
|
## You're In a Task Thread
|
|
3170
3168
|
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.
|
|
3171
3169
|
|
|
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,
|
|
3170
|
+
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_create_task.
|
|
3173
3171
|
|
|
3174
3172
|
**Context loading:**
|
|
3175
3173
|
- If your first message contains \`<task_context>\`, the task is already loaded. Start working immediately \u2014 do NOT call deskfree_start_task.
|
|
@@ -3180,24 +3178,30 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
3180
3178
|
- If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to load it.
|
|
3181
3179
|
- If continuing from a previous conversation (you can see prior tool calls and context), respond directly to the human's latest message \u2014 do NOT call deskfree_start_task again.
|
|
3182
3180
|
|
|
3181
|
+
**Your text responses are automatically streamed to the human as messages.** You do NOT need to call \`deskfree_send_message\` to talk \u2014 just write your response text directly. Only use \`deskfree_send_message\` when you need to send a message mid-tool-execution (e.g. a progress update while doing file operations).
|
|
3182
|
+
|
|
3183
3183
|
**Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
|
|
3184
3184
|
|
|
3185
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.
|
|
3186
|
-
2. **Align** \u2014
|
|
3186
|
+
2. **Align** \u2014 State briefly what you found and what you'll produce. One or two sentences. ("I'll build on the existing brand guide and create a new tone reference.") Just write this as your response text \u2014 do NOT use \`deskfree_send_message\`.
|
|
3187
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
3188
|
- **Straightforward execution?** Proceed immediately \u2014 don't wait for a response.
|
|
3189
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.
|
|
3190
|
-
4. **Deliver** \u2014 When work is ready for review,
|
|
3190
|
+
4. **Deliver** \u2014 When work is ready for review, set \`awaiting: 'human'\` via \`deskfree_update_task_status\`. The human will complete the task when satisfied.
|
|
3191
3191
|
|
|
3192
3192
|
**Push back when warranted:**
|
|
3193
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.
|
|
3194
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.
|
|
3195
3195
|
- You're a teammate, not a task executor. Have an opinion when you have the context to form one.
|
|
3196
3196
|
|
|
3197
|
+
**New requests mid-task:**
|
|
3198
|
+
- When the human asks for something new in your thread, don't make it awkward \u2014 just roll with it. Qualify naturally ("Sure \u2014 what kind of audit?"), get enough clarity to write a well-scoped task, then use \`deskfree_create_task\`. No meta-commentary about scope boundaries or "that's outside this task." Just be a good conversationalist who happens to spin up a new task.
|
|
3199
|
+
- Every task needs a concrete deliverable and bounded scope. "Audit the codebase" is not a task \u2014 "Review tRPC error handling and list inconsistencies" is. If the human's request is too vague, ask a narrowing question before creating a task.
|
|
3200
|
+
- If you notice follow-up work yourself while executing, create a task immediately \u2014 don't wait until completion.
|
|
3201
|
+
|
|
3197
3202
|
**File rules:**
|
|
3198
3203
|
- Create files when your task naturally produces them. Don't be afraid to create multiple files if the work calls for it.
|
|
3199
3204
|
- Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
|
|
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.
|
|
3201
3205
|
|
|
3202
3206
|
**Learnings \u2014 record aggressively:**
|
|
3203
3207
|
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.
|
|
@@ -3235,7 +3239,7 @@ On each heartbeat, run through this checklist:
|
|
|
3235
3239
|
|
|
3236
3240
|
### 1. Work the queue
|
|
3237
3241
|
- Run \`deskfree_state\` to get the full workspace snapshot.
|
|
3238
|
-
- **Check board load.** If there are 3+ tasks awaiting human review or input, skip proactive
|
|
3242
|
+
- **Check board load.** If there are 3+ tasks awaiting human review or input, skip proactive task creation entirely \u2014 the human has enough on their plate. Focus only on dispatching approved work.
|
|
3239
3243
|
- Any open tasks with awaiting=bot? Use \`deskfree_dispatch_worker\` to start working on each one. Pass the taskId.
|
|
3240
3244
|
- Any open tasks that seem stalled (no recent activity)? Check on them.
|
|
3241
3245
|
|
|
@@ -3250,7 +3254,7 @@ After handling the queue, step back and think about the bigger picture. You have
|
|
|
3250
3254
|
|
|
3251
3255
|
**Then act \u2014 but only if you have something genuinely useful:**
|
|
3252
3256
|
|
|
3253
|
-
*Things you can do* \u2014 research, drafts, analysis, prep.
|
|
3257
|
+
*Things you can do* \u2014 research, drafts, analysis, prep. Create a task via \`deskfree_create_task\`. One focused task, not a batch.
|
|
3254
3258
|
|
|
3255
3259
|
*Things the human should do* \u2014 nudges, reminders, conversation starters. Send via \`deskfree_send_message\`. Keep it brief and genuinely helpful, not nagging.
|
|
3256
3260
|
|
|
@@ -3266,7 +3270,7 @@ function buildSleepDirective(ctx) {
|
|
|
3266
3270
|
## Nightly Sleep Cycle \u2014 Memory Consolidation
|
|
3267
3271
|
You're running your nightly cycle to consolidate observations into long-term memory.
|
|
3268
3272
|
|
|
3269
|
-
Tools available: deskfree_state,
|
|
3273
|
+
Tools available: deskfree_state, deskfree_create_task, deskfree_send_message, deskfree_learning.
|
|
3270
3274
|
|
|
3271
3275
|
Your job: classify new observations, decide merges/rewrites of existing entries, and write the operating memory summary. Code handles strength scoring, decay, archival, and dedup \u2014 you do semantics.
|
|
3272
3276
|
|
|
@@ -3353,16 +3357,16 @@ Write a ~1500 token markdown summary with these sections:
|
|
|
3353
3357
|
After outputting the consolidation result:
|
|
3354
3358
|
1. Call \`deskfree_state\` to see the board.
|
|
3355
3359
|
2. Send a brief main-thread message via \`deskfree_send_message\` summarizing what was consolidated (1-2 sentences).
|
|
3356
|
-
3. Check for recurring commitments in operating memory \u2014
|
|
3357
|
-
4. One proactive
|
|
3360
|
+
3. Check for recurring commitments in operating memory \u2014 create via \`deskfree_create_task\` if needed.
|
|
3361
|
+
4. One proactive task max. Skip if nothing merits it or board is busy (3+ items needing human attention).`;
|
|
3358
3362
|
}
|
|
3359
3363
|
function buildDuskDirective(ctx) {
|
|
3360
3364
|
return `${identityBlock(ctx)}
|
|
3361
3365
|
|
|
3362
3366
|
## Evening Dusk Cycle
|
|
3363
|
-
You're running your evening cycle to review the day,
|
|
3367
|
+
You're running your evening cycle to review the day, create overnight tasks, and brief the human.
|
|
3364
3368
|
|
|
3365
|
-
Tools available: deskfree_state,
|
|
3369
|
+
Tools available: deskfree_state, deskfree_create_task, deskfree_send_message, deskfree_read_file, deskfree_orient.
|
|
3366
3370
|
|
|
3367
3371
|
---
|
|
3368
3372
|
|
|
@@ -3393,28 +3397,28 @@ Think about work that can be done autonomously overnight \u2014 WITHOUT human ju
|
|
|
3393
3397
|
- Creative work where the human has strong opinions on direction
|
|
3394
3398
|
- Anything the human explicitly said to wait on
|
|
3395
3399
|
|
|
3396
|
-
### 3.
|
|
3400
|
+
### 3. CREATE TASKS
|
|
3397
3401
|
|
|
3398
3402
|
If you identified useful overnight work:
|
|
3399
|
-
1. **Check board load first.** Count open and awaiting-review tasks. If the human already has 3+ items needing their attention, limit to 1
|
|
3400
|
-
2. Use \`
|
|
3403
|
+
1. **Check board load first.** Count open and awaiting-review tasks. If the human already has 3+ items needing their attention, limit to 1 task max \u2014 or skip entirely. Don't pile on.
|
|
3404
|
+
2. Use \`deskfree_create_task\` to create 1-3 well-scoped tasks. Quality over quantity.
|
|
3401
3405
|
3. Each task should be self-contained \u2014 it must be completable without human input.
|
|
3402
3406
|
4. Set \`scheduledFor\` if work should start at a specific time (e.g. early morning).
|
|
3403
|
-
5. If nothing genuinely useful can be done overnight, skip
|
|
3407
|
+
5. If nothing genuinely useful can be done overnight, skip task creation entirely. Don't force it.
|
|
3404
3408
|
|
|
3405
3409
|
### 4. BRIEF THE HUMAN
|
|
3406
3410
|
|
|
3407
3411
|
Send a brief main-thread message via \`deskfree_send_message\`:
|
|
3408
3412
|
- 2-3 sentence summary: what happened today + what you're proposing for overnight (if anything).
|
|
3409
3413
|
- Keep it conversational and useful \u2014 this is the human's "end of day" touchpoint.
|
|
3410
|
-
- If no
|
|
3414
|
+
- If no tasks created, still send a brief day summary.
|
|
3411
3415
|
|
|
3412
3416
|
### Rules
|
|
3413
|
-
- Do NOT
|
|
3417
|
+
- Do NOT create tasks for things already on the board or recently completed.
|
|
3414
3418
|
- Do NOT repeat suggestions the human previously ignored or rejected.
|
|
3415
3419
|
- Quality over quantity. One good task beats three mediocre ones.
|
|
3416
3420
|
- Keep the briefing message short and actionable.
|
|
3417
|
-
- Cross-reference memory for recurring patterns \u2014 if something is due,
|
|
3421
|
+
- Cross-reference memory for recurring patterns \u2014 if something is due, create a task for it.
|
|
3418
3422
|
- Use \`deskfree_read_file\` only if you need file content beyond what's in your prompt.`;
|
|
3419
3423
|
}
|
|
3420
3424
|
function setActiveWs(ws) {
|
|
@@ -7552,17 +7556,9 @@ var init_dist = __esm({
|
|
|
7552
7556
|
async consolidateMemory(input) {
|
|
7553
7557
|
return this.request("POST", "memory.consolidate", input);
|
|
7554
7558
|
}
|
|
7555
|
-
/**
|
|
7556
|
-
async
|
|
7557
|
-
|
|
7558
|
-
throw new DeskFreeError(
|
|
7559
|
-
"client",
|
|
7560
|
-
"tasks",
|
|
7561
|
-
"tasks array is required and cannot be empty",
|
|
7562
|
-
"Missing required parameter: tasks."
|
|
7563
|
-
);
|
|
7564
|
-
}
|
|
7565
|
-
return this.request("POST", "tasks.propose", input);
|
|
7559
|
+
/** Create a task directly — no approval needed. */
|
|
7560
|
+
async createTask(input) {
|
|
7561
|
+
return this.request("POST", "tasks.create", input);
|
|
7566
7562
|
}
|
|
7567
7563
|
/**
|
|
7568
7564
|
* Fetch runtime bootstrap config from the backend.
|
|
@@ -7965,63 +7961,49 @@ var init_dist = __esm({
|
|
|
7965
7961
|
)
|
|
7966
7962
|
})
|
|
7967
7963
|
},
|
|
7968
|
-
|
|
7969
|
-
name: "
|
|
7970
|
-
description: "
|
|
7964
|
+
CREATE_TASK: {
|
|
7965
|
+
name: "deskfree_create_task",
|
|
7966
|
+
description: "Create a task directly. The task is created immediately and available for work. Put all details in the instructions field using simple markdown (bold, lists, inline code). Do NOT use markdown headers (#) \u2014 use **bold text** instead for section labels.",
|
|
7971
7967
|
parameters: Type.Object({
|
|
7972
|
-
|
|
7968
|
+
title: Type.String({
|
|
7969
|
+
description: "Task title \u2014 short, action-oriented (max 200 chars)"
|
|
7970
|
+
}),
|
|
7971
|
+
instructions: Type.Optional(
|
|
7973
7972
|
Type.String({
|
|
7974
|
-
description: "
|
|
7973
|
+
description: "Detailed instructions, constraints, or context for the task."
|
|
7975
7974
|
})
|
|
7976
7975
|
),
|
|
7977
|
-
|
|
7978
|
-
Type.
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
}),
|
|
7982
|
-
instructions: Type.Optional(
|
|
7983
|
-
Type.String({
|
|
7984
|
-
description: "Detailed instructions, constraints, or context for the task."
|
|
7985
|
-
})
|
|
7986
|
-
),
|
|
7987
|
-
estimatedTokens: Type.Optional(
|
|
7988
|
-
Type.Number({
|
|
7989
|
-
description: "Estimated token cost \u2014 consider files to read, reasoning, output"
|
|
7990
|
-
})
|
|
7991
|
-
),
|
|
7992
|
-
scheduledFor: Type.Optional(
|
|
7993
|
-
Type.String({
|
|
7994
|
-
description: "ISO-8601 date for when this task should become available. Use for future-dated or recurring work."
|
|
7995
|
-
})
|
|
7996
|
-
),
|
|
7997
|
-
inputFileIds: Type.Optional(
|
|
7998
|
-
Type.Array(
|
|
7999
|
-
Type.String({ description: "File ID to pre-load as context" }),
|
|
8000
|
-
{
|
|
8001
|
-
description: "File IDs to read as context input for this task",
|
|
8002
|
-
maxItems: 20
|
|
8003
|
-
}
|
|
8004
|
-
)
|
|
8005
|
-
),
|
|
8006
|
-
outputFileIds: Type.Optional(
|
|
8007
|
-
Type.Array(
|
|
8008
|
-
Type.String({ description: "File ID to update as deliverable" }),
|
|
8009
|
-
{
|
|
8010
|
-
description: "File IDs this task will produce or update",
|
|
8011
|
-
maxItems: 10
|
|
8012
|
-
}
|
|
8013
|
-
)
|
|
8014
|
-
)
|
|
8015
|
-
}),
|
|
8016
|
-
{
|
|
8017
|
-
description: "Array of tasks to propose (1-20)",
|
|
8018
|
-
minItems: 1,
|
|
8019
|
-
maxItems: 20
|
|
8020
|
-
}
|
|
7976
|
+
estimatedTokens: Type.Optional(
|
|
7977
|
+
Type.Number({
|
|
7978
|
+
description: "Estimated token cost \u2014 consider files to read, reasoning, output"
|
|
7979
|
+
})
|
|
8021
7980
|
),
|
|
8022
|
-
|
|
7981
|
+
scheduledFor: Type.Optional(
|
|
8023
7982
|
Type.String({
|
|
8024
|
-
description: "
|
|
7983
|
+
description: "ISO-8601 date for when this task should become available. Use for future-dated or recurring work."
|
|
7984
|
+
})
|
|
7985
|
+
),
|
|
7986
|
+
inputFileIds: Type.Optional(
|
|
7987
|
+
Type.Array(
|
|
7988
|
+
Type.String({ description: "File ID to pre-load as context" }),
|
|
7989
|
+
{
|
|
7990
|
+
description: "File IDs to read as context input for this task",
|
|
7991
|
+
maxItems: 20
|
|
7992
|
+
}
|
|
7993
|
+
)
|
|
7994
|
+
),
|
|
7995
|
+
outputFileIds: Type.Optional(
|
|
7996
|
+
Type.Array(
|
|
7997
|
+
Type.String({ description: "File ID to update as deliverable" }),
|
|
7998
|
+
{
|
|
7999
|
+
description: "File IDs this task will produce or update",
|
|
8000
|
+
maxItems: 10
|
|
8001
|
+
}
|
|
8002
|
+
)
|
|
8003
|
+
),
|
|
8004
|
+
suggestedByTaskId: Type.Optional(
|
|
8005
|
+
Type.String({
|
|
8006
|
+
description: "Parent task ID (for follow-up tasks created from within a task thread)"
|
|
8025
8007
|
})
|
|
8026
8008
|
)
|
|
8027
8009
|
})
|
|
@@ -8100,63 +8082,49 @@ var init_dist = __esm({
|
|
|
8100
8082
|
)
|
|
8101
8083
|
})
|
|
8102
8084
|
},
|
|
8103
|
-
|
|
8104
|
-
name: "
|
|
8105
|
-
description: "
|
|
8085
|
+
CREATE_TASK: {
|
|
8086
|
+
name: "deskfree_create_task",
|
|
8087
|
+
description: "Create a task directly. The task is created immediately and available for work. Put all details in the instructions field using simple markdown (bold, lists, inline code). Do NOT use markdown headers (#) \u2014 use **bold text** instead for section labels.",
|
|
8106
8088
|
parameters: Type.Object({
|
|
8107
|
-
|
|
8089
|
+
title: Type.String({
|
|
8090
|
+
description: "Task title \u2014 short, action-oriented (max 200 chars)"
|
|
8091
|
+
}),
|
|
8092
|
+
instructions: Type.Optional(
|
|
8108
8093
|
Type.String({
|
|
8109
|
-
description: "
|
|
8094
|
+
description: "Detailed instructions, constraints, or context for the task."
|
|
8110
8095
|
})
|
|
8111
8096
|
),
|
|
8112
|
-
|
|
8113
|
-
Type.
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
}),
|
|
8117
|
-
instructions: Type.Optional(
|
|
8118
|
-
Type.String({
|
|
8119
|
-
description: "Detailed instructions, constraints, or context for the task."
|
|
8120
|
-
})
|
|
8121
|
-
),
|
|
8122
|
-
estimatedTokens: Type.Optional(
|
|
8123
|
-
Type.Number({
|
|
8124
|
-
description: "Estimated token cost \u2014 consider files to read, reasoning, output"
|
|
8125
|
-
})
|
|
8126
|
-
),
|
|
8127
|
-
scheduledFor: Type.Optional(
|
|
8128
|
-
Type.String({
|
|
8129
|
-
description: "ISO-8601 date for when this task should become available. Use for future-dated or recurring work."
|
|
8130
|
-
})
|
|
8131
|
-
),
|
|
8132
|
-
inputFileIds: Type.Optional(
|
|
8133
|
-
Type.Array(
|
|
8134
|
-
Type.String({ description: "File ID to pre-load as context" }),
|
|
8135
|
-
{
|
|
8136
|
-
description: "File IDs to read as context input for this task",
|
|
8137
|
-
maxItems: 20
|
|
8138
|
-
}
|
|
8139
|
-
)
|
|
8140
|
-
),
|
|
8141
|
-
outputFileIds: Type.Optional(
|
|
8142
|
-
Type.Array(
|
|
8143
|
-
Type.String({ description: "File ID to update as deliverable" }),
|
|
8144
|
-
{
|
|
8145
|
-
description: "File IDs this task will produce or update",
|
|
8146
|
-
maxItems: 10
|
|
8147
|
-
}
|
|
8148
|
-
)
|
|
8149
|
-
)
|
|
8150
|
-
}),
|
|
8151
|
-
{
|
|
8152
|
-
description: "Array of tasks to propose (1-20)",
|
|
8153
|
-
minItems: 1,
|
|
8154
|
-
maxItems: 20
|
|
8155
|
-
}
|
|
8097
|
+
estimatedTokens: Type.Optional(
|
|
8098
|
+
Type.Number({
|
|
8099
|
+
description: "Estimated token cost \u2014 consider files to read, reasoning, output"
|
|
8100
|
+
})
|
|
8156
8101
|
),
|
|
8157
|
-
|
|
8102
|
+
scheduledFor: Type.Optional(
|
|
8103
|
+
Type.String({
|
|
8104
|
+
description: "ISO-8601 date for when this task should become available. Use for future-dated or recurring work."
|
|
8105
|
+
})
|
|
8106
|
+
),
|
|
8107
|
+
inputFileIds: Type.Optional(
|
|
8108
|
+
Type.Array(
|
|
8109
|
+
Type.String({ description: "File ID to pre-load as context" }),
|
|
8110
|
+
{
|
|
8111
|
+
description: "File IDs to read as context input for this task",
|
|
8112
|
+
maxItems: 20
|
|
8113
|
+
}
|
|
8114
|
+
)
|
|
8115
|
+
),
|
|
8116
|
+
outputFileIds: Type.Optional(
|
|
8117
|
+
Type.Array(
|
|
8118
|
+
Type.String({ description: "File ID to update as deliverable" }),
|
|
8119
|
+
{
|
|
8120
|
+
description: "File IDs this task will produce or update",
|
|
8121
|
+
maxItems: 10
|
|
8122
|
+
}
|
|
8123
|
+
)
|
|
8124
|
+
),
|
|
8125
|
+
suggestedByTaskId: Type.Optional(
|
|
8158
8126
|
Type.String({
|
|
8159
|
-
description: "
|
|
8127
|
+
description: "Parent task ID (for follow-up tasks created from within a task thread)"
|
|
8160
8128
|
})
|
|
8161
8129
|
)
|
|
8162
8130
|
})
|
|
@@ -8253,32 +8221,32 @@ var init_dist = __esm({
|
|
|
8253
8221
|
})
|
|
8254
8222
|
},
|
|
8255
8223
|
SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
|
|
8256
|
-
|
|
8224
|
+
CREATE_TASK: SHARED_TOOLS.CREATE_TASK
|
|
8257
8225
|
};
|
|
8258
8226
|
MAX_FULL_MESSAGES = 15;
|
|
8259
8227
|
DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 Main Thread
|
|
8260
|
-
You handle the main conversation thread. Your job: turn human intent into
|
|
8228
|
+
You handle the main conversation thread. Your job: turn human intent into concrete tasks, then start working on them.
|
|
8261
8229
|
|
|
8262
|
-
**Main thread = short and snappy.** Keep responses to 1-3 sentences. Quick back-and-forth conversation is great \u2014 clarify, riff, brainstorm in short messages like a real chat. But if something needs deep research, multiple rounds of clarification, or a deliverable \u2014
|
|
8230
|
+
**Main thread = short and snappy.** Keep responses to 1-3 sentences. Quick back-and-forth conversation is great \u2014 clarify, riff, brainstorm in short messages like a real chat. But if something needs deep research, multiple rounds of clarification, or a deliverable \u2014 create a task and move the work to a thread.
|
|
8263
8231
|
|
|
8264
|
-
**The core loop
|
|
8232
|
+
**The core loop:**
|
|
8265
8233
|
|
|
8266
8234
|
1. **Check state** \u2192 \`deskfree_state\` \u2014 see tasks and files. Use \`deskfree_orient\` to recall relevant memories.
|
|
8267
|
-
2. **
|
|
8268
|
-
3. **Start work** \u2192 \`deskfree_dispatch_worker\` with the taskId
|
|
8235
|
+
2. **Create tasks** \u2192 \`deskfree_create_task\` \u2014 turn requests into concrete tasks.
|
|
8236
|
+
3. **Start work** \u2192 \`deskfree_dispatch_worker\` with the taskId to work on the task in a thread.
|
|
8269
8237
|
4. **Communicate** \u2192 \`deskfree_send_message\` for updates outside task threads.
|
|
8270
8238
|
|
|
8271
|
-
**Before
|
|
8272
|
-
- **One-off task** ("proofread this") \u2192
|
|
8273
|
-
- **New aspiration** ("I want to start posting on LinkedIn") \u2192 don't rush to
|
|
8274
|
-
- Never call \`
|
|
8239
|
+
**Before creating a task, qualify the request.** Figure out what kind of thing this is:
|
|
8240
|
+
- **One-off task** ("proofread this") \u2192 create a task directly.
|
|
8241
|
+
- **New aspiration** ("I want to start posting on LinkedIn") \u2192 don't rush to create a task. Ask 1-2 short qualifying questions to understand the real goal.
|
|
8242
|
+
- Never call \`deskfree_create_task\` as your very first action \u2014 qualify first, even if briefly.
|
|
8275
8243
|
|
|
8276
8244
|
**Match the human's energy.** Short message \u2192 short reply. Casual tone \u2192 casual response. Don't over-explain, don't lecture, don't pad responses.
|
|
8277
8245
|
|
|
8278
|
-
In the main thread you
|
|
8246
|
+
In the main thread you create tasks and coordinate \u2014 the actual work happens in task threads. Use \`deskfree_dispatch_worker\` to start working on tasks.
|
|
8279
8247
|
- When a human writes in a task thread, decide:
|
|
8280
8248
|
- **Continuation of the same task?** \u2192 reopen and pick it back up.
|
|
8281
|
-
- **New/different work request?** \u2192
|
|
8249
|
+
- **New/different work request?** \u2192 create it as a new task (don't reopen the old one).
|
|
8282
8250
|
- **Just confirmation or deferred?** \u2192 leave it for now.
|
|
8283
8251
|
- Estimate token cost per task \u2014 consider files to read, reasoning, output.
|
|
8284
8252
|
|
|
@@ -8298,7 +8266,7 @@ Record immediately when: the human corrects you, expresses a preference, shares
|
|
|
8298
8266
|
DESKFREE_WORKER_DIRECTIVE = `## DeskFree \u2014 Task Thread
|
|
8299
8267
|
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.
|
|
8300
8268
|
|
|
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,
|
|
8269
|
+
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_create_task.
|
|
8302
8270
|
|
|
8303
8271
|
**Context loading:**
|
|
8304
8272
|
- If your first message contains \`<task_context>\`, the task is already loaded. Start working immediately \u2014 do NOT call deskfree_start_task.
|
|
@@ -8309,24 +8277,30 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
|
|
|
8309
8277
|
- If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to load it.
|
|
8310
8278
|
- If continuing from a previous conversation (you can see prior tool calls and context), respond directly to the human's latest message \u2014 do NOT call deskfree_start_task again.
|
|
8311
8279
|
|
|
8280
|
+
**Your text responses are automatically streamed to the human as messages.** You do NOT need to call \`deskfree_send_message\` to talk \u2014 just write your response text directly. Only use \`deskfree_send_message\` when you need to send a message mid-tool-execution (e.g. a progress update while doing file operations).
|
|
8281
|
+
|
|
8312
8282
|
**Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
|
|
8313
8283
|
|
|
8314
8284
|
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.
|
|
8315
|
-
2. **Align** \u2014
|
|
8285
|
+
2. **Align** \u2014 State briefly what you found and what you'll produce. One or two sentences. ("I'll build on the existing brand guide and create a new tone reference.") Just write this as your response text \u2014 do NOT use \`deskfree_send_message\`.
|
|
8316
8286
|
- **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
8287
|
- **Straightforward execution?** Proceed immediately \u2014 don't wait for a response.
|
|
8318
8288
|
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.
|
|
8319
|
-
4. **Deliver** \u2014 When work is ready for review,
|
|
8289
|
+
4. **Deliver** \u2014 When work is ready for review, set \`awaiting: 'human'\` via \`deskfree_update_task_status\`. The human will complete the task when satisfied.
|
|
8320
8290
|
|
|
8321
8291
|
**Push back when warranted:**
|
|
8322
8292
|
- 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.
|
|
8323
8293
|
- 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.
|
|
8324
8294
|
- You're a teammate, not a task executor. Have an opinion when you have the context to form one.
|
|
8325
8295
|
|
|
8296
|
+
**New requests mid-task:**
|
|
8297
|
+
- When the human asks for something new in your thread, don't make it awkward \u2014 just roll with it. Qualify naturally ("Sure \u2014 what kind of audit?"), get enough clarity to write a well-scoped task, then use \`deskfree_create_task\`. No meta-commentary about scope boundaries or "that's outside this task." Just be a good conversationalist who happens to spin up a new task.
|
|
8298
|
+
- Every task needs a concrete deliverable and bounded scope. "Audit the codebase" is not a task \u2014 "Review tRPC error handling and list inconsistencies" is. If the human's request is too vague, ask a narrowing question before creating a task.
|
|
8299
|
+
- If you notice follow-up work yourself while executing, create a task immediately \u2014 don't wait until completion.
|
|
8300
|
+
|
|
8326
8301
|
**File rules:**
|
|
8327
8302
|
- Create files when your task naturally produces them. Don't be afraid to create multiple files if the work calls for it.
|
|
8328
8303
|
- Always pass \`taskId\` when creating or updating files \u2014 this threads notifications into the task.
|
|
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.
|
|
8330
8304
|
|
|
8331
8305
|
**Learnings \u2014 record aggressively:**
|
|
8332
8306
|
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.
|
|
@@ -8666,14 +8640,22 @@ var init_orchestrator = __esm({
|
|
|
8666
8640
|
];
|
|
8667
8641
|
}
|
|
8668
8642
|
});
|
|
8669
|
-
function
|
|
8643
|
+
function getBaseDomain(stage) {
|
|
8644
|
+
return stage === "my" ? "deskfree.ai" : "dev.deskfree.ai";
|
|
8645
|
+
}
|
|
8646
|
+
function getStageHost(stage) {
|
|
8647
|
+
const domain = getBaseDomain(stage);
|
|
8670
8648
|
if (domain.startsWith(`${stage}.`)) return domain;
|
|
8671
8649
|
return `${stage}.${domain}`;
|
|
8672
8650
|
}
|
|
8673
8651
|
function deriveApiUrl() {
|
|
8674
|
-
const
|
|
8675
|
-
|
|
8676
|
-
|
|
8652
|
+
const stage = process.env["STAGE"] ?? "my";
|
|
8653
|
+
return `https://${getStageHost(stage)}/v1/bot`;
|
|
8654
|
+
}
|
|
8655
|
+
function deriveWsUrl() {
|
|
8656
|
+
const stage = process.env["STAGE"] ?? "my";
|
|
8657
|
+
const domain = getBaseDomain(stage);
|
|
8658
|
+
return `wss://ws.${domain}`;
|
|
8677
8659
|
}
|
|
8678
8660
|
function loadConfig() {
|
|
8679
8661
|
const botId = process.env["BOT"] ?? process.env["DESKFREE_BOT_ID"];
|
|
@@ -8686,7 +8668,7 @@ function loadConfig() {
|
|
|
8686
8668
|
if (idError !== null) {
|
|
8687
8669
|
throw new Error(`Invalid bot ID: ${idError}`);
|
|
8688
8670
|
}
|
|
8689
|
-
const apiUrl =
|
|
8671
|
+
const apiUrl = deriveApiUrl();
|
|
8690
8672
|
const apiUrlError = validateApiUrl(apiUrl);
|
|
8691
8673
|
if (apiUrlError !== null) {
|
|
8692
8674
|
throw new Error(`Invalid API URL: ${apiUrlError}`);
|
|
@@ -8701,9 +8683,13 @@ function loadConfig() {
|
|
|
8701
8683
|
if (isNaN(healthPort) || healthPort < 1 || healthPort > 65535) {
|
|
8702
8684
|
throw new Error(`Invalid HEALTH_PORT: ${healthPortRaw}`);
|
|
8703
8685
|
}
|
|
8686
|
+
const stage = process.env["STAGE"] ?? "my";
|
|
8687
|
+
const wsUrl = deriveWsUrl();
|
|
8704
8688
|
return {
|
|
8705
8689
|
botId,
|
|
8706
8690
|
apiUrl,
|
|
8691
|
+
stage,
|
|
8692
|
+
wsUrl,
|
|
8707
8693
|
stateDir: process.env["DESKFREE_STATE_DIR"] ?? DEFAULTS.stateDir,
|
|
8708
8694
|
toolsDir: process.env["DESKFREE_TOOLS_DIR"] ?? DEFAULTS.toolsDir,
|
|
8709
8695
|
logLevel,
|
|
@@ -8726,7 +8712,7 @@ function mergeWithRemoteConfig(local, remote) {
|
|
|
8726
8712
|
return {
|
|
8727
8713
|
...local,
|
|
8728
8714
|
claudeCodePath,
|
|
8729
|
-
wsUrl:
|
|
8715
|
+
wsUrl: remote.wsUrl ?? local.wsUrl,
|
|
8730
8716
|
model: process.env["DESKFREE_MODEL"] ?? remote.model,
|
|
8731
8717
|
awsRegion: process.env["AWS_REGION"] ?? remote.awsRegion,
|
|
8732
8718
|
heartbeatIntervalMs: process.env["DESKFREE_HEARTBEAT_INTERVAL_MS"] ? parseInt(process.env["DESKFREE_HEARTBEAT_INTERVAL_MS"], 10) : remote.heartbeatIntervalMs,
|
|
@@ -12579,14 +12565,23 @@ var require_websocket_server2 = __commonJS({
|
|
|
12579
12565
|
});
|
|
12580
12566
|
|
|
12581
12567
|
// ../../node_modules/ws/wrapper.mjs
|
|
12582
|
-
var
|
|
12568
|
+
var wrapper_exports = {};
|
|
12569
|
+
__export(wrapper_exports, {
|
|
12570
|
+
Receiver: () => import_receiver2.default,
|
|
12571
|
+
Sender: () => import_sender2.default,
|
|
12572
|
+
WebSocket: () => import_websocket2.default,
|
|
12573
|
+
WebSocketServer: () => import_websocket_server2.default,
|
|
12574
|
+
createWebSocketStream: () => import_stream2.default,
|
|
12575
|
+
default: () => wrapper_default2
|
|
12576
|
+
});
|
|
12577
|
+
var import_stream2, import_receiver2, import_sender2, import_websocket2, import_websocket_server2, wrapper_default2;
|
|
12583
12578
|
var init_wrapper = __esm({
|
|
12584
12579
|
"../../node_modules/ws/wrapper.mjs"() {
|
|
12585
|
-
__toESM(require_stream2());
|
|
12586
|
-
__toESM(require_receiver2());
|
|
12587
|
-
__toESM(require_sender2());
|
|
12580
|
+
import_stream2 = __toESM(require_stream2());
|
|
12581
|
+
import_receiver2 = __toESM(require_receiver2());
|
|
12582
|
+
import_sender2 = __toESM(require_sender2());
|
|
12588
12583
|
import_websocket2 = __toESM(require_websocket2());
|
|
12589
|
-
__toESM(require_websocket_server2());
|
|
12584
|
+
import_websocket_server2 = __toESM(require_websocket_server2());
|
|
12590
12585
|
wrapper_default2 = import_websocket2.default;
|
|
12591
12586
|
}
|
|
12592
12587
|
});
|
|
@@ -12600,9 +12595,7 @@ __export(ws_gateway_exports, {
|
|
|
12600
12595
|
});
|
|
12601
12596
|
function getRotationToken() {
|
|
12602
12597
|
if (!currentRotationToken) {
|
|
12603
|
-
throw new Error(
|
|
12604
|
-
"No rotation token available \u2014 bots.connect not yet called"
|
|
12605
|
-
);
|
|
12598
|
+
throw new Error("No rotation token available \u2014 WS init not yet completed");
|
|
12606
12599
|
}
|
|
12607
12600
|
return currentRotationToken;
|
|
12608
12601
|
}
|
|
@@ -12634,54 +12627,88 @@ function sleepWithAbort(ms, signal) {
|
|
|
12634
12627
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
12635
12628
|
});
|
|
12636
12629
|
}
|
|
12637
|
-
async function
|
|
12638
|
-
const { botId,
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
}
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
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
|
|
12630
|
+
async function wsReconnect(config, rotationToken) {
|
|
12631
|
+
const { botId, stage, wsUrl, log, abortSignal } = config;
|
|
12632
|
+
const params = new URLSearchParams({ id: botId, stage });
|
|
12633
|
+
if (rotationToken) {
|
|
12634
|
+
params.set("token", rotationToken);
|
|
12635
|
+
}
|
|
12636
|
+
const fullUrl = `${wsUrl}?${params.toString()}`;
|
|
12637
|
+
return new Promise(
|
|
12638
|
+
(resolve, reject) => {
|
|
12639
|
+
const ws = new wrapper_default2(fullUrl);
|
|
12640
|
+
let settled = false;
|
|
12641
|
+
let timeoutTimer;
|
|
12642
|
+
const cleanup = () => {
|
|
12643
|
+
if (timeoutTimer !== void 0) {
|
|
12644
|
+
clearTimeout(timeoutTimer);
|
|
12645
|
+
timeoutTimer = void 0;
|
|
12646
|
+
}
|
|
12670
12647
|
};
|
|
12671
|
-
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12648
|
+
const fail = (err) => {
|
|
12649
|
+
if (settled) return;
|
|
12650
|
+
settled = true;
|
|
12651
|
+
cleanup();
|
|
12652
|
+
try {
|
|
12653
|
+
ws.close();
|
|
12654
|
+
} catch {
|
|
12655
|
+
}
|
|
12656
|
+
reject(err);
|
|
12657
|
+
};
|
|
12658
|
+
timeoutTimer = setTimeout(() => {
|
|
12659
|
+
fail(
|
|
12660
|
+
new Error(
|
|
12661
|
+
`WS reconnect timeout after ${WS_RECONNECT_INIT_TIMEOUT_MS}ms`
|
|
12662
|
+
)
|
|
12663
|
+
);
|
|
12664
|
+
}, WS_RECONNECT_INIT_TIMEOUT_MS);
|
|
12665
|
+
ws.on("open", () => {
|
|
12666
|
+
ws.send(JSON.stringify({ action: "init" }));
|
|
12667
|
+
});
|
|
12668
|
+
ws.on("message", (data) => {
|
|
12669
|
+
try {
|
|
12670
|
+
const msg = JSON.parse(data.toString());
|
|
12671
|
+
if (msg.action === "go" && msg.rotationToken) {
|
|
12672
|
+
if (settled) return;
|
|
12673
|
+
settled = true;
|
|
12674
|
+
cleanup();
|
|
12675
|
+
resolve({ ws, rotationToken: msg.rotationToken });
|
|
12676
|
+
} else if (msg.action === "rejected") {
|
|
12677
|
+
fail(
|
|
12678
|
+
new Error(
|
|
12679
|
+
`Reconnect rejected: ${msg.reason ?? "unknown reason"}`
|
|
12680
|
+
)
|
|
12681
|
+
);
|
|
12682
|
+
} else if (msg.action === "lobby") {
|
|
12683
|
+
log.info("Reconnect landed in lobby \u2014 awaiting approval...");
|
|
12684
|
+
cleanup();
|
|
12685
|
+
}
|
|
12686
|
+
} catch (err) {
|
|
12687
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
12688
|
+
log.warn(`Error parsing reconnect response: ${errMsg}`);
|
|
12689
|
+
}
|
|
12690
|
+
});
|
|
12691
|
+
ws.on("error", (err) => {
|
|
12692
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
12693
|
+
});
|
|
12694
|
+
ws.on("close", (code, reason) => {
|
|
12695
|
+
if (!settled) {
|
|
12696
|
+
fail(
|
|
12697
|
+
new Error(
|
|
12698
|
+
`WebSocket closed during reconnect: ${code} ${reason.toString()}`
|
|
12699
|
+
)
|
|
12700
|
+
);
|
|
12701
|
+
}
|
|
12702
|
+
});
|
|
12703
|
+
abortSignal.addEventListener(
|
|
12704
|
+
"abort",
|
|
12705
|
+
() => {
|
|
12706
|
+
fail(new Error("Aborted during WS reconnect"));
|
|
12707
|
+
},
|
|
12708
|
+
{ once: true }
|
|
12681
12709
|
);
|
|
12682
12710
|
}
|
|
12683
|
-
|
|
12684
|
-
}
|
|
12711
|
+
);
|
|
12685
12712
|
}
|
|
12686
12713
|
async function startGateway(config) {
|
|
12687
12714
|
const { client, accountId, stateDir, log, abortSignal } = config;
|
|
@@ -12699,33 +12726,24 @@ async function startGateway(config) {
|
|
|
12699
12726
|
});
|
|
12700
12727
|
while (!abortSignal.aborted) {
|
|
12701
12728
|
try {
|
|
12702
|
-
let
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12729
|
+
let ws;
|
|
12730
|
+
if (totalReconnects === 0) {
|
|
12731
|
+
ws = config.initialWs;
|
|
12732
|
+
currentRotationToken = config.initialRotationToken;
|
|
12733
|
+
log.info("Using initial WS connection from handshake.");
|
|
12707
12734
|
} 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
|
-
}
|
|
12716
|
-
resetBackoff(backoff);
|
|
12717
|
-
if (totalReconnects > 0) {
|
|
12718
12735
|
log.info(
|
|
12719
|
-
`
|
|
12736
|
+
`Reconnecting to ${config.wsUrl}... (reconnect #${totalReconnects})`
|
|
12720
12737
|
);
|
|
12738
|
+
const result = await wsReconnect(config, currentRotationToken ?? "");
|
|
12739
|
+
ws = result.ws;
|
|
12740
|
+
currentRotationToken = result.rotationToken;
|
|
12721
12741
|
recordReconnect(accountId);
|
|
12722
|
-
} else {
|
|
12723
|
-
log.info(`Got WS ticket, connecting to ${wsUrl}...`);
|
|
12724
12742
|
}
|
|
12743
|
+
resetBackoff(backoff);
|
|
12725
12744
|
updateHealthMode(accountId, "websocket");
|
|
12726
12745
|
cursor = await runWebSocketConnection({
|
|
12727
|
-
|
|
12728
|
-
wsUrl,
|
|
12746
|
+
ws,
|
|
12729
12747
|
client,
|
|
12730
12748
|
accountId,
|
|
12731
12749
|
stateDir,
|
|
@@ -12734,33 +12752,22 @@ async function startGateway(config) {
|
|
|
12734
12752
|
abortSignal,
|
|
12735
12753
|
onMessage: config.onMessage,
|
|
12736
12754
|
getWorkerStatus: config.getWorkerStatus,
|
|
12737
|
-
onConsolidate: config.onConsolidate
|
|
12738
|
-
isV2: true
|
|
12755
|
+
onConsolidate: config.onConsolidate
|
|
12739
12756
|
});
|
|
12740
12757
|
totalReconnects++;
|
|
12741
12758
|
} catch (err) {
|
|
12742
12759
|
totalReconnects++;
|
|
12743
12760
|
const message = err instanceof Error ? err.message : String(err);
|
|
12744
|
-
|
|
12761
|
+
const isConnectFailure = message.includes("WS-first connect") || message.includes("WS reconnect") || message.includes("Connection rejected") || message.includes("Reconnect rejected");
|
|
12762
|
+
if (isConnectFailure) {
|
|
12745
12763
|
log.warn(
|
|
12746
|
-
`Connection setup failed (attempt #${totalReconnects}): ${message}.
|
|
12764
|
+
`Connection setup failed (attempt #${totalReconnects}): ${message}. Will retry after backoff.`
|
|
12747
12765
|
);
|
|
12748
12766
|
reportError("error", `Connection setup failed: ${message}`, {
|
|
12749
12767
|
component: "gateway",
|
|
12750
|
-
event: "
|
|
12768
|
+
event: "ws_connect_failed",
|
|
12751
12769
|
attempt: totalReconnects
|
|
12752
12770
|
});
|
|
12753
|
-
recordReconnect(accountId);
|
|
12754
|
-
updateHealthMode(accountId, "polling");
|
|
12755
|
-
cursor = await runPollingFallback({
|
|
12756
|
-
client,
|
|
12757
|
-
accountId,
|
|
12758
|
-
stateDir,
|
|
12759
|
-
cursor,
|
|
12760
|
-
log,
|
|
12761
|
-
abortSignal,
|
|
12762
|
-
onMessage: config.onMessage
|
|
12763
|
-
});
|
|
12764
12771
|
} else {
|
|
12765
12772
|
log.warn(`Connection error (attempt #${totalReconnects}): ${message}`);
|
|
12766
12773
|
reportError("warn", `WS connection error: ${message}`, {
|
|
@@ -12768,8 +12775,8 @@ async function startGateway(config) {
|
|
|
12768
12775
|
event: "connection_error",
|
|
12769
12776
|
attempt: totalReconnects
|
|
12770
12777
|
});
|
|
12771
|
-
recordReconnect(accountId);
|
|
12772
12778
|
}
|
|
12779
|
+
recordReconnect(accountId);
|
|
12773
12780
|
if (abortSignal.aborted) break;
|
|
12774
12781
|
const delay = nextBackoff(backoff);
|
|
12775
12782
|
log.info(
|
|
@@ -12781,26 +12788,20 @@ async function startGateway(config) {
|
|
|
12781
12788
|
log.info(`Gateway loop exited after ${totalReconnects} reconnect(s).`);
|
|
12782
12789
|
}
|
|
12783
12790
|
async function runWebSocketConnection(opts) {
|
|
12784
|
-
const {
|
|
12791
|
+
const { client, accountId, stateDir, log, abortSignal } = opts;
|
|
12792
|
+
const ws = opts.ws;
|
|
12785
12793
|
const ctx = { accountId };
|
|
12786
12794
|
let cursor = opts.cursor;
|
|
12787
12795
|
return new Promise((resolve, reject) => {
|
|
12788
|
-
const ws = new wrapper_default2(`${wsUrl}?ticket=${ticket}`);
|
|
12789
12796
|
let pingInterval;
|
|
12790
|
-
let connectionTimer;
|
|
12791
12797
|
let pongTimer;
|
|
12792
12798
|
let notifyDebounceTimer;
|
|
12793
12799
|
let proactiveReconnectTimer;
|
|
12794
|
-
let isConnected = false;
|
|
12795
12800
|
const cleanup = () => {
|
|
12796
12801
|
if (pingInterval !== void 0) {
|
|
12797
12802
|
clearInterval(pingInterval);
|
|
12798
12803
|
pingInterval = void 0;
|
|
12799
12804
|
}
|
|
12800
|
-
if (connectionTimer !== void 0) {
|
|
12801
|
-
clearTimeout(connectionTimer);
|
|
12802
|
-
connectionTimer = void 0;
|
|
12803
|
-
}
|
|
12804
12805
|
if (pongTimer !== void 0) {
|
|
12805
12806
|
clearTimeout(pongTimer);
|
|
12806
12807
|
pongTimer = void 0;
|
|
@@ -12814,88 +12815,67 @@ async function runWebSocketConnection(opts) {
|
|
|
12814
12815
|
proactiveReconnectTimer = void 0;
|
|
12815
12816
|
}
|
|
12816
12817
|
};
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12818
|
+
setActiveWs(ws);
|
|
12819
|
+
log.info("WebSocket session started.");
|
|
12820
|
+
setWsConnected(true);
|
|
12821
|
+
setHealthMode("websocket");
|
|
12822
|
+
pingInterval = setInterval(() => {
|
|
12823
|
+
if (ws.readyState === wrapper_default2.OPEN) {
|
|
12820
12824
|
try {
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
reject(
|
|
12825
|
-
new Error(
|
|
12826
|
-
`WebSocket connection timeout after ${WS_CONNECTION_TIMEOUT_MS}ms`
|
|
12827
|
-
)
|
|
12828
|
-
);
|
|
12829
|
-
}
|
|
12830
|
-
}, WS_CONNECTION_TIMEOUT_MS);
|
|
12831
|
-
ws.on("open", () => {
|
|
12832
|
-
isConnected = true;
|
|
12833
|
-
setActiveWs(ws);
|
|
12834
|
-
if (connectionTimer !== void 0) {
|
|
12835
|
-
clearTimeout(connectionTimer);
|
|
12836
|
-
connectionTimer = void 0;
|
|
12837
|
-
}
|
|
12838
|
-
log.info("WebSocket connected.");
|
|
12839
|
-
setWsConnected(true);
|
|
12840
|
-
setHealthMode("websocket");
|
|
12841
|
-
pingInterval = setInterval(() => {
|
|
12842
|
-
if (ws.readyState === wrapper_default2.OPEN) {
|
|
12843
|
-
try {
|
|
12844
|
-
const pingPayload = { action: "ping" };
|
|
12845
|
-
if (currentRotationToken) {
|
|
12846
|
-
pingPayload.token = currentRotationToken;
|
|
12847
|
-
}
|
|
12848
|
-
ws.send(JSON.stringify(pingPayload));
|
|
12849
|
-
pongTimer = setTimeout(() => {
|
|
12850
|
-
log.warn("Pong timeout \u2014 closing WebSocket");
|
|
12851
|
-
try {
|
|
12852
|
-
ws.close(1002, "pong timeout");
|
|
12853
|
-
} catch {
|
|
12854
|
-
}
|
|
12855
|
-
}, WS_PONG_TIMEOUT_MS);
|
|
12856
|
-
} catch (err) {
|
|
12857
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
12858
|
-
log.warn(`Failed to send ping: ${msg}`);
|
|
12825
|
+
const pingPayload = { action: "ping" };
|
|
12826
|
+
if (currentRotationToken) {
|
|
12827
|
+
pingPayload.token = currentRotationToken;
|
|
12859
12828
|
}
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
}, PROACTIVE_RECONNECT_MS);
|
|
12869
|
-
if (opts.getWorkerStatus) {
|
|
12870
|
-
try {
|
|
12871
|
-
const status2 = opts.getWorkerStatus();
|
|
12872
|
-
ws.send(
|
|
12873
|
-
JSON.stringify({
|
|
12874
|
-
action: "heartbeatResponse",
|
|
12875
|
-
...status2
|
|
12876
|
-
})
|
|
12877
|
-
);
|
|
12829
|
+
ws.send(JSON.stringify(pingPayload));
|
|
12830
|
+
pongTimer = setTimeout(() => {
|
|
12831
|
+
log.warn("Pong timeout \u2014 closing WebSocket");
|
|
12832
|
+
try {
|
|
12833
|
+
ws.close(1002, "pong timeout");
|
|
12834
|
+
} catch {
|
|
12835
|
+
}
|
|
12836
|
+
}, WS_PONG_TIMEOUT_MS);
|
|
12878
12837
|
} catch (err) {
|
|
12879
|
-
const
|
|
12880
|
-
log.warn(`Failed to send
|
|
12838
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12839
|
+
log.warn(`Failed to send ping: ${msg}`);
|
|
12881
12840
|
}
|
|
12882
12841
|
}
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12842
|
+
}, PING_INTERVAL_MS);
|
|
12843
|
+
proactiveReconnectTimer = setTimeout(() => {
|
|
12844
|
+
log.info("Proactive reconnect at 1h50m \u2014 closing for reconnect");
|
|
12845
|
+
try {
|
|
12846
|
+
ws.close(1e3, "proactive_reconnect");
|
|
12847
|
+
} catch {
|
|
12848
|
+
}
|
|
12849
|
+
}, PROACTIVE_RECONNECT_MS);
|
|
12850
|
+
if (opts.getWorkerStatus) {
|
|
12851
|
+
try {
|
|
12852
|
+
const status2 = opts.getWorkerStatus();
|
|
12853
|
+
ws.send(
|
|
12854
|
+
JSON.stringify({
|
|
12855
|
+
action: "heartbeatResponse",
|
|
12856
|
+
...status2
|
|
12857
|
+
})
|
|
12858
|
+
);
|
|
12859
|
+
} catch (err) {
|
|
12860
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
12861
|
+
log.warn(`Failed to send initial heartbeat: ${errMsg}`);
|
|
12862
|
+
}
|
|
12863
|
+
}
|
|
12864
|
+
void pollAndDeliver(
|
|
12865
|
+
client,
|
|
12866
|
+
accountId,
|
|
12867
|
+
stateDir,
|
|
12868
|
+
cursor,
|
|
12869
|
+
opts.onMessage,
|
|
12870
|
+
log
|
|
12871
|
+
).then((result) => {
|
|
12872
|
+
if (result.cursor) {
|
|
12873
|
+
cursor = result.cursor;
|
|
12874
|
+
saveCursor(ctx, cursor, stateDir, log);
|
|
12875
|
+
}
|
|
12876
|
+
}).catch((err) => {
|
|
12877
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12878
|
+
log.warn(`Initial poll failed: ${msg}`);
|
|
12899
12879
|
});
|
|
12900
12880
|
ws.on("message", (data) => {
|
|
12901
12881
|
try {
|
|
@@ -12943,9 +12923,7 @@ async function runWebSocketConnection(opts) {
|
|
|
12943
12923
|
currentRotationToken = pongMsg.rotationToken;
|
|
12944
12924
|
log.debug("Received pong \u2014 token rotated");
|
|
12945
12925
|
} else if (pongMsg.error === "rotation_invalid") {
|
|
12946
|
-
log.warn(
|
|
12947
|
-
"Rotation token invalid \u2014 closing for reconnect via bots.connect"
|
|
12948
|
-
);
|
|
12926
|
+
log.warn("Rotation token invalid \u2014 closing for reconnect");
|
|
12949
12927
|
try {
|
|
12950
12928
|
ws.close(1e3, "rotation_invalid");
|
|
12951
12929
|
} catch {
|
|
@@ -13054,7 +13032,6 @@ async function runWebSocketConnection(opts) {
|
|
|
13054
13032
|
});
|
|
13055
13033
|
ws.on("close", (code, reason) => {
|
|
13056
13034
|
cleanup();
|
|
13057
|
-
isConnected = false;
|
|
13058
13035
|
setActiveWs(null);
|
|
13059
13036
|
setWsConnected(false);
|
|
13060
13037
|
if (code === 1e3 || code === 1001) {
|
|
@@ -13088,7 +13065,6 @@ async function runWebSocketConnection(opts) {
|
|
|
13088
13065
|
});
|
|
13089
13066
|
ws.on("error", (err) => {
|
|
13090
13067
|
cleanup();
|
|
13091
|
-
isConnected = false;
|
|
13092
13068
|
setActiveWs(null);
|
|
13093
13069
|
setWsConnected(false);
|
|
13094
13070
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -13117,50 +13093,7 @@ async function runWebSocketConnection(opts) {
|
|
|
13117
13093
|
);
|
|
13118
13094
|
});
|
|
13119
13095
|
}
|
|
13120
|
-
|
|
13121
|
-
const { client, accountId, stateDir, log, abortSignal } = opts;
|
|
13122
|
-
let cursor = opts.cursor;
|
|
13123
|
-
let iterations = 0;
|
|
13124
|
-
let consecutiveFailures = 0;
|
|
13125
|
-
log.info("Running in polling fallback mode.");
|
|
13126
|
-
setHealthMode("polling");
|
|
13127
|
-
while (!abortSignal.aborted && iterations < MAX_POLLING_ITERATIONS) {
|
|
13128
|
-
const result = await pollAndDeliver(
|
|
13129
|
-
client,
|
|
13130
|
-
accountId,
|
|
13131
|
-
stateDir,
|
|
13132
|
-
cursor,
|
|
13133
|
-
opts.onMessage,
|
|
13134
|
-
log
|
|
13135
|
-
);
|
|
13136
|
-
if (result.ok) {
|
|
13137
|
-
if (result.cursor) cursor = result.cursor;
|
|
13138
|
-
consecutiveFailures = 0;
|
|
13139
|
-
} else {
|
|
13140
|
-
consecutiveFailures++;
|
|
13141
|
-
if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
|
|
13142
|
-
log.warn(
|
|
13143
|
-
`${consecutiveFailures} consecutive poll failures, breaking to retry WebSocket`
|
|
13144
|
-
);
|
|
13145
|
-
reportError(
|
|
13146
|
-
"error",
|
|
13147
|
-
`${consecutiveFailures} consecutive poll failures, switching to WS retry`,
|
|
13148
|
-
{
|
|
13149
|
-
component: "gateway",
|
|
13150
|
-
event: "poll_max_failures",
|
|
13151
|
-
consecutiveFailures
|
|
13152
|
-
}
|
|
13153
|
-
);
|
|
13154
|
-
break;
|
|
13155
|
-
}
|
|
13156
|
-
}
|
|
13157
|
-
iterations++;
|
|
13158
|
-
const jitter = Math.random() * POLL_FALLBACK_INTERVAL_MS * 0.2;
|
|
13159
|
-
await sleepWithAbort(POLL_FALLBACK_INTERVAL_MS + jitter, abortSignal);
|
|
13160
|
-
}
|
|
13161
|
-
return cursor;
|
|
13162
|
-
}
|
|
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;
|
|
13096
|
+
var PING_INTERVAL_MS, WS_PONG_TIMEOUT_MS, NOTIFY_DEBOUNCE_MS, HEALTH_LOG_INTERVAL_MS, PROACTIVE_RECONNECT_MS, WS_RECONNECT_INIT_TIMEOUT_MS, BACKOFF_INITIAL_MS, BACKOFF_MAX_MS, BACKOFF_FACTOR, currentRotationToken;
|
|
13164
13097
|
var init_ws_gateway = __esm({
|
|
13165
13098
|
"src/gateway/ws-gateway.ts"() {
|
|
13166
13099
|
init_health_state();
|
|
@@ -13169,15 +13102,11 @@ var init_ws_gateway = __esm({
|
|
|
13169
13102
|
init_dist();
|
|
13170
13103
|
init_wrapper();
|
|
13171
13104
|
PING_INTERVAL_MS = 5 * 60 * 1e3;
|
|
13172
|
-
POLL_FALLBACK_INTERVAL_MS = 3e4;
|
|
13173
|
-
WS_CONNECTION_TIMEOUT_MS = 3e4;
|
|
13174
13105
|
WS_PONG_TIMEOUT_MS = 1e4;
|
|
13175
13106
|
NOTIFY_DEBOUNCE_MS = 200;
|
|
13176
13107
|
HEALTH_LOG_INTERVAL_MS = 30 * 60 * 1e3;
|
|
13177
|
-
MAX_POLLING_ITERATIONS = 10;
|
|
13178
|
-
MAX_CONSECUTIVE_POLL_FAILURES = 5;
|
|
13179
|
-
CONNECT_POLL_INTERVAL_MS = 3e4;
|
|
13180
13108
|
PROACTIVE_RECONNECT_MS = 110 * 60 * 1e3;
|
|
13109
|
+
WS_RECONNECT_INIT_TIMEOUT_MS = 3e4;
|
|
13181
13110
|
BACKOFF_INITIAL_MS = 2e3;
|
|
13182
13111
|
BACKOFF_MAX_MS = 3e4;
|
|
13183
13112
|
BACKOFF_FACTOR = 1.8;
|
|
@@ -14725,6 +14654,17 @@ ${userMessage}
|
|
|
14725
14654
|
} catch {
|
|
14726
14655
|
}
|
|
14727
14656
|
} finally {
|
|
14657
|
+
try {
|
|
14658
|
+
const task = await client.getTask({ taskId });
|
|
14659
|
+
if (task.status === "open" && task.awaiting === "bot") {
|
|
14660
|
+
log.info(
|
|
14661
|
+
`Worker finished but task ${taskId} still awaiting bot \u2014 auto-flipping to human`
|
|
14662
|
+
);
|
|
14663
|
+
await client.updateTaskStatus({ taskId, awaiting: "human" });
|
|
14664
|
+
}
|
|
14665
|
+
} catch {
|
|
14666
|
+
log.warn(`Failed to check/flip awaiting state for task ${taskId}`);
|
|
14667
|
+
}
|
|
14728
14668
|
const handle = this.workers.get(taskId);
|
|
14729
14669
|
if (handle) {
|
|
14730
14670
|
clearTimeout(handle.idleTimer);
|
|
@@ -14909,49 +14849,82 @@ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, cla
|
|
|
14909
14849
|
log.info(`Heartbeat scheduled every ${Math.round(intervalMs / 1e3)}s`);
|
|
14910
14850
|
}
|
|
14911
14851
|
async function initialConnect(opts) {
|
|
14912
|
-
const { botId,
|
|
14913
|
-
|
|
14914
|
-
|
|
14915
|
-
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14852
|
+
const { botId, stage, wsUrl, fingerprint, log, abortSignal } = opts;
|
|
14853
|
+
const WebSocket2 = (await Promise.resolve().then(() => (init_wrapper(), wrapper_exports))).default;
|
|
14854
|
+
const params = new URLSearchParams({ id: botId, stage });
|
|
14855
|
+
const fullUrl = `${wsUrl}?${params.toString()}`;
|
|
14856
|
+
return new Promise((resolve, reject) => {
|
|
14857
|
+
const ws = new WebSocket2(fullUrl);
|
|
14858
|
+
let settled = false;
|
|
14859
|
+
let timeoutTimer;
|
|
14860
|
+
const cleanup = () => {
|
|
14861
|
+
if (timeoutTimer !== void 0) {
|
|
14862
|
+
clearTimeout(timeoutTimer);
|
|
14863
|
+
timeoutTimer = void 0;
|
|
14864
|
+
}
|
|
14920
14865
|
};
|
|
14921
|
-
const
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14866
|
+
const fail = (err) => {
|
|
14867
|
+
if (settled) return;
|
|
14868
|
+
settled = true;
|
|
14869
|
+
cleanup();
|
|
14870
|
+
try {
|
|
14871
|
+
ws.close();
|
|
14872
|
+
} catch {
|
|
14873
|
+
}
|
|
14874
|
+
reject(err);
|
|
14875
|
+
};
|
|
14876
|
+
timeoutTimer = setTimeout(() => {
|
|
14877
|
+
fail(new Error(`WS-first connect timeout after ${WS_INIT_TIMEOUT_MS}ms`));
|
|
14878
|
+
}, WS_INIT_TIMEOUT_MS);
|
|
14879
|
+
ws.on("open", () => {
|
|
14880
|
+
const initMsg = { action: "init" };
|
|
14881
|
+
{
|
|
14882
|
+
initMsg.fingerprint = fingerprint;
|
|
14883
|
+
}
|
|
14884
|
+
ws.send(JSON.stringify(initMsg));
|
|
14925
14885
|
});
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
}
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14886
|
+
ws.on("message", (data) => {
|
|
14887
|
+
try {
|
|
14888
|
+
const msg = JSON.parse(data.toString());
|
|
14889
|
+
if (msg.action === "go" && msg.rotationToken) {
|
|
14890
|
+
if (settled) return;
|
|
14891
|
+
settled = true;
|
|
14892
|
+
cleanup();
|
|
14893
|
+
ws.removeAllListeners();
|
|
14894
|
+
resolve({ ws, rotationToken: msg.rotationToken });
|
|
14895
|
+
} else if (msg.action === "lobby") {
|
|
14896
|
+
log.info("Awaiting human approval...");
|
|
14897
|
+
cleanup();
|
|
14898
|
+
} else if (msg.action === "rejected") {
|
|
14899
|
+
fail(
|
|
14900
|
+
new Error(`Connection rejected: ${msg.reason ?? "unknown reason"}`)
|
|
14901
|
+
);
|
|
14902
|
+
}
|
|
14903
|
+
} catch (err) {
|
|
14904
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
14905
|
+
log.warn(`Error parsing init response: ${errMsg}`);
|
|
14906
|
+
}
|
|
14907
|
+
});
|
|
14908
|
+
ws.on("error", (err) => {
|
|
14909
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
14910
|
+
});
|
|
14911
|
+
ws.on("close", (code, reason) => {
|
|
14912
|
+
if (!settled) {
|
|
14913
|
+
fail(
|
|
14914
|
+
new Error(
|
|
14915
|
+
`WebSocket closed during init: ${code} ${reason.toString()}`
|
|
14916
|
+
)
|
|
14917
|
+
);
|
|
14918
|
+
}
|
|
14919
|
+
});
|
|
14920
|
+
abortSignal.addEventListener(
|
|
14921
|
+
"abort",
|
|
14922
|
+
() => {
|
|
14923
|
+
fail(new Error("Aborted during WS-first connect"));
|
|
14924
|
+
},
|
|
14925
|
+
{ once: true }
|
|
14926
|
+
);
|
|
14927
|
+
});
|
|
14955
14928
|
}
|
|
14956
14929
|
async function startAgent(opts) {
|
|
14957
14930
|
const localConfig = loadConfig();
|
|
@@ -14960,19 +14933,22 @@ async function startAgent(opts) {
|
|
|
14960
14933
|
log.info("DeskFree Agent Runtime starting...");
|
|
14961
14934
|
const { getRotationToken: getRotationToken2, setInitialRotationToken: setInitialRotationToken2 } = await Promise.resolve().then(() => (init_ws_gateway(), ws_gateway_exports));
|
|
14962
14935
|
const { collectFingerprint: collectFingerprint2 } = await Promise.resolve().then(() => (init_fingerprint(), fingerprint_exports));
|
|
14963
|
-
const
|
|
14964
|
-
const
|
|
14936
|
+
const { createRequire } = await import('module');
|
|
14937
|
+
const require3 = createRequire(import.meta.url);
|
|
14938
|
+
const runtimePkg = require3("../../package.json");
|
|
14939
|
+
const runtimeVersion = runtimePkg.version;
|
|
14965
14940
|
const fingerprint = collectFingerprint2(localConfig.stateDir, runtimeVersion);
|
|
14966
|
-
log.info("Connecting to DeskFree...", {
|
|
14941
|
+
log.info("Connecting to DeskFree...", { wsUrl: localConfig.wsUrl });
|
|
14967
14942
|
const connectResult = await initialConnect({
|
|
14968
14943
|
botId: localConfig.botId,
|
|
14969
|
-
|
|
14944
|
+
stage: localConfig.stage,
|
|
14945
|
+
wsUrl: localConfig.wsUrl,
|
|
14970
14946
|
fingerprint,
|
|
14971
14947
|
log,
|
|
14972
14948
|
abortSignal: abortController.signal
|
|
14973
14949
|
});
|
|
14974
14950
|
setInitialRotationToken2(connectResult.rotationToken);
|
|
14975
|
-
log.info("Connected \u2014 got rotation token
|
|
14951
|
+
log.info("Connected \u2014 got rotation token via WS handshake.");
|
|
14976
14952
|
const client = new DeskFreeClient({
|
|
14977
14953
|
apiUrl: localConfig.apiUrl,
|
|
14978
14954
|
getToken: () => getRotationToken2()
|
|
@@ -15067,7 +15043,7 @@ async function startAgent(opts) {
|
|
|
15067
15043
|
const createOrchServer = () => createOrchestratorMcpServer(client, customTools, workerManager);
|
|
15068
15044
|
const healthServer = startHealthServer(config.healthPort, log);
|
|
15069
15045
|
const sessionStore = new SessionStore();
|
|
15070
|
-
log.info("Starting gateway", { wsUrl: config.wsUrl
|
|
15046
|
+
log.info("Starting gateway", { wsUrl: config.wsUrl });
|
|
15071
15047
|
void startGateway({
|
|
15072
15048
|
client,
|
|
15073
15049
|
wsUrl: config.wsUrl,
|
|
@@ -15076,12 +15052,9 @@ async function startAgent(opts) {
|
|
|
15076
15052
|
log,
|
|
15077
15053
|
abortSignal: abortController.signal,
|
|
15078
15054
|
botId: localConfig.botId,
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
ticket: connectResult.ticket,
|
|
15083
|
-
wsUrl: connectResult.wsUrl
|
|
15084
|
-
},
|
|
15055
|
+
stage: localConfig.stage,
|
|
15056
|
+
initialWs: connectResult.ws,
|
|
15057
|
+
initialRotationToken: connectResult.rotationToken,
|
|
15085
15058
|
getWorkerStatus: () => ({
|
|
15086
15059
|
activeWorkers: workerManager.activeCount,
|
|
15087
15060
|
queuedTasks: workerManager.queuedCount,
|
|
@@ -15200,6 +15173,11 @@ async function startAgent(opts) {
|
|
|
15200
15173
|
log.info(
|
|
15201
15174
|
`Sleep cycle: consolidation applied (${consolidateResult.opsApplied} ops, ${consolidateResult.entriesArchived} archived)`
|
|
15202
15175
|
);
|
|
15176
|
+
if (consolidateResult.validationErrors?.length > 0) {
|
|
15177
|
+
log.warn(
|
|
15178
|
+
`Sleep cycle: validation errors: ${consolidateResult.validationErrors.join("; ")}`
|
|
15179
|
+
);
|
|
15180
|
+
}
|
|
15203
15181
|
} catch (parseErr) {
|
|
15204
15182
|
const parseMsg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
15205
15183
|
log.warn(`Sleep cycle: failed to submit consolidation: ${parseMsg}`);
|
|
@@ -15282,7 +15260,7 @@ async function startAgent(opts) {
|
|
|
15282
15260
|
log.info("Shutdown complete.");
|
|
15283
15261
|
};
|
|
15284
15262
|
}
|
|
15285
|
-
var
|
|
15263
|
+
var WS_INIT_TIMEOUT_MS;
|
|
15286
15264
|
var init_entrypoint = __esm({
|
|
15287
15265
|
"src/service/entrypoint.ts"() {
|
|
15288
15266
|
init_orchestrator();
|
|
@@ -15299,7 +15277,7 @@ var init_entrypoint = __esm({
|
|
|
15299
15277
|
init_sessions();
|
|
15300
15278
|
init_worker_manager();
|
|
15301
15279
|
init_dist();
|
|
15302
|
-
|
|
15280
|
+
WS_INIT_TIMEOUT_MS = 3e4;
|
|
15303
15281
|
}
|
|
15304
15282
|
});
|
|
15305
15283
|
|