@questionbase/deskfree 0.6.2 → 0.6.3
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 +459 -485
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +14 -25
- package/dist/index.js +458 -484
- 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.
|
|
@@ -8675,6 +8649,10 @@ function deriveApiUrl() {
|
|
|
8675
8649
|
const stage = process.env["STAGE"] ?? "dev";
|
|
8676
8650
|
return `https://${getStageDomain(stage, domain)}/v1/bot`;
|
|
8677
8651
|
}
|
|
8652
|
+
function deriveWsUrl() {
|
|
8653
|
+
const domain = process.env["DESKFREE_DOMAIN"] ?? "dev.deskfree.ai";
|
|
8654
|
+
return `wss://ws.${domain}`;
|
|
8655
|
+
}
|
|
8678
8656
|
function loadConfig() {
|
|
8679
8657
|
const botId = process.env["BOT"] ?? process.env["DESKFREE_BOT_ID"];
|
|
8680
8658
|
if (!botId) {
|
|
@@ -8701,9 +8679,13 @@ function loadConfig() {
|
|
|
8701
8679
|
if (isNaN(healthPort) || healthPort < 1 || healthPort > 65535) {
|
|
8702
8680
|
throw new Error(`Invalid HEALTH_PORT: ${healthPortRaw}`);
|
|
8703
8681
|
}
|
|
8682
|
+
const stage = process.env["STAGE"] ?? "dev";
|
|
8683
|
+
const wsUrl = process.env["DESKFREE_WS_URL"] ?? deriveWsUrl();
|
|
8704
8684
|
return {
|
|
8705
8685
|
botId,
|
|
8706
8686
|
apiUrl,
|
|
8687
|
+
stage,
|
|
8688
|
+
wsUrl,
|
|
8707
8689
|
stateDir: process.env["DESKFREE_STATE_DIR"] ?? DEFAULTS.stateDir,
|
|
8708
8690
|
toolsDir: process.env["DESKFREE_TOOLS_DIR"] ?? DEFAULTS.toolsDir,
|
|
8709
8691
|
logLevel,
|
|
@@ -8726,7 +8708,7 @@ function mergeWithRemoteConfig(local, remote) {
|
|
|
8726
8708
|
return {
|
|
8727
8709
|
...local,
|
|
8728
8710
|
claudeCodePath,
|
|
8729
|
-
wsUrl: process.env["DESKFREE_WS_URL"] ?? remote.wsUrl,
|
|
8711
|
+
wsUrl: process.env["DESKFREE_WS_URL"] ?? remote.wsUrl ?? local.wsUrl,
|
|
8730
8712
|
model: process.env["DESKFREE_MODEL"] ?? remote.model,
|
|
8731
8713
|
awsRegion: process.env["AWS_REGION"] ?? remote.awsRegion,
|
|
8732
8714
|
heartbeatIntervalMs: process.env["DESKFREE_HEARTBEAT_INTERVAL_MS"] ? parseInt(process.env["DESKFREE_HEARTBEAT_INTERVAL_MS"], 10) : remote.heartbeatIntervalMs,
|
|
@@ -12579,14 +12561,23 @@ var require_websocket_server2 = __commonJS({
|
|
|
12579
12561
|
});
|
|
12580
12562
|
|
|
12581
12563
|
// ../../node_modules/ws/wrapper.mjs
|
|
12582
|
-
var
|
|
12564
|
+
var wrapper_exports = {};
|
|
12565
|
+
__export(wrapper_exports, {
|
|
12566
|
+
Receiver: () => import_receiver2.default,
|
|
12567
|
+
Sender: () => import_sender2.default,
|
|
12568
|
+
WebSocket: () => import_websocket2.default,
|
|
12569
|
+
WebSocketServer: () => import_websocket_server2.default,
|
|
12570
|
+
createWebSocketStream: () => import_stream2.default,
|
|
12571
|
+
default: () => wrapper_default2
|
|
12572
|
+
});
|
|
12573
|
+
var import_stream2, import_receiver2, import_sender2, import_websocket2, import_websocket_server2, wrapper_default2;
|
|
12583
12574
|
var init_wrapper = __esm({
|
|
12584
12575
|
"../../node_modules/ws/wrapper.mjs"() {
|
|
12585
|
-
__toESM(require_stream2());
|
|
12586
|
-
__toESM(require_receiver2());
|
|
12587
|
-
__toESM(require_sender2());
|
|
12576
|
+
import_stream2 = __toESM(require_stream2());
|
|
12577
|
+
import_receiver2 = __toESM(require_receiver2());
|
|
12578
|
+
import_sender2 = __toESM(require_sender2());
|
|
12588
12579
|
import_websocket2 = __toESM(require_websocket2());
|
|
12589
|
-
__toESM(require_websocket_server2());
|
|
12580
|
+
import_websocket_server2 = __toESM(require_websocket_server2());
|
|
12590
12581
|
wrapper_default2 = import_websocket2.default;
|
|
12591
12582
|
}
|
|
12592
12583
|
});
|
|
@@ -12600,9 +12591,7 @@ __export(ws_gateway_exports, {
|
|
|
12600
12591
|
});
|
|
12601
12592
|
function getRotationToken() {
|
|
12602
12593
|
if (!currentRotationToken) {
|
|
12603
|
-
throw new Error(
|
|
12604
|
-
"No rotation token available \u2014 bots.connect not yet called"
|
|
12605
|
-
);
|
|
12594
|
+
throw new Error("No rotation token available \u2014 WS init not yet completed");
|
|
12606
12595
|
}
|
|
12607
12596
|
return currentRotationToken;
|
|
12608
12597
|
}
|
|
@@ -12634,54 +12623,88 @@ function sleepWithAbort(ms, signal) {
|
|
|
12634
12623
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
12635
12624
|
});
|
|
12636
12625
|
}
|
|
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
|
|
12626
|
+
async function wsReconnect(config, rotationToken) {
|
|
12627
|
+
const { botId, stage, wsUrl, log, abortSignal } = config;
|
|
12628
|
+
const params = new URLSearchParams({ id: botId, stage });
|
|
12629
|
+
if (rotationToken) {
|
|
12630
|
+
params.set("token", rotationToken);
|
|
12631
|
+
}
|
|
12632
|
+
const fullUrl = `${wsUrl}?${params.toString()}`;
|
|
12633
|
+
return new Promise(
|
|
12634
|
+
(resolve, reject) => {
|
|
12635
|
+
const ws = new wrapper_default2(fullUrl);
|
|
12636
|
+
let settled = false;
|
|
12637
|
+
let timeoutTimer;
|
|
12638
|
+
const cleanup = () => {
|
|
12639
|
+
if (timeoutTimer !== void 0) {
|
|
12640
|
+
clearTimeout(timeoutTimer);
|
|
12641
|
+
timeoutTimer = void 0;
|
|
12642
|
+
}
|
|
12670
12643
|
};
|
|
12671
|
-
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12644
|
+
const fail = (err) => {
|
|
12645
|
+
if (settled) return;
|
|
12646
|
+
settled = true;
|
|
12647
|
+
cleanup();
|
|
12648
|
+
try {
|
|
12649
|
+
ws.close();
|
|
12650
|
+
} catch {
|
|
12651
|
+
}
|
|
12652
|
+
reject(err);
|
|
12653
|
+
};
|
|
12654
|
+
timeoutTimer = setTimeout(() => {
|
|
12655
|
+
fail(
|
|
12656
|
+
new Error(
|
|
12657
|
+
`WS reconnect timeout after ${WS_RECONNECT_INIT_TIMEOUT_MS}ms`
|
|
12658
|
+
)
|
|
12659
|
+
);
|
|
12660
|
+
}, WS_RECONNECT_INIT_TIMEOUT_MS);
|
|
12661
|
+
ws.on("open", () => {
|
|
12662
|
+
ws.send(JSON.stringify({ action: "init" }));
|
|
12663
|
+
});
|
|
12664
|
+
ws.on("message", (data) => {
|
|
12665
|
+
try {
|
|
12666
|
+
const msg = JSON.parse(data.toString());
|
|
12667
|
+
if (msg.action === "go" && msg.rotationToken) {
|
|
12668
|
+
if (settled) return;
|
|
12669
|
+
settled = true;
|
|
12670
|
+
cleanup();
|
|
12671
|
+
resolve({ ws, rotationToken: msg.rotationToken });
|
|
12672
|
+
} else if (msg.action === "rejected") {
|
|
12673
|
+
fail(
|
|
12674
|
+
new Error(
|
|
12675
|
+
`Reconnect rejected: ${msg.reason ?? "unknown reason"}`
|
|
12676
|
+
)
|
|
12677
|
+
);
|
|
12678
|
+
} else if (msg.action === "lobby") {
|
|
12679
|
+
log.info("Reconnect landed in lobby \u2014 awaiting approval...");
|
|
12680
|
+
cleanup();
|
|
12681
|
+
}
|
|
12682
|
+
} catch (err) {
|
|
12683
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
12684
|
+
log.warn(`Error parsing reconnect response: ${errMsg}`);
|
|
12685
|
+
}
|
|
12686
|
+
});
|
|
12687
|
+
ws.on("error", (err) => {
|
|
12688
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
12689
|
+
});
|
|
12690
|
+
ws.on("close", (code, reason) => {
|
|
12691
|
+
if (!settled) {
|
|
12692
|
+
fail(
|
|
12693
|
+
new Error(
|
|
12694
|
+
`WebSocket closed during reconnect: ${code} ${reason.toString()}`
|
|
12695
|
+
)
|
|
12696
|
+
);
|
|
12697
|
+
}
|
|
12698
|
+
});
|
|
12699
|
+
abortSignal.addEventListener(
|
|
12700
|
+
"abort",
|
|
12701
|
+
() => {
|
|
12702
|
+
fail(new Error("Aborted during WS reconnect"));
|
|
12703
|
+
},
|
|
12704
|
+
{ once: true }
|
|
12681
12705
|
);
|
|
12682
12706
|
}
|
|
12683
|
-
|
|
12684
|
-
}
|
|
12707
|
+
);
|
|
12685
12708
|
}
|
|
12686
12709
|
async function startGateway(config) {
|
|
12687
12710
|
const { client, accountId, stateDir, log, abortSignal } = config;
|
|
@@ -12699,33 +12722,24 @@ async function startGateway(config) {
|
|
|
12699
12722
|
});
|
|
12700
12723
|
while (!abortSignal.aborted) {
|
|
12701
12724
|
try {
|
|
12702
|
-
let
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12725
|
+
let ws;
|
|
12726
|
+
if (totalReconnects === 0) {
|
|
12727
|
+
ws = config.initialWs;
|
|
12728
|
+
currentRotationToken = config.initialRotationToken;
|
|
12729
|
+
log.info("Using initial WS connection from handshake.");
|
|
12707
12730
|
} 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
12731
|
log.info(
|
|
12719
|
-
`
|
|
12732
|
+
`Reconnecting to ${config.wsUrl}... (reconnect #${totalReconnects})`
|
|
12720
12733
|
);
|
|
12734
|
+
const result = await wsReconnect(config, currentRotationToken ?? "");
|
|
12735
|
+
ws = result.ws;
|
|
12736
|
+
currentRotationToken = result.rotationToken;
|
|
12721
12737
|
recordReconnect(accountId);
|
|
12722
|
-
} else {
|
|
12723
|
-
log.info(`Got WS ticket, connecting to ${wsUrl}...`);
|
|
12724
12738
|
}
|
|
12739
|
+
resetBackoff(backoff);
|
|
12725
12740
|
updateHealthMode(accountId, "websocket");
|
|
12726
12741
|
cursor = await runWebSocketConnection({
|
|
12727
|
-
|
|
12728
|
-
wsUrl,
|
|
12742
|
+
ws,
|
|
12729
12743
|
client,
|
|
12730
12744
|
accountId,
|
|
12731
12745
|
stateDir,
|
|
@@ -12734,33 +12748,22 @@ async function startGateway(config) {
|
|
|
12734
12748
|
abortSignal,
|
|
12735
12749
|
onMessage: config.onMessage,
|
|
12736
12750
|
getWorkerStatus: config.getWorkerStatus,
|
|
12737
|
-
onConsolidate: config.onConsolidate
|
|
12738
|
-
isV2: true
|
|
12751
|
+
onConsolidate: config.onConsolidate
|
|
12739
12752
|
});
|
|
12740
12753
|
totalReconnects++;
|
|
12741
12754
|
} catch (err) {
|
|
12742
12755
|
totalReconnects++;
|
|
12743
12756
|
const message = err instanceof Error ? err.message : String(err);
|
|
12744
|
-
|
|
12757
|
+
const isConnectFailure = message.includes("WS-first connect") || message.includes("WS reconnect") || message.includes("Connection rejected") || message.includes("Reconnect rejected");
|
|
12758
|
+
if (isConnectFailure) {
|
|
12745
12759
|
log.warn(
|
|
12746
|
-
`Connection setup failed (attempt #${totalReconnects}): ${message}.
|
|
12760
|
+
`Connection setup failed (attempt #${totalReconnects}): ${message}. Will retry after backoff.`
|
|
12747
12761
|
);
|
|
12748
12762
|
reportError("error", `Connection setup failed: ${message}`, {
|
|
12749
12763
|
component: "gateway",
|
|
12750
|
-
event: "
|
|
12764
|
+
event: "ws_connect_failed",
|
|
12751
12765
|
attempt: totalReconnects
|
|
12752
12766
|
});
|
|
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
12767
|
} else {
|
|
12765
12768
|
log.warn(`Connection error (attempt #${totalReconnects}): ${message}`);
|
|
12766
12769
|
reportError("warn", `WS connection error: ${message}`, {
|
|
@@ -12768,8 +12771,8 @@ async function startGateway(config) {
|
|
|
12768
12771
|
event: "connection_error",
|
|
12769
12772
|
attempt: totalReconnects
|
|
12770
12773
|
});
|
|
12771
|
-
recordReconnect(accountId);
|
|
12772
12774
|
}
|
|
12775
|
+
recordReconnect(accountId);
|
|
12773
12776
|
if (abortSignal.aborted) break;
|
|
12774
12777
|
const delay = nextBackoff(backoff);
|
|
12775
12778
|
log.info(
|
|
@@ -12781,26 +12784,20 @@ async function startGateway(config) {
|
|
|
12781
12784
|
log.info(`Gateway loop exited after ${totalReconnects} reconnect(s).`);
|
|
12782
12785
|
}
|
|
12783
12786
|
async function runWebSocketConnection(opts) {
|
|
12784
|
-
const {
|
|
12787
|
+
const { client, accountId, stateDir, log, abortSignal } = opts;
|
|
12788
|
+
const ws = opts.ws;
|
|
12785
12789
|
const ctx = { accountId };
|
|
12786
12790
|
let cursor = opts.cursor;
|
|
12787
12791
|
return new Promise((resolve, reject) => {
|
|
12788
|
-
const ws = new wrapper_default2(`${wsUrl}?ticket=${ticket}`);
|
|
12789
12792
|
let pingInterval;
|
|
12790
|
-
let connectionTimer;
|
|
12791
12793
|
let pongTimer;
|
|
12792
12794
|
let notifyDebounceTimer;
|
|
12793
12795
|
let proactiveReconnectTimer;
|
|
12794
|
-
let isConnected = false;
|
|
12795
12796
|
const cleanup = () => {
|
|
12796
12797
|
if (pingInterval !== void 0) {
|
|
12797
12798
|
clearInterval(pingInterval);
|
|
12798
12799
|
pingInterval = void 0;
|
|
12799
12800
|
}
|
|
12800
|
-
if (connectionTimer !== void 0) {
|
|
12801
|
-
clearTimeout(connectionTimer);
|
|
12802
|
-
connectionTimer = void 0;
|
|
12803
|
-
}
|
|
12804
12801
|
if (pongTimer !== void 0) {
|
|
12805
12802
|
clearTimeout(pongTimer);
|
|
12806
12803
|
pongTimer = void 0;
|
|
@@ -12814,88 +12811,67 @@ async function runWebSocketConnection(opts) {
|
|
|
12814
12811
|
proactiveReconnectTimer = void 0;
|
|
12815
12812
|
}
|
|
12816
12813
|
};
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12814
|
+
setActiveWs(ws);
|
|
12815
|
+
log.info("WebSocket session started.");
|
|
12816
|
+
setWsConnected(true);
|
|
12817
|
+
setHealthMode("websocket");
|
|
12818
|
+
pingInterval = setInterval(() => {
|
|
12819
|
+
if (ws.readyState === wrapper_default2.OPEN) {
|
|
12820
12820
|
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}`);
|
|
12821
|
+
const pingPayload = { action: "ping" };
|
|
12822
|
+
if (currentRotationToken) {
|
|
12823
|
+
pingPayload.token = currentRotationToken;
|
|
12859
12824
|
}
|
|
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
|
-
);
|
|
12825
|
+
ws.send(JSON.stringify(pingPayload));
|
|
12826
|
+
pongTimer = setTimeout(() => {
|
|
12827
|
+
log.warn("Pong timeout \u2014 closing WebSocket");
|
|
12828
|
+
try {
|
|
12829
|
+
ws.close(1002, "pong timeout");
|
|
12830
|
+
} catch {
|
|
12831
|
+
}
|
|
12832
|
+
}, WS_PONG_TIMEOUT_MS);
|
|
12878
12833
|
} catch (err) {
|
|
12879
|
-
const
|
|
12880
|
-
log.warn(`Failed to send
|
|
12834
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12835
|
+
log.warn(`Failed to send ping: ${msg}`);
|
|
12881
12836
|
}
|
|
12882
12837
|
}
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12838
|
+
}, PING_INTERVAL_MS);
|
|
12839
|
+
proactiveReconnectTimer = setTimeout(() => {
|
|
12840
|
+
log.info("Proactive reconnect at 1h50m \u2014 closing for reconnect");
|
|
12841
|
+
try {
|
|
12842
|
+
ws.close(1e3, "proactive_reconnect");
|
|
12843
|
+
} catch {
|
|
12844
|
+
}
|
|
12845
|
+
}, PROACTIVE_RECONNECT_MS);
|
|
12846
|
+
if (opts.getWorkerStatus) {
|
|
12847
|
+
try {
|
|
12848
|
+
const status2 = opts.getWorkerStatus();
|
|
12849
|
+
ws.send(
|
|
12850
|
+
JSON.stringify({
|
|
12851
|
+
action: "heartbeatResponse",
|
|
12852
|
+
...status2
|
|
12853
|
+
})
|
|
12854
|
+
);
|
|
12855
|
+
} catch (err) {
|
|
12856
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
12857
|
+
log.warn(`Failed to send initial heartbeat: ${errMsg}`);
|
|
12858
|
+
}
|
|
12859
|
+
}
|
|
12860
|
+
void pollAndDeliver(
|
|
12861
|
+
client,
|
|
12862
|
+
accountId,
|
|
12863
|
+
stateDir,
|
|
12864
|
+
cursor,
|
|
12865
|
+
opts.onMessage,
|
|
12866
|
+
log
|
|
12867
|
+
).then((result) => {
|
|
12868
|
+
if (result.cursor) {
|
|
12869
|
+
cursor = result.cursor;
|
|
12870
|
+
saveCursor(ctx, cursor, stateDir, log);
|
|
12871
|
+
}
|
|
12872
|
+
}).catch((err) => {
|
|
12873
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12874
|
+
log.warn(`Initial poll failed: ${msg}`);
|
|
12899
12875
|
});
|
|
12900
12876
|
ws.on("message", (data) => {
|
|
12901
12877
|
try {
|
|
@@ -12943,9 +12919,7 @@ async function runWebSocketConnection(opts) {
|
|
|
12943
12919
|
currentRotationToken = pongMsg.rotationToken;
|
|
12944
12920
|
log.debug("Received pong \u2014 token rotated");
|
|
12945
12921
|
} else if (pongMsg.error === "rotation_invalid") {
|
|
12946
|
-
log.warn(
|
|
12947
|
-
"Rotation token invalid \u2014 closing for reconnect via bots.connect"
|
|
12948
|
-
);
|
|
12922
|
+
log.warn("Rotation token invalid \u2014 closing for reconnect");
|
|
12949
12923
|
try {
|
|
12950
12924
|
ws.close(1e3, "rotation_invalid");
|
|
12951
12925
|
} catch {
|
|
@@ -13054,7 +13028,6 @@ async function runWebSocketConnection(opts) {
|
|
|
13054
13028
|
});
|
|
13055
13029
|
ws.on("close", (code, reason) => {
|
|
13056
13030
|
cleanup();
|
|
13057
|
-
isConnected = false;
|
|
13058
13031
|
setActiveWs(null);
|
|
13059
13032
|
setWsConnected(false);
|
|
13060
13033
|
if (code === 1e3 || code === 1001) {
|
|
@@ -13088,7 +13061,6 @@ async function runWebSocketConnection(opts) {
|
|
|
13088
13061
|
});
|
|
13089
13062
|
ws.on("error", (err) => {
|
|
13090
13063
|
cleanup();
|
|
13091
|
-
isConnected = false;
|
|
13092
13064
|
setActiveWs(null);
|
|
13093
13065
|
setWsConnected(false);
|
|
13094
13066
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -13117,50 +13089,7 @@ async function runWebSocketConnection(opts) {
|
|
|
13117
13089
|
);
|
|
13118
13090
|
});
|
|
13119
13091
|
}
|
|
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;
|
|
13092
|
+
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
13093
|
var init_ws_gateway = __esm({
|
|
13165
13094
|
"src/gateway/ws-gateway.ts"() {
|
|
13166
13095
|
init_health_state();
|
|
@@ -13169,15 +13098,11 @@ var init_ws_gateway = __esm({
|
|
|
13169
13098
|
init_dist();
|
|
13170
13099
|
init_wrapper();
|
|
13171
13100
|
PING_INTERVAL_MS = 5 * 60 * 1e3;
|
|
13172
|
-
POLL_FALLBACK_INTERVAL_MS = 3e4;
|
|
13173
|
-
WS_CONNECTION_TIMEOUT_MS = 3e4;
|
|
13174
13101
|
WS_PONG_TIMEOUT_MS = 1e4;
|
|
13175
13102
|
NOTIFY_DEBOUNCE_MS = 200;
|
|
13176
13103
|
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
13104
|
PROACTIVE_RECONNECT_MS = 110 * 60 * 1e3;
|
|
13105
|
+
WS_RECONNECT_INIT_TIMEOUT_MS = 3e4;
|
|
13181
13106
|
BACKOFF_INITIAL_MS = 2e3;
|
|
13182
13107
|
BACKOFF_MAX_MS = 3e4;
|
|
13183
13108
|
BACKOFF_FACTOR = 1.8;
|
|
@@ -14725,6 +14650,17 @@ ${userMessage}
|
|
|
14725
14650
|
} catch {
|
|
14726
14651
|
}
|
|
14727
14652
|
} finally {
|
|
14653
|
+
try {
|
|
14654
|
+
const task = await client.getTask({ taskId });
|
|
14655
|
+
if (task.status === "open" && task.awaiting === "bot") {
|
|
14656
|
+
log.info(
|
|
14657
|
+
`Worker finished but task ${taskId} still awaiting bot \u2014 auto-flipping to human`
|
|
14658
|
+
);
|
|
14659
|
+
await client.updateTaskStatus({ taskId, awaiting: "human" });
|
|
14660
|
+
}
|
|
14661
|
+
} catch {
|
|
14662
|
+
log.warn(`Failed to check/flip awaiting state for task ${taskId}`);
|
|
14663
|
+
}
|
|
14728
14664
|
const handle = this.workers.get(taskId);
|
|
14729
14665
|
if (handle) {
|
|
14730
14666
|
clearTimeout(handle.idleTimer);
|
|
@@ -14909,49 +14845,82 @@ function scheduleHeartbeat(createOrchServer, model, intervalMs, signal, log, cla
|
|
|
14909
14845
|
log.info(`Heartbeat scheduled every ${Math.round(intervalMs / 1e3)}s`);
|
|
14910
14846
|
}
|
|
14911
14847
|
async function initialConnect(opts) {
|
|
14912
|
-
const { botId,
|
|
14913
|
-
|
|
14914
|
-
|
|
14915
|
-
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14848
|
+
const { botId, stage, wsUrl, fingerprint, log, abortSignal } = opts;
|
|
14849
|
+
const WebSocket2 = (await Promise.resolve().then(() => (init_wrapper(), wrapper_exports))).default;
|
|
14850
|
+
const params = new URLSearchParams({ id: botId, stage });
|
|
14851
|
+
const fullUrl = `${wsUrl}?${params.toString()}`;
|
|
14852
|
+
return new Promise((resolve, reject) => {
|
|
14853
|
+
const ws = new WebSocket2(fullUrl);
|
|
14854
|
+
let settled = false;
|
|
14855
|
+
let timeoutTimer;
|
|
14856
|
+
const cleanup = () => {
|
|
14857
|
+
if (timeoutTimer !== void 0) {
|
|
14858
|
+
clearTimeout(timeoutTimer);
|
|
14859
|
+
timeoutTimer = void 0;
|
|
14860
|
+
}
|
|
14920
14861
|
};
|
|
14921
|
-
const
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14862
|
+
const fail = (err) => {
|
|
14863
|
+
if (settled) return;
|
|
14864
|
+
settled = true;
|
|
14865
|
+
cleanup();
|
|
14866
|
+
try {
|
|
14867
|
+
ws.close();
|
|
14868
|
+
} catch {
|
|
14869
|
+
}
|
|
14870
|
+
reject(err);
|
|
14871
|
+
};
|
|
14872
|
+
timeoutTimer = setTimeout(() => {
|
|
14873
|
+
fail(new Error(`WS-first connect timeout after ${WS_INIT_TIMEOUT_MS}ms`));
|
|
14874
|
+
}, WS_INIT_TIMEOUT_MS);
|
|
14875
|
+
ws.on("open", () => {
|
|
14876
|
+
const initMsg = { action: "init" };
|
|
14877
|
+
{
|
|
14878
|
+
initMsg.fingerprint = fingerprint;
|
|
14879
|
+
}
|
|
14880
|
+
ws.send(JSON.stringify(initMsg));
|
|
14925
14881
|
});
|
|
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
|
-
|
|
14882
|
+
ws.on("message", (data) => {
|
|
14883
|
+
try {
|
|
14884
|
+
const msg = JSON.parse(data.toString());
|
|
14885
|
+
if (msg.action === "go" && msg.rotationToken) {
|
|
14886
|
+
if (settled) return;
|
|
14887
|
+
settled = true;
|
|
14888
|
+
cleanup();
|
|
14889
|
+
ws.removeAllListeners();
|
|
14890
|
+
resolve({ ws, rotationToken: msg.rotationToken });
|
|
14891
|
+
} else if (msg.action === "lobby") {
|
|
14892
|
+
log.info("Awaiting human approval...");
|
|
14893
|
+
cleanup();
|
|
14894
|
+
} else if (msg.action === "rejected") {
|
|
14895
|
+
fail(
|
|
14896
|
+
new Error(`Connection rejected: ${msg.reason ?? "unknown reason"}`)
|
|
14897
|
+
);
|
|
14898
|
+
}
|
|
14899
|
+
} catch (err) {
|
|
14900
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
14901
|
+
log.warn(`Error parsing init response: ${errMsg}`);
|
|
14902
|
+
}
|
|
14903
|
+
});
|
|
14904
|
+
ws.on("error", (err) => {
|
|
14905
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
14906
|
+
});
|
|
14907
|
+
ws.on("close", (code, reason) => {
|
|
14908
|
+
if (!settled) {
|
|
14909
|
+
fail(
|
|
14910
|
+
new Error(
|
|
14911
|
+
`WebSocket closed during init: ${code} ${reason.toString()}`
|
|
14912
|
+
)
|
|
14913
|
+
);
|
|
14914
|
+
}
|
|
14915
|
+
});
|
|
14916
|
+
abortSignal.addEventListener(
|
|
14917
|
+
"abort",
|
|
14918
|
+
() => {
|
|
14919
|
+
fail(new Error("Aborted during WS-first connect"));
|
|
14920
|
+
},
|
|
14921
|
+
{ once: true }
|
|
14922
|
+
);
|
|
14923
|
+
});
|
|
14955
14924
|
}
|
|
14956
14925
|
async function startAgent(opts) {
|
|
14957
14926
|
const localConfig = loadConfig();
|
|
@@ -14960,19 +14929,22 @@ async function startAgent(opts) {
|
|
|
14960
14929
|
log.info("DeskFree Agent Runtime starting...");
|
|
14961
14930
|
const { getRotationToken: getRotationToken2, setInitialRotationToken: setInitialRotationToken2 } = await Promise.resolve().then(() => (init_ws_gateway(), ws_gateway_exports));
|
|
14962
14931
|
const { collectFingerprint: collectFingerprint2 } = await Promise.resolve().then(() => (init_fingerprint(), fingerprint_exports));
|
|
14963
|
-
const
|
|
14964
|
-
const
|
|
14932
|
+
const { createRequire } = await import('module');
|
|
14933
|
+
const require3 = createRequire(import.meta.url);
|
|
14934
|
+
const runtimePkg = require3("../../package.json");
|
|
14935
|
+
const runtimeVersion = runtimePkg.version;
|
|
14965
14936
|
const fingerprint = collectFingerprint2(localConfig.stateDir, runtimeVersion);
|
|
14966
|
-
log.info("Connecting to DeskFree...", {
|
|
14937
|
+
log.info("Connecting to DeskFree...", { wsUrl: localConfig.wsUrl });
|
|
14967
14938
|
const connectResult = await initialConnect({
|
|
14968
14939
|
botId: localConfig.botId,
|
|
14969
|
-
|
|
14940
|
+
stage: localConfig.stage,
|
|
14941
|
+
wsUrl: localConfig.wsUrl,
|
|
14970
14942
|
fingerprint,
|
|
14971
14943
|
log,
|
|
14972
14944
|
abortSignal: abortController.signal
|
|
14973
14945
|
});
|
|
14974
14946
|
setInitialRotationToken2(connectResult.rotationToken);
|
|
14975
|
-
log.info("Connected \u2014 got rotation token
|
|
14947
|
+
log.info("Connected \u2014 got rotation token via WS handshake.");
|
|
14976
14948
|
const client = new DeskFreeClient({
|
|
14977
14949
|
apiUrl: localConfig.apiUrl,
|
|
14978
14950
|
getToken: () => getRotationToken2()
|
|
@@ -15067,7 +15039,7 @@ async function startAgent(opts) {
|
|
|
15067
15039
|
const createOrchServer = () => createOrchestratorMcpServer(client, customTools, workerManager);
|
|
15068
15040
|
const healthServer = startHealthServer(config.healthPort, log);
|
|
15069
15041
|
const sessionStore = new SessionStore();
|
|
15070
|
-
log.info("Starting gateway", { wsUrl: config.wsUrl
|
|
15042
|
+
log.info("Starting gateway", { wsUrl: config.wsUrl });
|
|
15071
15043
|
void startGateway({
|
|
15072
15044
|
client,
|
|
15073
15045
|
wsUrl: config.wsUrl,
|
|
@@ -15076,12 +15048,9 @@ async function startAgent(opts) {
|
|
|
15076
15048
|
log,
|
|
15077
15049
|
abortSignal: abortController.signal,
|
|
15078
15050
|
botId: localConfig.botId,
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
ticket: connectResult.ticket,
|
|
15083
|
-
wsUrl: connectResult.wsUrl
|
|
15084
|
-
},
|
|
15051
|
+
stage: localConfig.stage,
|
|
15052
|
+
initialWs: connectResult.ws,
|
|
15053
|
+
initialRotationToken: connectResult.rotationToken,
|
|
15085
15054
|
getWorkerStatus: () => ({
|
|
15086
15055
|
activeWorkers: workerManager.activeCount,
|
|
15087
15056
|
queuedTasks: workerManager.queuedCount,
|
|
@@ -15200,6 +15169,11 @@ async function startAgent(opts) {
|
|
|
15200
15169
|
log.info(
|
|
15201
15170
|
`Sleep cycle: consolidation applied (${consolidateResult.opsApplied} ops, ${consolidateResult.entriesArchived} archived)`
|
|
15202
15171
|
);
|
|
15172
|
+
if (consolidateResult.validationErrors?.length > 0) {
|
|
15173
|
+
log.warn(
|
|
15174
|
+
`Sleep cycle: validation errors: ${consolidateResult.validationErrors.join("; ")}`
|
|
15175
|
+
);
|
|
15176
|
+
}
|
|
15203
15177
|
} catch (parseErr) {
|
|
15204
15178
|
const parseMsg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
15205
15179
|
log.warn(`Sleep cycle: failed to submit consolidation: ${parseMsg}`);
|
|
@@ -15282,7 +15256,7 @@ async function startAgent(opts) {
|
|
|
15282
15256
|
log.info("Shutdown complete.");
|
|
15283
15257
|
};
|
|
15284
15258
|
}
|
|
15285
|
-
var
|
|
15259
|
+
var WS_INIT_TIMEOUT_MS;
|
|
15286
15260
|
var init_entrypoint = __esm({
|
|
15287
15261
|
"src/service/entrypoint.ts"() {
|
|
15288
15262
|
init_orchestrator();
|
|
@@ -15299,7 +15273,7 @@ var init_entrypoint = __esm({
|
|
|
15299
15273
|
init_sessions();
|
|
15300
15274
|
init_worker_manager();
|
|
15301
15275
|
init_dist();
|
|
15302
|
-
|
|
15276
|
+
WS_INIT_TIMEOUT_MS = 3e4;
|
|
15303
15277
|
}
|
|
15304
15278
|
});
|
|
15305
15279
|
|