@questionbase/deskfree 0.3.0-alpha.21 → 0.3.0-alpha.22

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
@@ -3857,19 +3857,12 @@ var DeskFreeClient = class {
3857
3857
  return this.request("POST", "messages.update", input);
3858
3858
  }
3859
3859
  /**
3860
- * Send a text message (with optional attachments or suggestions) to a DeskFree conversation.
3860
+ * Send a text message to a DeskFree conversation.
3861
3861
  *
3862
- * @param input - Message content, optional userId, taskId, attachments, and suggestions
3862
+ * @param input - Message content, optional userId, taskId, attachments
3863
3863
  */
3864
3864
  async sendMessage(input) {
3865
- if (!input.content && !input.suggestions) {
3866
- throw new DeskFreeError(
3867
- "client",
3868
- "content",
3869
- "content or suggestions is required",
3870
- "Missing required parameter: provide content or suggestions."
3871
- );
3872
- }
3865
+ this.requireNonEmpty(input.content, "content");
3873
3866
  return this.request("POST", "messages.send", input);
3874
3867
  }
3875
3868
  /** Fetch paginated message history for a conversation. */
@@ -3894,11 +3887,25 @@ var DeskFreeClient = class {
3894
3887
  this.requireNonEmpty(input.taskId, "taskId");
3895
3888
  return this.request("POST", "tasks.claim", input);
3896
3889
  }
3897
- /** Update the deliverable (markdown or HTML content) for a task. */
3898
- async updateDeliverable(input) {
3890
+ /** Fetch a lightweight task summary by ID. Read-only, no side effects. */
3891
+ async getTask(input) {
3899
3892
  this.requireNonEmpty(input.taskId, "taskId");
3900
- this.requireNonEmpty(input.deliverable, "deliverable");
3901
- return this.request("POST", "tasks.updateDeliverable", input);
3893
+ return this.request("GET", "tasks.get", input);
3894
+ }
3895
+ /** Update the content of an existing file. */
3896
+ async updateFile(input) {
3897
+ this.requireNonEmpty(input.fileId, "fileId");
3898
+ this.requireNonEmpty(input.content, "content");
3899
+ return this.request("POST", "files.update", input);
3900
+ }
3901
+ /** Create a new persistent file. */
3902
+ async createFile(input) {
3903
+ this.requireNonEmpty(input.name, "name");
3904
+ return this.request("POST", "files.create", input);
3905
+ }
3906
+ /** List all files for this bot (metadata only, no content). */
3907
+ async listFiles() {
3908
+ return this.request("GET", "files.list", {});
3902
3909
  }
3903
3910
  /** Send an agent status update to DeskFree. */
3904
3911
  async statusUpdate(input) {
@@ -3923,21 +3930,6 @@ var DeskFreeClient = class {
3923
3930
  this.requireNonEmpty(input.taskId, "taskId");
3924
3931
  return this.request("POST", "tasks.complete", input);
3925
3932
  }
3926
- /** Suggest new tasks for the human to review and approve (via messages.send with suggestions). */
3927
- async suggestTasks(input) {
3928
- if (!input.tasks || input.tasks.length === 0) {
3929
- throw new DeskFreeError(
3930
- "client",
3931
- "tasks",
3932
- "tasks array is required and cannot be empty",
3933
- "Missing required parameter: tasks. Please provide at least one task to suggest."
3934
- );
3935
- }
3936
- return this.sendMessage({
3937
- suggestions: input.tasks,
3938
- taskId: input.taskId
3939
- });
3940
- }
3941
3933
  /** Suggest tasks via the dedicated bot/tasks.suggest endpoint. */
3942
3934
  async suggestTasksDedicated(input) {
3943
3935
  if (!input.suggestions || input.suggestions.length === 0) {
@@ -4429,6 +4421,20 @@ async function fetchAndSaveMedia(attachment) {
4429
4421
  }
4430
4422
  }
4431
4423
  }
4424
+ var MAX_INSTRUCTIONS_LENGTH = 500;
4425
+ function truncateAtWord(text, maxLen) {
4426
+ if (text.length <= maxLen) return text;
4427
+ const truncated = text.slice(0, maxLen);
4428
+ const lastSpace = truncated.lastIndexOf(" ");
4429
+ return (lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated) + "\u2026";
4430
+ }
4431
+ function buildBodyForAgent(task, content) {
4432
+ let prefix = `[Task thread: "${task.title}" | status: ${task.status}]`;
4433
+ if (task.instructions) {
4434
+ prefix += "\n" + truncateAtWord(task.instructions, MAX_INSTRUCTIONS_LENGTH);
4435
+ }
4436
+ return prefix + "\n\n" + content;
4437
+ }
4432
4438
  async function deliverMessageToAgent(ctx, message, client) {
4433
4439
  const runtime = getDeskFreeRuntime();
4434
4440
  const log = ctx.log ?? runtime.logging.createLogger("deskfree:deliver");
@@ -4471,9 +4477,20 @@ async function deliverMessageToAgent(ctx, message, client) {
4471
4477
  );
4472
4478
  }
4473
4479
  }
4480
+ let bodyForAgent;
4481
+ if (message.taskId) {
4482
+ try {
4483
+ const task = await client.getTask({ taskId: message.taskId });
4484
+ bodyForAgent = buildBodyForAgent(task, message.content);
4485
+ } catch (err) {
4486
+ const errMsg = err instanceof Error ? err.message : String(err);
4487
+ log.warn(`Failed to fetch task context for ${message.taskId}: ${errMsg}`);
4488
+ }
4489
+ }
4474
4490
  const msgCtx = runtime.channel.reply.finalizeInboundContext({
4475
4491
  Body: message.content,
4476
4492
  RawBody: message.content,
4493
+ ...bodyForAgent ? { BodyForAgent: bodyForAgent } : {},
4477
4494
  ChatType: "dm",
4478
4495
  Provider: "deskfree",
4479
4496
  Surface: "deskfree",
@@ -4585,6 +4602,7 @@ var wrapper_default = import_websocket.default;
4585
4602
  // src/gateway.ts
4586
4603
  var activeTaskId = null;
4587
4604
  var completedTaskId = null;
4605
+ var inboundThreadId = null;
4588
4606
  function setActiveTaskId(taskId) {
4589
4607
  if (taskId === null && activeTaskId !== null) {
4590
4608
  completedTaskId = activeTaskId;
@@ -4592,15 +4610,19 @@ function setActiveTaskId(taskId) {
4592
4610
  activeTaskId = taskId;
4593
4611
  }
4594
4612
  function getActiveTaskId() {
4595
- return activeTaskId ?? completedTaskId;
4613
+ return activeTaskId ?? completedTaskId ?? inboundThreadId;
4596
4614
  }
4597
4615
  function clearCompletedTaskId() {
4598
4616
  completedTaskId = null;
4599
4617
  }
4618
+ function setInboundThreadId(taskId) {
4619
+ inboundThreadId = taskId;
4620
+ }
4600
4621
  var PING_INTERVAL_MS = 5 * 60 * 1e3;
4601
4622
  var POLL_FALLBACK_INTERVAL_MS = 30 * 1e3;
4602
4623
  var WS_CONNECTION_TIMEOUT_MS = 30 * 1e3;
4603
4624
  var WS_PONG_TIMEOUT_MS = 10 * 1e3;
4625
+ var NOTIFY_DEBOUNCE_MS = 200;
4604
4626
  var BACKOFF_INITIAL_MS = 2e3;
4605
4627
  var BACKOFF_MAX_MS = 3e4;
4606
4628
  var BACKOFF_FACTOR = 1.8;
@@ -4824,6 +4846,7 @@ async function runWebSocketConnection(opts) {
4824
4846
  let pingInterval;
4825
4847
  let connectionTimer;
4826
4848
  let pongTimer;
4849
+ let notifyDebounceTimer;
4827
4850
  let isConnected = false;
4828
4851
  const cleanup = () => {
4829
4852
  if (pingInterval !== void 0) {
@@ -4838,6 +4861,10 @@ async function runWebSocketConnection(opts) {
4838
4861
  clearTimeout(pongTimer);
4839
4862
  pongTimer = void 0;
4840
4863
  }
4864
+ if (notifyDebounceTimer !== void 0) {
4865
+ clearTimeout(notifyDebounceTimer);
4866
+ notifyDebounceTimer = void 0;
4867
+ }
4841
4868
  };
4842
4869
  connectionTimer = setTimeout(() => {
4843
4870
  if (!isConnected) {
@@ -4918,15 +4945,21 @@ async function runWebSocketConnection(opts) {
4918
4945
  return;
4919
4946
  }
4920
4947
  if (msg.action === "notify") {
4921
- enqueuePoll(
4922
- client,
4923
- ctx,
4924
- () => cursor,
4925
- (c) => {
4926
- cursor = c ?? cursor;
4927
- },
4928
- log
4929
- );
4948
+ if (notifyDebounceTimer !== void 0) {
4949
+ clearTimeout(notifyDebounceTimer);
4950
+ }
4951
+ notifyDebounceTimer = setTimeout(() => {
4952
+ notifyDebounceTimer = void 0;
4953
+ enqueuePoll(
4954
+ client,
4955
+ ctx,
4956
+ () => cursor,
4957
+ (c) => {
4958
+ cursor = c ?? cursor;
4959
+ },
4960
+ log
4961
+ );
4962
+ }, NOTIFY_DEBOUNCE_MS);
4930
4963
  } else if (msg.action === "pong") {
4931
4964
  if (pongTimer !== void 0) {
4932
4965
  clearTimeout(pongTimer);
@@ -5123,6 +5156,7 @@ async function pollAndDeliver(client, ctx, cursor, log, account) {
5123
5156
  continue;
5124
5157
  }
5125
5158
  clearCompletedTaskId();
5159
+ setInboundThreadId(message.taskId ?? null);
5126
5160
  await deliverMessageToAgent(ctx, message, client);
5127
5161
  deliveredMessageIds.add(message.messageId);
5128
5162
  deliveredCount++;
@@ -7767,66 +7801,72 @@ var ORCHESTRATOR_TOOLS = {
7767
7801
  },
7768
7802
  START_TASK: {
7769
7803
  name: "deskfree_start_task",
7770
- description: "Claim a bot task (isWorking=false) and start working. Returns full context (instructions, deliverable, message history).",
7804
+ description: "Claim a bot task (isWorking=false) and start working. Returns full context (instructions, message history, and fileContext if the task has a linked file \u2014 use the file content as working context).",
7771
7805
  parameters: Type.Object({
7772
7806
  taskId: Type.String({ description: "Task UUID to claim" })
7773
7807
  })
7774
7808
  },
7775
- UPDATE_DELIVERABLE: {
7776
- name: "deskfree_update_deliverable",
7777
- description: 'Update task deliverable. Build incrementally as you work. Use format="html" when delivering rich web content (reports, dashboards, interactive pages); use format="markdown" (default) for everything else.',
7809
+ UPDATE_FILE: {
7810
+ name: "deskfree_update_file",
7811
+ description: `Update a file's content. Use this to save work to a persistent file linked to your task. Call incrementally as you build content. Use format="html" for rich web content; use format="markdown" (default) for everything else.`,
7778
7812
  parameters: Type.Object({
7779
- taskId: Type.String({ description: "Task UUID" }),
7780
- deliverable: Type.String({
7781
- description: "Deliverable content (markdown or HTML depending on format)"
7813
+ fileId: Type.String({ description: "File ID to update" }),
7814
+ content: Type.String({
7815
+ description: "Full file content (replaces previous)"
7782
7816
  }),
7783
- format: Type.Optional(
7817
+ contentFormat: Type.Optional(
7784
7818
  Type.Union([Type.Literal("markdown"), Type.Literal("html")], {
7785
- description: '"markdown" (default) for text/documents, "html" for rich web content. HTML is rendered in a sandboxed iframe.'
7819
+ description: '"markdown" (default) for text/documents, "html" for rich web content.'
7820
+ })
7821
+ )
7822
+ })
7823
+ },
7824
+ CREATE_FILE: {
7825
+ name: "deskfree_create_file",
7826
+ description: "Create a new persistent file. Use when your task produces a document that should persist beyond the task lifecycle (reports, specs, runbooks, etc.).",
7827
+ parameters: Type.Object({
7828
+ name: Type.String({ description: "File name (max 200 chars)" }),
7829
+ description: Type.Optional(
7830
+ Type.String({
7831
+ description: "Brief description of the file's purpose (max 2000 chars)"
7832
+ })
7833
+ ),
7834
+ content: Type.Optional(
7835
+ Type.String({ description: "Initial file content" })
7836
+ ),
7837
+ contentFormat: Type.Optional(
7838
+ Type.Union([Type.Literal("markdown"), Type.Literal("html")], {
7839
+ description: '"markdown" (default) or "html"'
7786
7840
  })
7787
7841
  )
7788
7842
  })
7789
7843
  },
7790
7844
  COMPLETE_TASK: {
7791
7845
  name: "deskfree_complete_task",
7792
- description: 'Finish a task. Outcome "done" = work complete for review. Outcome "blocked" = need human input. Both move to human.',
7846
+ description: 'Finish a task. Outcome "done" = work complete (summary required). Outcome "blocked" = need human input. Both move to human.',
7793
7847
  parameters: Type.Object({
7794
7848
  taskId: Type.String({ description: "Task UUID" }),
7795
7849
  outcome: Type.Union([Type.Literal("done"), Type.Literal("blocked")], {
7796
7850
  description: '"done" = work complete, "blocked" = need human input'
7797
- })
7851
+ }),
7852
+ summary: Type.Optional(
7853
+ Type.String({
7854
+ description: 'Brief summary of what was accomplished (required for outcome "done", max 2000 chars)'
7855
+ })
7856
+ )
7798
7857
  })
7799
7858
  },
7800
7859
  SEND_MESSAGE: {
7801
7860
  name: "deskfree_send_message",
7802
- description: "Send a message in the task thread (progress update, question, status report). Can also suggest follow-up tasks for human review by providing the suggestions parameter instead of content.",
7861
+ description: "Send a message in the task thread (progress update, question, status report).",
7803
7862
  parameters: Type.Object({
7804
- content: Type.Optional(
7805
- Type.String({
7806
- description: "Message content. Required unless suggestions is provided."
7807
- })
7808
- ),
7863
+ content: Type.String({
7864
+ description: "Message content."
7865
+ }),
7809
7866
  taskId: Type.Optional(
7810
7867
  Type.String({
7811
7868
  description: "Task UUID (optional if context provides it)"
7812
7869
  })
7813
- ),
7814
- suggestions: Type.Optional(
7815
- Type.Array(
7816
- Type.Object({
7817
- title: Type.String({ description: "Task title (max 200 chars)" }),
7818
- instructions: Type.Optional(
7819
- Type.String({
7820
- description: "Detailed instructions for the suggested task"
7821
- })
7822
- )
7823
- }),
7824
- {
7825
- description: "Suggest tasks for human review (1-10). The human will see approve/reject buttons for each. Provide this instead of content.",
7826
- minItems: 1,
7827
- maxItems: 10
7828
- }
7829
- )
7830
7870
  )
7831
7871
  })
7832
7872
  },
@@ -7849,15 +7889,35 @@ var ORCHESTRATOR_TOOLS = {
7849
7889
  description: "Estimated token cost \u2014 consider files to read, reasoning, output"
7850
7890
  })
7851
7891
  ),
7852
- dependsOn: Type.Optional(
7853
- Type.Array(Type.String(), {
7854
- description: "Task IDs this suggestion depends on (blocks claiming until those are done)"
7855
- })
7856
- ),
7857
7892
  initiativeId: Type.Optional(
7858
7893
  Type.String({
7859
7894
  description: "Link to existing initiative ID \u2014 set when this task belongs to an active initiative"
7860
7895
  })
7896
+ ),
7897
+ scheduledFor: Type.Optional(
7898
+ Type.String({
7899
+ description: "ISO-8601 date for when this task should become available. Use for future-dated or recurring work."
7900
+ })
7901
+ ),
7902
+ fileId: Type.Optional(
7903
+ Type.String({
7904
+ description: "Link to an existing file ID \u2014 the bot will receive the file content when claiming this task"
7905
+ })
7906
+ ),
7907
+ newFile: Type.Optional(
7908
+ Type.Object(
7909
+ {
7910
+ name: Type.String({ description: "File name (max 200 chars)" }),
7911
+ description: Type.Optional(
7912
+ Type.String({
7913
+ description: "Brief description of the file's purpose"
7914
+ })
7915
+ )
7916
+ },
7917
+ {
7918
+ description: "Create a new file and link it to this task \u2014 use when the task will produce a persistent document"
7919
+ }
7920
+ )
7861
7921
  )
7862
7922
  }),
7863
7923
  {
@@ -7889,7 +7949,7 @@ var ORCHESTRATOR_TOOLS = {
7889
7949
  {
7890
7950
  description: 'Propose new initiatives (optional). Created with "suggested" status \u2014 human approves or rejects independently.',
7891
7951
  minItems: 1,
7892
- maxItems: 10
7952
+ maxItems: 5
7893
7953
  }
7894
7954
  )
7895
7955
  )
@@ -7944,9 +8004,12 @@ var ORCHESTRATOR_TOOLS = {
7944
8004
  }
7945
8005
  };
7946
8006
  var WORKER_TOOLS = {
7947
- UPDATE_DELIVERABLE: ORCHESTRATOR_TOOLS.UPDATE_DELIVERABLE,
8007
+ UPDATE_FILE: ORCHESTRATOR_TOOLS.UPDATE_FILE,
8008
+ CREATE_FILE: ORCHESTRATOR_TOOLS.CREATE_FILE,
7948
8009
  COMPLETE_TASK: ORCHESTRATOR_TOOLS.COMPLETE_TASK,
7949
8010
  SEND_MESSAGE: ORCHESTRATOR_TOOLS.SEND_MESSAGE,
8011
+ SUGGEST_TASKS: ORCHESTRATOR_TOOLS.SUGGEST_TASKS,
8012
+ CLAIM_EVALUATION: ORCHESTRATOR_TOOLS.CLAIM_EVALUATION,
7950
8013
  SUBMIT_EVALUATION: ORCHESTRATOR_TOOLS.SUBMIT_EVALUATION
7951
8014
  };
7952
8015
  var CHANNEL_META = {
@@ -8506,20 +8569,20 @@ var deskFreePlugin = {
8506
8569
  // src/context.ts
8507
8570
  var DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 The Work Loop
8508
8571
  Always read the deskfree skill (SKILL.md) at startup. Follow the suggest-first work loop:
8509
- 1. **Check state** \u2192 \`deskfree_state\` \u2014 see what needs attention, read ways of working.
8510
- 2. **Suggest tasks** \u2192 \`deskfree_suggest_tasks\` \u2014 ALL work requires human-approved tasks first.
8511
- 3. **Claim a task** \u2192 \`deskfree_start_task\` \u2014 read instructions + parent context carefully.
8512
- 4. **Do the work** \u2192 \`deskfree_update_deliverable\` incrementally from the start.
8572
+ 1. **Check state** \u2192 \`deskfree_state\` \u2014 see what needs attention, read ways of working, check files.
8573
+ 2. **Suggest tasks** \u2192 \`deskfree_suggest_tasks\` \u2014 ALL work requires human-approved tasks first. Use \`newFile\` to pre-create a file for tasks that will produce persistent documents.
8574
+ 3. **Claim a task** \u2192 \`deskfree_start_task\` \u2014 read instructions + parent context. If task has a linked file, its content is in \`fileContext\`.
8575
+ 4. **Do the work** \u2192 use \`deskfree_update_file\` to save work to linked files incrementally. Create new files with \`deskfree_create_file\` when needed.
8513
8576
  5. **Suggest follow-ups** \u2192 if work reveals more to do, suggest them (strongest when you have full context).
8514
- 6. **Complete** \u2192 \`deskfree_complete_task\` \u2014 deliverable required for "done".
8577
+ 6. **Complete** \u2192 \`deskfree_complete_task\` \u2014 summary required for outcome "done".
8515
8578
  7. **Evaluate** \u2192 check \`pendingEvaluations\` and update ways of working.
8516
8579
 
8517
8580
  Key principles:
8518
- - You're building a chain. Your instructions become someone else's brief. Your deliverable becomes someone else's context.
8581
+ - You're building a chain. Your instructions become someone else's brief. Your file output becomes someone else's context.
8519
8582
  - Write instructions as if briefing a contractor who has never seen the codebase.
8520
- - MUST update deliverable before completing with outcome "done".
8583
+ - MUST provide summary when completing with outcome "done".
8521
8584
  - Estimate token cost per suggestion \u2014 consider files to read, reasoning, output.
8522
- - Sub-agents get 4 tools: update_deliverable, complete_task, send_message, submit_evaluation.`;
8585
+ - Sub-agents get 7 tools: update_file, create_file, complete_task, send_message, suggest_tasks, claim_evaluation, submit_evaluation.`;
8523
8586
  function getDeskFreeContext() {
8524
8587
  return `${DESKFREE_AGENT_DIRECTIVE}
8525
8588
 
@@ -8558,15 +8621,7 @@ function formatTaskResponse(task, summary, nextActions) {
8558
8621
  content: [
8559
8622
  {
8560
8623
  type: "text",
8561
- text: JSON.stringify(
8562
- {
8563
- summary,
8564
- nextActions,
8565
- task
8566
- },
8567
- null,
8568
- 2
8569
- )
8624
+ text: JSON.stringify({ summary, nextActions, task }, null, 2)
8570
8625
  }
8571
8626
  ]
8572
8627
  };
@@ -8577,11 +8632,7 @@ function formatConfirmation(summary, nextActions, data) {
8577
8632
  {
8578
8633
  type: "text",
8579
8634
  text: JSON.stringify(
8580
- {
8581
- summary,
8582
- nextActions,
8583
- ...data ? { data } : {}
8584
- },
8635
+ { summary, nextActions, ...data ? { data } : {} },
8585
8636
  null,
8586
8637
  2
8587
8638
  )
@@ -8642,14 +8693,7 @@ function errorResult(err) {
8642
8693
  content: [
8643
8694
  {
8644
8695
  type: "text",
8645
- text: JSON.stringify(
8646
- {
8647
- error: true,
8648
- message: rawMessage
8649
- },
8650
- null,
8651
- 2
8652
- )
8696
+ text: JSON.stringify({ error: true, message: rawMessage }, null, 2)
8653
8697
  }
8654
8698
  ],
8655
8699
  isError: true
@@ -8666,9 +8710,7 @@ function validateStringParam(params, key, required) {
8666
8710
  }
8667
8711
  const value = params[key];
8668
8712
  if (value == null) {
8669
- if (required) {
8670
- throw new Error(`Required parameter '${key}' is missing`);
8671
- }
8713
+ if (required) throw new Error(`Required parameter '${key}' is missing`);
8672
8714
  return void 0;
8673
8715
  }
8674
8716
  if (typeof value !== "string") {
@@ -8690,9 +8732,7 @@ function validateEnumParam(params, key, allowedValues, required) {
8690
8732
  }
8691
8733
  const value = params[key];
8692
8734
  if (value == null) {
8693
- if (required) {
8694
- throw new Error(`Required parameter '${key}' is missing`);
8695
- }
8735
+ if (required) throw new Error(`Required parameter '${key}' is missing`);
8696
8736
  return void 0;
8697
8737
  }
8698
8738
  if (typeof value !== "string") {
@@ -8708,6 +8748,298 @@ function validateEnumParam(params, key, allowedValues, required) {
8708
8748
  }
8709
8749
  return value;
8710
8750
  }
8751
+ function parseSuggestions(raw) {
8752
+ if (!Array.isArray(raw) || raw.length === 0) {
8753
+ throw new Error('Parameter "suggestions" must be a non-empty array');
8754
+ }
8755
+ return raw.map((s, i) => {
8756
+ if (typeof s !== "object" || s === null) {
8757
+ throw new Error(`suggestions[${i}] must be an object`);
8758
+ }
8759
+ const item = s;
8760
+ const title = item["title"];
8761
+ if (typeof title !== "string" || title.trim() === "") {
8762
+ throw new Error(`suggestions[${i}].title must be a non-empty string`);
8763
+ }
8764
+ let newFile;
8765
+ if (item["newFile"] && typeof item["newFile"] === "object") {
8766
+ const nf = item["newFile"];
8767
+ if (typeof nf["name"] === "string" && nf["name"].trim()) {
8768
+ newFile = {
8769
+ name: nf["name"].trim(),
8770
+ description: typeof nf["description"] === "string" ? nf["description"] : void 0
8771
+ };
8772
+ }
8773
+ }
8774
+ return {
8775
+ title: title.trim(),
8776
+ instructions: typeof item["instructions"] === "string" ? item["instructions"] : void 0,
8777
+ estimatedTokens: typeof item["estimatedTokens"] === "number" ? item["estimatedTokens"] : void 0,
8778
+ initiativeId: typeof item["initiativeId"] === "string" ? item["initiativeId"] : void 0,
8779
+ scheduledFor: typeof item["scheduledFor"] === "string" ? item["scheduledFor"] : void 0,
8780
+ fileId: typeof item["fileId"] === "string" ? item["fileId"] : void 0,
8781
+ newFile
8782
+ };
8783
+ });
8784
+ }
8785
+ function parseInitiativeSuggestions(raw) {
8786
+ if (!Array.isArray(raw) || raw.length === 0) return void 0;
8787
+ return raw.map((s, i) => {
8788
+ if (typeof s !== "object" || s === null) {
8789
+ throw new Error(`initiativeSuggestions[${i}] must be an object`);
8790
+ }
8791
+ const item = s;
8792
+ const title = item["title"];
8793
+ if (typeof title !== "string" || title.trim() === "") {
8794
+ throw new Error(
8795
+ `initiativeSuggestions[${i}].title must be a non-empty string`
8796
+ );
8797
+ }
8798
+ const content = item["content"];
8799
+ if (typeof content !== "string") {
8800
+ throw new Error(`initiativeSuggestions[${i}].content must be a string`);
8801
+ }
8802
+ return {
8803
+ title: title.trim(),
8804
+ content,
8805
+ taskRefs: Array.isArray(item["taskRefs"]) ? item["taskRefs"] : void 0
8806
+ };
8807
+ });
8808
+ }
8809
+ function parseEvaluationSection(raw, name) {
8810
+ if (typeof raw !== "object" || raw === null) {
8811
+ throw new Error(`Parameter "${name}" must be an object`);
8812
+ }
8813
+ const obj = raw;
8814
+ if (typeof obj["hasChanges"] !== "boolean") {
8815
+ throw new Error(`Parameter "${name}.hasChanges" must be a boolean`);
8816
+ }
8817
+ return {
8818
+ hasChanges: obj["hasChanges"],
8819
+ updatedContent: typeof obj["updatedContent"] === "string" ? obj["updatedContent"] : void 0
8820
+ };
8821
+ }
8822
+ function makeUpdateFileHandler(client) {
8823
+ return async (_id, params) => {
8824
+ try {
8825
+ const fileId = validateStringParam(params, "fileId", true);
8826
+ const content = validateStringParam(params, "content", true);
8827
+ const contentFormat = validateEnumParam(
8828
+ params,
8829
+ "contentFormat",
8830
+ ["markdown", "html"],
8831
+ false
8832
+ );
8833
+ await client.updateFile({ fileId, content, contentFormat });
8834
+ return formatConfirmation(`Updated file ${fileId}`, [
8835
+ "File content has been saved",
8836
+ "Complete the task with deskfree_complete_task when ready"
8837
+ ]);
8838
+ } catch (err) {
8839
+ return errorResult(err);
8840
+ }
8841
+ };
8842
+ }
8843
+ function makeCreateFileHandler(client) {
8844
+ return async (_id, params) => {
8845
+ try {
8846
+ const name = validateStringParam(params, "name", true);
8847
+ const description = validateStringParam(params, "description", false);
8848
+ const content = validateStringParam(params, "content", false);
8849
+ const contentFormat = validateEnumParam(
8850
+ params,
8851
+ "contentFormat",
8852
+ ["markdown", "html"],
8853
+ false
8854
+ );
8855
+ const result = await client.createFile({
8856
+ name,
8857
+ description,
8858
+ content,
8859
+ contentFormat
8860
+ });
8861
+ return formatConfirmation(
8862
+ `Created file "${name}"`,
8863
+ [
8864
+ `File ID: ${result.fileId}`,
8865
+ "Use deskfree_update_file to update its content"
8866
+ ],
8867
+ result
8868
+ );
8869
+ } catch (err) {
8870
+ return errorResult(err);
8871
+ }
8872
+ };
8873
+ }
8874
+ function makeCompleteTaskHandler(client) {
8875
+ return async (_id, params) => {
8876
+ try {
8877
+ const taskId = validateStringParam(params, "taskId", true);
8878
+ const outcome = validateEnumParam(
8879
+ params,
8880
+ "outcome",
8881
+ ["done", "blocked"],
8882
+ true
8883
+ );
8884
+ const summary = validateStringParam(params, "summary", false);
8885
+ const result = await client.completeTask({ taskId, outcome, summary });
8886
+ setActiveTaskId(null);
8887
+ const summaryVerb = outcome === "done" ? "completed" : "blocked";
8888
+ const icon = outcome === "done" ? "\u2705" : "\u{1F6AB}";
8889
+ await client.sendMessage({
8890
+ content: `${icon} Task ${summaryVerb}: "${result.title}"`
8891
+ }).catch(() => {
8892
+ });
8893
+ return formatTaskResponse(
8894
+ result,
8895
+ `Task "${result.title}" marked as ${summaryVerb} \u2014 waiting for human`,
8896
+ [
8897
+ outcome === "done" ? "Human will review your work summary" : "Human will review the blocker and provide guidance",
8898
+ "Use deskfree_state to check for other tasks"
8899
+ ]
8900
+ );
8901
+ } catch (err) {
8902
+ return errorResult(err);
8903
+ }
8904
+ };
8905
+ }
8906
+ function makeSendMessageHandler(client) {
8907
+ return async (_id, params) => {
8908
+ try {
8909
+ const content = validateStringParam(params, "content", true);
8910
+ const taskId = validateStringParam(params, "taskId", false);
8911
+ await client.sendMessage({ content, taskId });
8912
+ return formatConfirmation(
8913
+ `Message sent${taskId ? ` to task ${taskId}` : ""}`,
8914
+ [
8915
+ "Message delivered to the human",
8916
+ taskId ? "Continue working on the task or wait for response" : "Check for response with task messages"
8917
+ ]
8918
+ );
8919
+ } catch (err) {
8920
+ return errorResult(err);
8921
+ }
8922
+ };
8923
+ }
8924
+ function makeSuggestTasksHandler(client) {
8925
+ return async (_id, params) => {
8926
+ try {
8927
+ const suggestions = parseSuggestions(params?.suggestions);
8928
+ const parentTaskId = validateStringParam(params, "parentTaskId", false);
8929
+ const initiativeSuggestions = parseInitiativeSuggestions(
8930
+ params?.initiativeSuggestions
8931
+ );
8932
+ const result = await client.suggestTasksDedicated({
8933
+ suggestions,
8934
+ parentTaskId,
8935
+ initiativeSuggestions
8936
+ });
8937
+ const initiativeCount = initiativeSuggestions?.length ?? 0;
8938
+ const summaryParts = [
8939
+ `Suggested ${suggestions.length} task${suggestions.length === 1 ? "" : "s"} for human approval`,
8940
+ ...initiativeCount > 0 ? [
8941
+ `and ${initiativeCount} initiative${initiativeCount === 1 ? "" : "s"}`
8942
+ ] : []
8943
+ ];
8944
+ return formatConfirmation(
8945
+ summaryParts.join(" "),
8946
+ [
8947
+ 'Tasks created with "suggested" status \u2014 human will approve or reject each',
8948
+ ...initiativeCount > 0 ? [
8949
+ "Initiative suggestions created \u2014 human will approve or reject independently"
8950
+ ] : [],
8951
+ "Use deskfree_state to check for approved tasks"
8952
+ ],
8953
+ result
8954
+ );
8955
+ } catch (err) {
8956
+ return errorResult(err);
8957
+ }
8958
+ };
8959
+ }
8960
+ function makeClaimEvaluationHandler(client) {
8961
+ return async (_id, params) => {
8962
+ try {
8963
+ const taskId = validateStringParam(params, "taskId", true);
8964
+ const result = await client.claimEvaluation({ taskId });
8965
+ if (!result) {
8966
+ return formatConfirmation(
8967
+ "Evaluation already claimed by another process",
8968
+ ["Use deskfree_state to check for other pending evaluations"]
8969
+ );
8970
+ }
8971
+ const state = await client.getState().catch(() => null);
8972
+ return {
8973
+ content: [
8974
+ {
8975
+ type: "text",
8976
+ text: JSON.stringify(
8977
+ {
8978
+ summary: `Claimed evaluation for task "${result.task.title}"`,
8979
+ nextActions: [
8980
+ "Review the task messages, current global ways of working, and initiative content (if present)",
8981
+ "Decide what to update: globalWoW (universal patterns), initiative (area-specific learnings), both, or neither",
8982
+ "Call deskfree_submit_evaluation with your analysis"
8983
+ ],
8984
+ task: result.task,
8985
+ waysOfWorking: result.waysOfWorking,
8986
+ currentVersion: result.currentVersion,
8987
+ messages: result.messages,
8988
+ ...result.initiative ? { initiative: result.initiative } : {},
8989
+ ...state ? { workspaceState: state } : {}
8990
+ },
8991
+ null,
8992
+ 2
8993
+ )
8994
+ }
8995
+ ]
8996
+ };
8997
+ } catch (err) {
8998
+ return errorResult(err);
8999
+ }
9000
+ };
9001
+ }
9002
+ function makeSubmitEvaluationHandler(client, followUpHint) {
9003
+ return async (_id, params) => {
9004
+ try {
9005
+ const taskId = validateStringParam(params, "taskId", true);
9006
+ const reasoning = validateStringParam(params, "reasoning", true);
9007
+ const globalWoW = parseEvaluationSection(params?.globalWoW, "globalWoW");
9008
+ const initiative = parseEvaluationSection(
9009
+ params?.initiative,
9010
+ "initiative"
9011
+ );
9012
+ const result = await client.submitEvaluation({
9013
+ taskId,
9014
+ reasoning,
9015
+ globalWoW,
9016
+ initiative
9017
+ });
9018
+ const parts = [];
9019
+ if (globalWoW.hasChanges)
9020
+ parts.push(`global WoW \u2192 v${result.globalVersion}`);
9021
+ if (initiative.hasChanges && result.initiativeVersion !== void 0) {
9022
+ parts.push(`initiative \u2192 v${result.initiativeVersion}`);
9023
+ }
9024
+ const messageContent = parts.length > 0 ? `\u{1F4DD} Updated ${parts.join(", ")}: ${reasoning}` : `\u{1F4DD} No updates to ways of working: ${reasoning}`;
9025
+ await client.sendMessage({ content: messageContent, taskId }).catch(() => {
9026
+ });
9027
+ const summaryParts = [];
9028
+ if (globalWoW.hasChanges)
9029
+ summaryParts.push(`global WoW v${result.globalVersion}`);
9030
+ if (initiative.hasChanges && result.initiativeVersion !== void 0) {
9031
+ summaryParts.push(`initiative v${result.initiativeVersion}`);
9032
+ }
9033
+ return formatConfirmation(
9034
+ summaryParts.length > 0 ? `Evaluation complete \u2014 updated ${summaryParts.join(", ")}` : "Evaluation complete \u2014 no changes needed",
9035
+ [followUpHint],
9036
+ result
9037
+ );
9038
+ } catch (err) {
9039
+ return errorResult(err);
9040
+ }
9041
+ };
9042
+ }
8711
9043
  function createOrchestratorTools(api) {
8712
9044
  const account = resolveAccountFromConfig(api);
8713
9045
  if (!account) return null;
@@ -8742,8 +9074,10 @@ function createOrchestratorTools(api) {
8742
9074
  summary: `Claimed task "${result.title}" \u2014 full context loaded`,
8743
9075
  nextActions: [
8744
9076
  "Read the instructions and message history carefully",
8745
- "Update deliverable incrementally with deskfree_update_deliverable",
8746
- "Complete with deskfree_complete_task when done"
9077
+ ...result.fileContext ? [
9078
+ `Task has a linked file "${result.fileContext.name}" (ID: ${result.fileContext.fileId}) \u2014 use deskfree_update_file to save your work to it`
9079
+ ] : [],
9080
+ "Complete with deskfree_complete_task when done (summary required)"
8747
9081
  ],
8748
9082
  task: result
8749
9083
  },
@@ -8759,304 +9093,35 @@ function createOrchestratorTools(api) {
8759
9093
  }
8760
9094
  },
8761
9095
  {
8762
- ...ORCHESTRATOR_TOOLS.UPDATE_DELIVERABLE,
8763
- async execute(_id, params) {
8764
- try {
8765
- const taskId = validateStringParam(params, "taskId", true);
8766
- const deliverable = validateStringParam(params, "deliverable", true);
8767
- const format = validateEnumParam(
8768
- params,
8769
- "format",
8770
- ["markdown", "html"],
8771
- false
8772
- );
8773
- await client.updateDeliverable({ taskId, deliverable, format });
8774
- return formatConfirmation(`Updated deliverable for task ${taskId}`, [
8775
- "Deliverable has been saved",
8776
- "Complete with deskfree_complete_task when ready"
8777
- ]);
8778
- } catch (err) {
8779
- return errorResult(err);
8780
- }
8781
- }
9096
+ ...ORCHESTRATOR_TOOLS.UPDATE_FILE,
9097
+ execute: makeUpdateFileHandler(client)
9098
+ },
9099
+ {
9100
+ ...ORCHESTRATOR_TOOLS.CREATE_FILE,
9101
+ execute: makeCreateFileHandler(client)
8782
9102
  },
8783
9103
  {
8784
9104
  ...ORCHESTRATOR_TOOLS.COMPLETE_TASK,
8785
- async execute(_id, params) {
8786
- try {
8787
- const taskId = validateStringParam(params, "taskId", true);
8788
- const outcome = validateEnumParam(
8789
- params,
8790
- "outcome",
8791
- ["done", "blocked"],
8792
- true
8793
- );
8794
- const result = await client.completeTask({ taskId, outcome });
8795
- setActiveTaskId(null);
8796
- const summaryVerb = outcome === "done" ? "completed" : "blocked";
8797
- const icon = outcome === "done" ? "\u2705" : "\u{1F6AB}";
8798
- await client.sendMessage({
8799
- content: `${icon} Task ${summaryVerb}: "${result.title}"`
8800
- }).catch(() => {
8801
- });
8802
- return formatTaskResponse(
8803
- result,
8804
- `Task "${result.title}" marked as ${summaryVerb} \u2014 waiting for human`,
8805
- [
8806
- outcome === "done" ? "Human will review the deliverable" : "Human will review the blocker and provide guidance",
8807
- "Use deskfree_state to check for other tasks"
8808
- ]
8809
- );
8810
- } catch (err) {
8811
- return errorResult(err);
8812
- }
8813
- }
9105
+ execute: makeCompleteTaskHandler(client)
8814
9106
  },
8815
9107
  {
8816
9108
  ...ORCHESTRATOR_TOOLS.SEND_MESSAGE,
8817
- async execute(_id, params) {
8818
- try {
8819
- const content = validateStringParam(params, "content", false);
8820
- const taskId = validateStringParam(params, "taskId", false);
8821
- const rawSuggestions = params?.suggestions;
8822
- let suggestions;
8823
- if (Array.isArray(rawSuggestions) && rawSuggestions.length > 0) {
8824
- suggestions = rawSuggestions.map((t, i) => {
8825
- if (typeof t !== "object" || t === null) {
8826
- throw new Error(`suggestions[${i}] must be an object`);
8827
- }
8828
- const item = t;
8829
- const title = item["title"];
8830
- if (typeof title !== "string" || title.trim() === "") {
8831
- throw new Error(
8832
- `suggestions[${i}].title must be a non-empty string`
8833
- );
8834
- }
8835
- const instructions = item["instructions"];
8836
- return {
8837
- title: title.trim(),
8838
- instructions: typeof instructions === "string" ? instructions : void 0
8839
- };
8840
- });
8841
- }
8842
- if (!content && !suggestions) {
8843
- throw new Error(
8844
- 'Either "content" or "suggestions" parameter is required'
8845
- );
8846
- }
8847
- if (suggestions) {
8848
- await client.sendMessage({ suggestions, taskId });
8849
- return formatConfirmation(
8850
- `Suggested ${suggestions.length} task${suggestions.length === 1 ? "" : "s"} for human review`,
8851
- [
8852
- "The human will see approve/reject buttons for each suggestion",
8853
- "Continue working on the current task"
8854
- ]
8855
- );
8856
- }
8857
- await client.sendMessage({ content, taskId });
8858
- return formatConfirmation(
8859
- `Message sent${taskId ? ` to task ${taskId}` : ""}`,
8860
- [
8861
- "Message delivered to the human",
8862
- taskId ? "Continue working on the task or wait for response" : "Check for response with task messages"
8863
- ]
8864
- );
8865
- } catch (err) {
8866
- return errorResult(err);
8867
- }
8868
- }
9109
+ execute: makeSendMessageHandler(client)
8869
9110
  },
8870
9111
  {
8871
9112
  ...ORCHESTRATOR_TOOLS.SUGGEST_TASKS,
8872
- async execute(_id, params) {
8873
- try {
8874
- const rawSuggestions = params?.suggestions;
8875
- if (!Array.isArray(rawSuggestions) || rawSuggestions.length === 0) {
8876
- throw new Error(
8877
- 'Parameter "suggestions" must be a non-empty array'
8878
- );
8879
- }
8880
- const suggestions = rawSuggestions.map((s, i) => {
8881
- if (typeof s !== "object" || s === null) {
8882
- throw new Error(`suggestions[${i}] must be an object`);
8883
- }
8884
- const item = s;
8885
- const title = item["title"];
8886
- if (typeof title !== "string" || title.trim() === "") {
8887
- throw new Error(
8888
- `suggestions[${i}].title must be a non-empty string`
8889
- );
8890
- }
8891
- return {
8892
- title: title.trim(),
8893
- instructions: typeof item["instructions"] === "string" ? item["instructions"] : void 0,
8894
- estimatedTokens: typeof item["estimatedTokens"] === "number" ? item["estimatedTokens"] : void 0,
8895
- dependsOn: Array.isArray(item["dependsOn"]) ? item["dependsOn"] : void 0,
8896
- initiativeId: typeof item["initiativeId"] === "string" ? item["initiativeId"] : void 0
8897
- };
8898
- });
8899
- const parentTaskId = validateStringParam(
8900
- params,
8901
- "parentTaskId",
8902
- false
8903
- );
8904
- const rawInitiativeSuggestions = params?.initiativeSuggestions;
8905
- let initiativeSuggestions;
8906
- if (Array.isArray(rawInitiativeSuggestions) && rawInitiativeSuggestions.length > 0) {
8907
- initiativeSuggestions = rawInitiativeSuggestions.map(
8908
- (s, i) => {
8909
- if (typeof s !== "object" || s === null) {
8910
- throw new Error(`initiativeSuggestions[${i}] must be an object`);
8911
- }
8912
- const item = s;
8913
- const title = item["title"];
8914
- if (typeof title !== "string" || title.trim() === "") {
8915
- throw new Error(
8916
- `initiativeSuggestions[${i}].title must be a non-empty string`
8917
- );
8918
- }
8919
- const content = item["content"];
8920
- if (typeof content !== "string") {
8921
- throw new Error(
8922
- `initiativeSuggestions[${i}].content must be a string`
8923
- );
8924
- }
8925
- return {
8926
- title: title.trim(),
8927
- content,
8928
- taskRefs: Array.isArray(item["taskRefs"]) ? item["taskRefs"] : void 0
8929
- };
8930
- }
8931
- );
8932
- }
8933
- const result = await client.suggestTasksDedicated({
8934
- suggestions,
8935
- parentTaskId,
8936
- initiativeSuggestions
8937
- });
8938
- const initiativeCount = initiativeSuggestions?.length ?? 0;
8939
- const summaryParts = [
8940
- `Suggested ${suggestions.length} task${suggestions.length === 1 ? "" : "s"} for human approval`,
8941
- ...initiativeCount > 0 ? [
8942
- `and ${initiativeCount} initiative${initiativeCount === 1 ? "" : "s"}`
8943
- ] : []
8944
- ];
8945
- return formatConfirmation(
8946
- summaryParts.join(" "),
8947
- [
8948
- 'Tasks created with "suggested" status \u2014 human will approve or reject each',
8949
- ...initiativeCount > 0 ? ["Initiative suggestions created \u2014 human will approve or reject independently"] : [],
8950
- "Use deskfree_state to check for approved tasks"
8951
- ],
8952
- result
8953
- );
8954
- } catch (err) {
8955
- return errorResult(err);
8956
- }
8957
- }
9113
+ execute: makeSuggestTasksHandler(client)
8958
9114
  },
8959
9115
  {
8960
9116
  ...ORCHESTRATOR_TOOLS.CLAIM_EVALUATION,
8961
- async execute(_id, params) {
8962
- try {
8963
- const taskId = validateStringParam(params, "taskId", true);
8964
- const result = await client.claimEvaluation({ taskId });
8965
- if (!result) {
8966
- return formatConfirmation(
8967
- "Evaluation already claimed by another process",
8968
- ["Use deskfree_state to check for other pending evaluations"]
8969
- );
8970
- }
8971
- return {
8972
- content: [
8973
- {
8974
- type: "text",
8975
- text: JSON.stringify(
8976
- {
8977
- summary: `Claimed evaluation for task "${result.task.title}"`,
8978
- nextActions: [
8979
- "Review the task messages, current global ways of working, and initiative content (if present)",
8980
- "Decide what to update: globalWoW (universal patterns), initiative (area-specific learnings), both, or neither",
8981
- "Call deskfree_submit_evaluation with your analysis"
8982
- ],
8983
- task: result.task,
8984
- waysOfWorking: result.waysOfWorking,
8985
- currentVersion: result.currentVersion,
8986
- messages: result.messages,
8987
- ...result.initiative ? { initiative: result.initiative } : {}
8988
- },
8989
- null,
8990
- 2
8991
- )
8992
- }
8993
- ]
8994
- };
8995
- } catch (err) {
8996
- return errorResult(err);
8997
- }
8998
- }
9117
+ execute: makeClaimEvaluationHandler(client)
8999
9118
  },
9000
9119
  {
9001
9120
  ...ORCHESTRATOR_TOOLS.SUBMIT_EVALUATION,
9002
- async execute(_id, params) {
9003
- try {
9004
- const taskId = validateStringParam(params, "taskId", true);
9005
- const reasoning = validateStringParam(params, "reasoning", true);
9006
- const rawGlobalWoW = params?.globalWoW;
9007
- if (typeof rawGlobalWoW !== "object" || rawGlobalWoW === null) {
9008
- throw new Error('Parameter "globalWoW" must be an object');
9009
- }
9010
- const globalWoWObj = rawGlobalWoW;
9011
- if (typeof globalWoWObj["hasChanges"] !== "boolean") {
9012
- throw new Error('Parameter "globalWoW.hasChanges" must be a boolean');
9013
- }
9014
- const globalWoW = {
9015
- hasChanges: globalWoWObj["hasChanges"],
9016
- updatedContent: typeof globalWoWObj["updatedContent"] === "string" ? globalWoWObj["updatedContent"] : void 0
9017
- };
9018
- const rawInitiative = params?.initiative;
9019
- if (typeof rawInitiative !== "object" || rawInitiative === null) {
9020
- throw new Error('Parameter "initiative" must be an object');
9021
- }
9022
- const initiativeObj = rawInitiative;
9023
- if (typeof initiativeObj["hasChanges"] !== "boolean") {
9024
- throw new Error('Parameter "initiative.hasChanges" must be a boolean');
9025
- }
9026
- const initiative = {
9027
- hasChanges: initiativeObj["hasChanges"],
9028
- updatedContent: typeof initiativeObj["updatedContent"] === "string" ? initiativeObj["updatedContent"] : void 0
9029
- };
9030
- const result = await client.submitEvaluation({
9031
- taskId,
9032
- reasoning,
9033
- globalWoW,
9034
- initiative
9035
- });
9036
- const parts = [];
9037
- if (globalWoW.hasChanges) {
9038
- parts.push(`global WoW \u2192 v${result.globalVersion}`);
9039
- }
9040
- if (initiative.hasChanges && result.initiativeVersion !== void 0) {
9041
- parts.push(`initiative \u2192 v${result.initiativeVersion}`);
9042
- }
9043
- const messageContent = parts.length > 0 ? `\u{1F4DD} Updated ${parts.join(", ")}: ${reasoning}` : `\u{1F4DD} No updates to ways of working: ${reasoning}`;
9044
- await client.sendMessage({ content: messageContent, taskId }).catch(() => {
9045
- });
9046
- const summaryParts = [];
9047
- if (globalWoW.hasChanges) summaryParts.push(`global WoW v${result.globalVersion}`);
9048
- if (initiative.hasChanges && result.initiativeVersion !== void 0) {
9049
- summaryParts.push(`initiative v${result.initiativeVersion}`);
9050
- }
9051
- return formatConfirmation(
9052
- summaryParts.length > 0 ? `Evaluation complete \u2014 updated ${summaryParts.join(", ")}` : "Evaluation complete \u2014 no changes needed",
9053
- ["Use deskfree_state to check for other pending evaluations"],
9054
- result
9055
- );
9056
- } catch (err) {
9057
- return errorResult(err);
9058
- }
9059
- }
9121
+ execute: makeSubmitEvaluationHandler(
9122
+ client,
9123
+ "Use deskfree_state to check for other pending evaluations"
9124
+ )
9060
9125
  }
9061
9126
  ];
9062
9127
  }