@questionbase/deskfree 0.3.0-alpha.26 → 0.3.0-alpha.27

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/index.js CHANGED
@@ -4697,6 +4697,12 @@ var wrapper_default = import_websocket.default;
4697
4697
  var activeTaskId = null;
4698
4698
  var completedTaskId = null;
4699
4699
  var inboundThreadId = null;
4700
+ function setActiveTaskId(taskId) {
4701
+ if (taskId === null && activeTaskId !== null) {
4702
+ completedTaskId = activeTaskId;
4703
+ }
4704
+ activeTaskId = taskId;
4705
+ }
4700
4706
  function getActiveTaskId() {
4701
4707
  return activeTaskId ?? completedTaskId ?? inboundThreadId;
4702
4708
  }
@@ -8842,6 +8848,54 @@ function resolveAccountFromConfig(api) {
8842
8848
  enabled: ch.enabled !== false
8843
8849
  };
8844
8850
  }
8851
+ function estimateTokens(text) {
8852
+ return Math.ceil(text.length / 4);
8853
+ }
8854
+ var MAX_TASK_CONTEXT_TOKENS = 8e3;
8855
+ var MAX_FULL_MESSAGES = 15;
8856
+ var MAX_FILE_CONTENT_CHARS = 4e3;
8857
+ function trimTaskContext(result) {
8858
+ const raw = result;
8859
+ const trimmed = {
8860
+ taskId: result.taskId,
8861
+ title: result.title,
8862
+ status: result.status,
8863
+ instructions: result.instructions
8864
+ };
8865
+ if (raw["substeps"]) {
8866
+ trimmed.substeps = raw["substeps"];
8867
+ }
8868
+ if (raw["parentContext"]) {
8869
+ trimmed.parentContext = raw["parentContext"];
8870
+ }
8871
+ if (result.messages && result.messages.length > 0) {
8872
+ if (result.messages.length <= MAX_FULL_MESSAGES) {
8873
+ trimmed.messages = result.messages;
8874
+ } else {
8875
+ const omitted = result.messages.length - MAX_FULL_MESSAGES;
8876
+ const recentMessages = result.messages.slice(-MAX_FULL_MESSAGES);
8877
+ trimmed.messages = recentMessages;
8878
+ trimmed.messagesOmitted = `${omitted} earlier message(s) omitted \u2014 use deskfree_send_message to ask about history if needed`;
8879
+ }
8880
+ }
8881
+ if (result.fileContext) {
8882
+ const fc = result.fileContext;
8883
+ if (fc.content.length > MAX_FILE_CONTENT_CHARS) {
8884
+ trimmed.fileContext = {
8885
+ fileId: fc.fileId,
8886
+ name: fc.name,
8887
+ description: fc.description,
8888
+ contentFormat: fc.contentFormat,
8889
+ version: fc.version,
8890
+ content: fc.content.slice(0, MAX_FILE_CONTENT_CHARS),
8891
+ contentTruncated: `Content truncated from ${fc.content.length} chars. Full content available \u2014 use deskfree_update_file with the complete replacement when editing.`
8892
+ };
8893
+ } else {
8894
+ trimmed.fileContext = fc;
8895
+ }
8896
+ }
8897
+ return trimmed;
8898
+ }
8845
8899
  function formatTaskResponse(task, summary, nextActions) {
8846
8900
  return {
8847
8901
  content: [
@@ -8947,6 +9001,33 @@ function validateStringParam(params, key, required) {
8947
9001
  }
8948
9002
  return value;
8949
9003
  }
9004
+ function validateEnumParam(params, key, allowedValues, required) {
9005
+ if (!params) {
9006
+ if (required) {
9007
+ throw new Error(
9008
+ `Required parameter '${key}' is missing (no params provided)`
9009
+ );
9010
+ }
9011
+ return void 0;
9012
+ }
9013
+ const value = params[key];
9014
+ if (value == null) {
9015
+ if (required) throw new Error(`Required parameter '${key}' is missing`);
9016
+ return void 0;
9017
+ }
9018
+ if (typeof value !== "string") {
9019
+ throw new Error(`Parameter '${key}' must be a string, got ${typeof value}`);
9020
+ }
9021
+ if (required && value.trim() === "") {
9022
+ throw new Error(`Required parameter '${key}' cannot be empty`);
9023
+ }
9024
+ if (!allowedValues.includes(value)) {
9025
+ throw new Error(
9026
+ `Parameter '${key}' must be one of: ${allowedValues.join(", ")}, got: ${value}`
9027
+ );
9028
+ }
9029
+ return value;
9030
+ }
8950
9031
  function parseProposeTasks(raw) {
8951
9032
  if (!Array.isArray(raw) || raw.length === 0) {
8952
9033
  throw new Error('Parameter "tasks" must be a non-empty array');
@@ -9002,6 +9083,65 @@ function parseInitiative(raw) {
9002
9083
  }
9003
9084
  return void 0;
9004
9085
  }
9086
+ function makeUpdateFileHandler(client) {
9087
+ return async (_id, params) => {
9088
+ try {
9089
+ const fileId = validateStringParam(params, "fileId", true);
9090
+ const content = validateStringParam(params, "content", true);
9091
+ const contentFormat = validateEnumParam(
9092
+ params,
9093
+ "contentFormat",
9094
+ ["markdown", "html"],
9095
+ false
9096
+ );
9097
+ await client.updateFile({ fileId, content, contentFormat });
9098
+ return formatConfirmation(`Updated file ${fileId}`, [
9099
+ "File content has been saved",
9100
+ "Complete the task with deskfree_complete_task when ready"
9101
+ ]);
9102
+ } catch (err) {
9103
+ return errorResult(err);
9104
+ }
9105
+ };
9106
+ }
9107
+ function makeCompleteTaskHandler(client) {
9108
+ return async (_id, params) => {
9109
+ try {
9110
+ const taskId = validateStringParam(params, "taskId", true);
9111
+ const outcome = validateEnumParam(
9112
+ params,
9113
+ "outcome",
9114
+ ["done", "blocked"],
9115
+ true
9116
+ );
9117
+ const summary = validateStringParam(params, "summary", false);
9118
+ const evaluation = params?.evaluation;
9119
+ const result = await client.completeTask({
9120
+ taskId,
9121
+ outcome,
9122
+ summary,
9123
+ ...evaluation ? { evaluation } : {}
9124
+ });
9125
+ setActiveTaskId(null);
9126
+ const summaryVerb = outcome === "done" ? "completed" : "blocked";
9127
+ const icon = outcome === "done" ? "\u2705" : "\u{1F6AB}";
9128
+ await client.sendMessage({
9129
+ content: `${icon} Task ${summaryVerb}: "${result.title}"`
9130
+ }).catch(() => {
9131
+ });
9132
+ return formatTaskResponse(
9133
+ result,
9134
+ `Task "${result.title}" marked as ${summaryVerb} \u2014 waiting for human`,
9135
+ [
9136
+ outcome === "done" ? "Human will review your work summary" : "Human will review the blocker and provide guidance",
9137
+ "Use deskfree_state to check for other tasks"
9138
+ ]
9139
+ );
9140
+ } catch (err) {
9141
+ return errorResult(err);
9142
+ }
9143
+ };
9144
+ }
9005
9145
  function makeReopenTaskHandler(client) {
9006
9146
  return async (_id, params) => {
9007
9147
  try {
@@ -9133,6 +9273,84 @@ function createOrchestratorTools(api) {
9133
9273
  }
9134
9274
  ];
9135
9275
  }
9276
+ function createWorkerTools(api) {
9277
+ const account = resolveAccountFromConfig(api);
9278
+ if (!account) return null;
9279
+ const client = new DeskFreeClient(account.botToken, account.apiUrl);
9280
+ return [
9281
+ {
9282
+ ...WORKER_TOOLS.START_TASK,
9283
+ async execute(_id, params) {
9284
+ try {
9285
+ const taskId = validateStringParam(params, "taskId", true);
9286
+ const runnerId = validateStringParam(params, "runnerId", false);
9287
+ const result = await client.claimTask({ taskId, runnerId });
9288
+ setActiveTaskId(taskId);
9289
+ let skillInstructions = "";
9290
+ if (result.skillContext?.length) {
9291
+ skillInstructions = result.skillContext.map(
9292
+ (s) => `
9293
+ \u26A0\uFE0F SKILL: ${s.displayName}
9294
+ ${s.criticalSection}
9295
+
9296
+ ${s.instructions}`
9297
+ ).join("\n\n---\n");
9298
+ }
9299
+ const trimmedTask = trimTaskContext(result);
9300
+ const taskJson = JSON.stringify(
9301
+ {
9302
+ summary: `Claimed task "${result.title}" \u2014 full context loaded${result.skillContext?.length ? ` (${result.skillContext.length} skill${result.skillContext.length > 1 ? "s" : ""} active)` : ""}`,
9303
+ mode: trimmedTask.mode ?? "work",
9304
+ nextActions: [
9305
+ "Read the instructions and message history carefully",
9306
+ ...result.fileContext ? [
9307
+ `Task has a linked file "${result.fileContext.name}" (ID: ${result.fileContext.fileId}) \u2014 use deskfree_update_file to save your work to it`
9308
+ ] : [],
9309
+ ...trimmedTask.mode === "evaluation" ? [
9310
+ "This is an evaluation task \u2014 review the WoW context and provide your evaluation in deskfree_complete_task"
9311
+ ] : [],
9312
+ "Complete with deskfree_complete_task when done (summary required)"
9313
+ ],
9314
+ task: trimmedTask
9315
+ },
9316
+ null,
9317
+ 2
9318
+ );
9319
+ const totalTokens = estimateTokens(skillInstructions) + estimateTokens(taskJson);
9320
+ const budgetWarning = totalTokens > MAX_TASK_CONTEXT_TOKENS ? `
9321
+ \u26A0\uFE0F Context budget: ~${totalTokens} tokens loaded (~${MAX_TASK_CONTEXT_TOKENS} target). Be concise in your reasoning.` : "";
9322
+ return {
9323
+ content: [
9324
+ ...skillInstructions ? [{ type: "text", text: skillInstructions }] : [],
9325
+ {
9326
+ type: "text",
9327
+ text: taskJson + budgetWarning
9328
+ }
9329
+ ]
9330
+ };
9331
+ } catch (err) {
9332
+ return errorResult(err);
9333
+ }
9334
+ }
9335
+ },
9336
+ {
9337
+ ...WORKER_TOOLS.UPDATE_FILE,
9338
+ execute: makeUpdateFileHandler(client)
9339
+ },
9340
+ {
9341
+ ...WORKER_TOOLS.COMPLETE_TASK,
9342
+ execute: makeCompleteTaskHandler(client)
9343
+ },
9344
+ {
9345
+ ...WORKER_TOOLS.SEND_MESSAGE,
9346
+ execute: makeSendMessageHandler(client)
9347
+ },
9348
+ {
9349
+ ...WORKER_TOOLS.PROPOSE,
9350
+ execute: makeProposeHandler(client)
9351
+ }
9352
+ ];
9353
+ }
9136
9354
 
9137
9355
  // src/offline-queue.ts
9138
9356
  var MAX_QUEUE_SIZE2 = 100;
@@ -9248,7 +9466,17 @@ var plugin = {
9248
9466
  setDeskFreeRuntime(api.runtime);
9249
9467
  api.registerChannel({ plugin: deskFreePlugin });
9250
9468
  api.registerTool(() => {
9251
- return createOrchestratorTools(api);
9469
+ const orchestratorTools = createOrchestratorTools(api) ?? [];
9470
+ const workerTools = createWorkerTools(api) ?? [];
9471
+ const seen = /* @__PURE__ */ new Set();
9472
+ const allTools = [];
9473
+ for (const tool of [...orchestratorTools, ...workerTools]) {
9474
+ if (!seen.has(tool.name)) {
9475
+ seen.add(tool.name);
9476
+ allTools.push(tool);
9477
+ }
9478
+ }
9479
+ return allTools.length > 0 ? allTools : null;
9252
9480
  });
9253
9481
  api.on("before_agent_start", (_event, ctx) => {
9254
9482
  return { prependContext: getDeskFreeContext(ctx.sessionKey) };