@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 CHANGED
@@ -2729,34 +2729,33 @@ function createOrchestratorTools(client, _options) {
2729
2729
  return errorResult(err);
2730
2730
  }
2731
2731
  }),
2732
- createTool(ORCHESTRATOR_TOOLS.PROPOSE, async (params) => {
2732
+ createTool(ORCHESTRATOR_TOOLS.CREATE_TASK, async (params) => {
2733
2733
  try {
2734
- const context = validateStringParam(params, "context", false);
2735
- const taskId = validateStringParam(params, "taskId", false);
2736
- const rawTasks = params.tasks;
2737
- if (!Array.isArray(rawTasks) || rawTasks.length === 0) {
2738
- throw new Error("tasks must be a non-empty array of task objects");
2739
- }
2740
- const tasks = rawTasks;
2741
- for (let i = 0; i < tasks.length; i++) {
2742
- const task = tasks[i];
2743
- if (!task || typeof task !== "object") {
2744
- throw new Error(`tasks[${i}] must be an object`);
2745
- }
2746
- if (!task.title || typeof task.title !== "string" || task.title.trim() === "") {
2747
- throw new Error(`tasks[${i}].title must be a non-empty string`);
2748
- }
2749
- }
2750
- await client.proposePlan({
2751
- context,
2752
- tasks,
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: `Proposal created with ${tasks.length} task(s)`
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.PROPOSE, async (params) => {
2976
+ createTool(WORKER_TOOLS.CREATE_TASK, async (params) => {
2978
2977
  try {
2979
- const context = validateStringParam(params, "context", false);
2980
- const taskId = validateStringParam(params, "taskId", false);
2981
- const rawTasks = params.tasks;
2982
- if (!Array.isArray(rawTasks) || rawTasks.length === 0) {
2983
- throw new Error("tasks must be a non-empty array of task objects");
2984
- }
2985
- const tasks = rawTasks;
2986
- for (let i = 0; i < tasks.length; i++) {
2987
- const task = tasks[i];
2988
- if (!task || typeof task !== "object") {
2989
- throw new Error(`tasks[${i}] must be an object`);
2990
- }
2991
- if (!task.title || typeof task.title !== "string" || task.title.trim() === "") {
2992
- throw new Error(`tasks[${i}].title must be a non-empty string`);
2993
- }
2994
- }
2995
- await client.proposePlan({
2996
- context,
2997
- tasks,
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: `Proposal created with ${tasks.length} task(s)`
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 propose a task and move the work to a thread.
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. **Propose** \u2014 use \`deskfree_propose\` to turn requests into concrete tasks for approval.
3135
- 3. **Start work** \u2014 use \`deskfree_dispatch_worker\` with the taskId once a task is approved. You'll then continue the work in the task thread.
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 proposing, qualify the request.** Figure out what kind of thing this is:
3139
- - **One-off task** ("proofread this") \u2014 propose a task directly.
3140
- - **New aspiration** ("I want to start posting on LinkedIn") \u2014 don't rush to propose. Ask 1-2 short qualifying questions to understand the real goal.
3141
- - Never call \`deskfree_propose\` as your very first action \u2014 qualify first, even if briefly.
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 propose and coordinate \u2014 the actual work happens in task threads. Use \`deskfree_dispatch_worker\` to start working on approved tasks.
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 propose it as a new task (don't reopen the old one).
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, deskfree_propose.
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 Send a brief message: what you found, what you'll produce. One or two sentences. ("I'll build on the existing brand guide and create a new tone reference.")
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, send a message and set \`awaiting: 'human'\` via \`deskfree_update_task_status\`. The human will complete the task when satisfied.
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 proposals entirely \u2014 the human has enough on their plate. Focus only on dispatching approved work.
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. Propose as a task via \`deskfree_propose\`. One focused task, not a batch.
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, deskfree_propose, deskfree_send_message, deskfree_learning.
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 propose via \`deskfree_propose\` if needed.
3357
- 4. One proactive proposal max. Skip if nothing merits it or board is busy (3+ items needing human attention).`;
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, propose overnight work, and brief the human.
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, deskfree_propose, deskfree_send_message, deskfree_read_file, deskfree_orient.
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. PROPOSE THE PLAN
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 proposal max \u2014 or skip entirely. Don't pile on.
3400
- 2. Use \`deskfree_propose\` with 1-3 well-scoped tasks. Quality over quantity.
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 the proposal entirely. Don't force it.
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 proposals, still send a brief day summary.
3414
+ - If no tasks created, still send a brief day summary.
3411
3415
 
3412
3416
  ### Rules
3413
- - Do NOT propose things already on the board or recently completed.
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, propose it.
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
- /** Propose a plancreates a proposal message with plan metadata. No DB rows until human approves. */
7556
- async proposePlan(input) {
7557
- if (!input.tasks || input.tasks.length === 0) {
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
- PROPOSE: {
7969
- name: "deskfree_propose",
7970
- description: "Propose a plan for human approval. Nothing is created until the human reviews and approves in a modal. 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.",
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
- context: Type.Optional(
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: "Why this plan is being proposed \u2014 helps the human understand the reasoning behind the proposal"
7973
+ description: "Detailed instructions, constraints, or context for the task."
7975
7974
  })
7976
7975
  ),
7977
- tasks: Type.Array(
7978
- Type.Object({
7979
- title: Type.String({
7980
- description: "Task title \u2014 short, action-oriented (max 200 chars)"
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
- taskId: Type.Optional(
7981
+ scheduledFor: Type.Optional(
8023
7982
  Type.String({
8024
- description: "Task ID to thread this proposal into (for follow-up proposals from within a task)"
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
- PROPOSE: {
8104
- name: "deskfree_propose",
8105
- description: "Propose a plan for human approval. Nothing is created until the human reviews and approves in a modal. 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.",
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
- context: Type.Optional(
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: "Why this plan is being proposed \u2014 helps the human understand the reasoning behind the proposal"
8094
+ description: "Detailed instructions, constraints, or context for the task."
8110
8095
  })
8111
8096
  ),
8112
- tasks: Type.Array(
8113
- Type.Object({
8114
- title: Type.String({
8115
- description: "Task title \u2014 short, action-oriented (max 200 chars)"
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
- taskId: Type.Optional(
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: "Task ID to thread this proposal into (for follow-up proposals from within a task)"
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
- PROPOSE: SHARED_TOOLS.PROPOSE
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 approved tasks, then start working on them.
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 propose a task and move the work to a thread.
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: propose \u2192 approve \u2192 work.**
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. **Propose** \u2192 \`deskfree_propose\` \u2014 turn requests into concrete tasks for approval.
8268
- 3. **Start work** \u2192 \`deskfree_dispatch_worker\` with the taskId. You'll then continue the work in the task thread.
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 proposing, qualify the request.** Figure out what kind of thing this is:
8272
- - **One-off task** ("proofread this") \u2192 propose a task directly.
8273
- - **New aspiration** ("I want to start posting on LinkedIn") \u2192 don't rush to propose. Ask 1-2 short qualifying questions to understand the real goal.
8274
- - Never call \`deskfree_propose\` as your very first action \u2014 qualify first, even if briefly.
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 propose and coordinate \u2014 the actual work happens in task threads. Use \`deskfree_dispatch_worker\` to start working on approved tasks.
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 propose it as a new task (don't reopen the old one).
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, deskfree_propose.
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 Send a brief message: what you found, what you'll produce. One or two sentences. ("I'll build on the existing brand guide and create a new tone reference.")
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, send a message and set \`awaiting: 'human'\` via \`deskfree_update_task_status\`. The human will complete the task when satisfied.
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 getStageDomain(stage, domain) {
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 domain = process.env["DESKFREE_DOMAIN"] ?? "dev.deskfree.ai";
8675
- const stage = process.env["STAGE"] ?? "dev";
8676
- return `https://${getStageDomain(stage, domain)}/v1/bot`;
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 = process.env["DESKFREE_API_URL"] ?? deriveApiUrl();
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: process.env["DESKFREE_WS_URL"] ?? remote.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 import_websocket2, wrapper_default2;
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 callBotsConnect(config, rotationToken) {
12638
- const { botId, publicApiUrl, fingerprint, log, abortSignal } = config;
12639
- if (!botId || !publicApiUrl || !fingerprint) {
12640
- throw new Error(
12641
- "Gateway config missing botId, publicApiUrl, or fingerprint"
12642
- );
12643
- }
12644
- let connectToken;
12645
- while (true) {
12646
- if (abortSignal.aborted) throw new Error("Aborted during bots.connect");
12647
- const body = {
12648
- botId,
12649
- fingerprint,
12650
- ...rotationToken ? { rotationToken } : {},
12651
- ...connectToken ? { connectToken } : {}
12652
- };
12653
- const response = await fetch(`${publicApiUrl}/bots.connect`, {
12654
- method: "POST",
12655
- headers: { "Content-Type": "application/json" },
12656
- body: JSON.stringify(body)
12657
- });
12658
- if (!response.ok) {
12659
- const text = await response.text().catch(() => "");
12660
- throw new Error(`bots.connect failed: ${response.status} ${text}`);
12661
- }
12662
- const json = await response.json();
12663
- const data = json.result?.data;
12664
- if (!data) throw new Error("bots.connect: invalid response structure");
12665
- if (data.status === "approved") {
12666
- return {
12667
- ticket: data.ticket,
12668
- wsUrl: data.wsUrl,
12669
- rotationToken: data.rotationToken
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
- if (data.status === "awaiting_approval") {
12673
- connectToken = data.connectToken;
12674
- log.info("Awaiting human approval... polling in 30s");
12675
- await sleepWithAbort(CONNECT_POLL_INTERVAL_MS, abortSignal);
12676
- continue;
12677
- }
12678
- if (data.status === "rejected") {
12679
- throw new Error(
12680
- `Connection rejected: ${data.reason || "unknown reason"}`
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
- throw new Error(`bots.connect: unexpected status "${data.status}"`);
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 ticket;
12703
- let wsUrl;
12704
- if (config.initialTicket && totalReconnects === 0) {
12705
- ticket = config.initialTicket.ticket;
12706
- wsUrl = config.initialTicket.wsUrl;
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
- `Got WS ticket, reconnecting to ${wsUrl}... (reconnect #${totalReconnects})`
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
- ticket,
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
- if (message.includes("bots.connect failed") || message.includes("server error") || message.includes("Connection rejected")) {
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}. Falling back to polling.`
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: "ticket_fetch_failed",
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 { ticket, wsUrl, client, accountId, stateDir, log, abortSignal } = opts;
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
- connectionTimer = setTimeout(() => {
12818
- if (!isConnected) {
12819
- cleanup();
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
- ws.close();
12822
- } catch {
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
- }, PING_INTERVAL_MS);
12862
- proactiveReconnectTimer = setTimeout(() => {
12863
- log.info("Proactive reconnect at 1h50m \u2014 closing for reconnect");
12864
- try {
12865
- ws.close(1e3, "proactive_reconnect");
12866
- } catch {
12867
- }
12868
- }, PROACTIVE_RECONNECT_MS);
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 errMsg = err instanceof Error ? err.message : String(err);
12880
- log.warn(`Failed to send initial heartbeat: ${errMsg}`);
12838
+ const msg = err instanceof Error ? err.message : String(err);
12839
+ log.warn(`Failed to send ping: ${msg}`);
12881
12840
  }
12882
12841
  }
12883
- void pollAndDeliver(
12884
- client,
12885
- accountId,
12886
- stateDir,
12887
- cursor,
12888
- opts.onMessage,
12889
- log
12890
- ).then((result) => {
12891
- if (result.cursor) {
12892
- cursor = result.cursor;
12893
- saveCursor(ctx, cursor, stateDir, log);
12894
- }
12895
- }).catch((err) => {
12896
- const msg = err instanceof Error ? err.message : String(err);
12897
- log.warn(`Initial poll failed: ${msg}`);
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
- async function runPollingFallback(opts) {
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, publicApiUrl, fingerprint, log, abortSignal } = opts;
14913
- let connectToken;
14914
- while (true) {
14915
- if (abortSignal.aborted) throw new Error("Aborted during bots.connect");
14916
- const body = {
14917
- botId,
14918
- fingerprint,
14919
- ...connectToken ? { connectToken } : {}
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 response = await fetch(`${publicApiUrl}/bots.connect`, {
14922
- method: "POST",
14923
- headers: { "Content-Type": "application/json" },
14924
- body: JSON.stringify(body)
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
- if (!response.ok) {
14927
- const text = await response.text().catch(() => "");
14928
- throw new Error(`bots.connect failed: ${response.status} ${text}`);
14929
- }
14930
- const json = await response.json();
14931
- const data = json.result?.data;
14932
- if (!data) throw new Error("bots.connect: invalid response structure");
14933
- if (data.status === "approved") {
14934
- return {
14935
- ticket: data.ticket,
14936
- wsUrl: data.wsUrl,
14937
- rotationToken: data.rotationToken
14938
- };
14939
- }
14940
- if (data.status === "awaiting_approval") {
14941
- connectToken = data.connectToken;
14942
- log.info("Awaiting human approval... polling in 30s");
14943
- await new Promise(
14944
- (resolve) => setTimeout(resolve, CONNECT_POLL_INTERVAL_MS2)
14945
- );
14946
- continue;
14947
- }
14948
- if (data.status === "rejected") {
14949
- throw new Error(
14950
- `Connection rejected: ${data.reason || "unknown reason"}`
14951
- );
14952
- }
14953
- throw new Error(`bots.connect: unexpected status "${data.status}"`);
14954
- }
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 runtimeVersion = process.env["npm_package_version"] ?? "unknown";
14964
- const publicApiUrl = localConfig.apiUrl.replace("/v1/bot", "/v1/public");
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...", { apiUrl: publicApiUrl });
14941
+ log.info("Connecting to DeskFree...", { wsUrl: localConfig.wsUrl });
14967
14942
  const connectResult = await initialConnect({
14968
14943
  botId: localConfig.botId,
14969
- publicApiUrl,
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 and WS ticket.");
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, apiUrl: config.apiUrl });
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
- publicApiUrl,
15080
- fingerprint,
15081
- initialTicket: {
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 CONNECT_POLL_INTERVAL_MS2;
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
- CONNECT_POLL_INTERVAL_MS2 = 3e4;
15280
+ WS_INIT_TIMEOUT_MS = 3e4;
15303
15281
  }
15304
15282
  });
15305
15283