@questionbase/deskfree 0.3.0-alpha.19 → 0.3.0-alpha.20

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
@@ -3687,12 +3687,39 @@ var DeskFreeError = class _DeskFreeError extends Error {
3687
3687
  status
3688
3688
  );
3689
3689
  }
3690
- if (status >= 500) {
3690
+ if (status === 400) {
3691
3691
  return new _DeskFreeError(
3692
- "server",
3692
+ "client",
3693
3693
  procedure,
3694
- `Server error: ${status} ${statusText} \u2014 ${responseText}`,
3695
- "DeskFree service is temporarily unavailable. Please try again in a few minutes.",
3694
+ `Bad request: ${status} ${statusText} \u2014 ${responseText}`,
3695
+ "The request was invalid. Please check your input parameters and try again.",
3696
+ status
3697
+ );
3698
+ }
3699
+ if (status === 404) {
3700
+ return new _DeskFreeError(
3701
+ "client",
3702
+ procedure,
3703
+ `Resource not found: ${status} ${statusText} \u2014 ${responseText}`,
3704
+ procedure.includes("task") ? "The specified task was not found or is not available. Use deskfree_state to see available tasks." : "The requested resource was not found. Please check your input and try again.",
3705
+ status
3706
+ );
3707
+ }
3708
+ if (status === 409) {
3709
+ return new _DeskFreeError(
3710
+ "client",
3711
+ procedure,
3712
+ `Conflict: ${status} ${statusText} \u2014 ${responseText}`,
3713
+ procedure.includes("start_task") || procedure.includes("claim") ? "The resource is already in use by another process. Use deskfree_state to see current status and try a different resource." : "The request conflicts with the current state of the resource. Please refresh and try again.",
3714
+ status
3715
+ );
3716
+ }
3717
+ if (status === 422) {
3718
+ return new _DeskFreeError(
3719
+ "client",
3720
+ procedure,
3721
+ `Validation failed: ${status} ${statusText} \u2014 ${responseText}`,
3722
+ "The request data failed validation. Please check all required fields and data formats.",
3696
3723
  status
3697
3724
  );
3698
3725
  }
@@ -3705,12 +3732,22 @@ var DeskFreeError = class _DeskFreeError extends Error {
3705
3732
  status
3706
3733
  );
3707
3734
  }
3708
- if (status === 400) {
3735
+ if (status >= 500 && status < 600) {
3736
+ const serverErrorMessage = status === 502 || status === 503 ? "DeskFree service is temporarily unavailable due to maintenance or high load. Please try again in a few minutes." : status === 504 ? "The request timed out on the server. This may be due to high load. Please try again with a smaller request or wait a few minutes." : "DeskFree service encountered an internal error. Please try again in a few minutes.";
3737
+ return new _DeskFreeError(
3738
+ "server",
3739
+ procedure,
3740
+ `Server error: ${status} ${statusText} \u2014 ${responseText}`,
3741
+ serverErrorMessage,
3742
+ status
3743
+ );
3744
+ }
3745
+ if (status >= 400 && status < 500) {
3709
3746
  return new _DeskFreeError(
3710
3747
  "client",
3711
3748
  procedure,
3712
- `Bad request: ${status} ${statusText} \u2014 ${responseText}`,
3713
- "The request was invalid. Please check your input and try again.",
3749
+ `Client error: ${status} ${statusText} \u2014 ${responseText}`,
3750
+ "The request was not accepted by the server. Please check your input and try again.",
3714
3751
  status
3715
3752
  );
3716
3753
  }
@@ -3820,12 +3857,19 @@ var DeskFreeClient = class {
3820
3857
  return this.request("POST", "messages.update", input);
3821
3858
  }
3822
3859
  /**
3823
- * Send a text message (with optional attachments) to a DeskFree conversation.
3860
+ * Send a text message (with optional attachments or suggestions) to a DeskFree conversation.
3824
3861
  *
3825
- * @param input - Message content, optional userId, taskId, and attachments
3862
+ * @param input - Message content, optional userId, taskId, attachments, and suggestions
3826
3863
  */
3827
3864
  async sendMessage(input) {
3828
- this.requireNonEmpty(input.content, "content");
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
+ }
3829
3873
  return this.request("POST", "messages.send", input);
3830
3874
  }
3831
3875
  /** Fetch paginated message history for a conversation. */
@@ -3844,28 +3888,6 @@ var DeskFreeClient = class {
3844
3888
  async getWsTicket() {
3845
3889
  return this.request("POST", "messages.wsTicket", {});
3846
3890
  }
3847
- // ── Activities ──────────────────────────────────────────────
3848
- /** Create a new activity — a knowledge container for a type of work. */
3849
- async createActivity(input) {
3850
- this.requireNonEmpty(input.name, "name");
3851
- return this.request("POST", "activities.create", input);
3852
- }
3853
- /** Update an activity's name, description, or instructions. */
3854
- async updateActivity(input) {
3855
- this.requireNonEmpty(input.activityId, "activityId");
3856
- return this.request("POST", "activities.update", input);
3857
- }
3858
- /** Find activities relevant to a task. Returns scored matches. */
3859
- async classifyTask(input) {
3860
- this.requireNonEmpty(input.taskTitle, "taskTitle");
3861
- return this.request("POST", "activities.classify", input);
3862
- }
3863
- /** Link a task to an activity and record learnings. */
3864
- async learnFromTask(input) {
3865
- this.requireNonEmpty(input.taskId, "taskId");
3866
- this.requireNonEmpty(input.activityId, "activityId");
3867
- return this.request("POST", "activities.learn", input);
3868
- }
3869
3891
  // ── Tasks ─────────────────────────────────────────────────
3870
3892
  /** Create a new task, optionally with a recurring schedule. */
3871
3893
  async createTask(input) {
@@ -3877,7 +3899,7 @@ var DeskFreeClient = class {
3877
3899
  this.requireNonEmpty(input.taskId, "taskId");
3878
3900
  return this.request("POST", "tasks.claim", input);
3879
3901
  }
3880
- /** Update the deliverable (markdown content) for a task. */
3902
+ /** Update the deliverable (markdown or HTML content) for a task. */
3881
3903
  async updateDeliverable(input) {
3882
3904
  this.requireNonEmpty(input.taskId, "taskId");
3883
3905
  this.requireNonEmpty(input.deliverable, "deliverable");
@@ -3891,14 +3913,6 @@ var DeskFreeClient = class {
3891
3913
  async reportError(input) {
3892
3914
  return this.request("POST", "agent.reportError", input);
3893
3915
  }
3894
- /** Get short-lived AWS credentials for S3 workspace access. */
3895
- async workspaceCredentials() {
3896
- return this.request("POST", "workspace.credentials", {});
3897
- }
3898
- /** Notify DeskFree that workspace files have changed locally. */
3899
- async workspaceRead(input) {
3900
- return this.request("GET", "workspace.read", input);
3901
- }
3902
3916
  /** Get full workspace snapshot — active tasks and recently done. */
3903
3917
  async getState() {
3904
3918
  return this.request("GET", "state.get", {});
@@ -3909,12 +3923,12 @@ var DeskFreeClient = class {
3909
3923
  this.requireNonEmpty(input.model, "model");
3910
3924
  return this.request("POST", "tasks.reportUsage", input);
3911
3925
  }
3912
- /** Complete a task with an outcome. Moves task to waiting_for_human. */
3926
+ /** Complete a task with an outcome. Moves task to human. */
3913
3927
  async completeTask(input) {
3914
3928
  this.requireNonEmpty(input.taskId, "taskId");
3915
3929
  return this.request("POST", "tasks.complete", input);
3916
3930
  }
3917
- /** Suggest new tasks for the human to review and approve. */
3931
+ /** Suggest new tasks for the human to review and approve (via messages.send with suggestions). */
3918
3932
  async suggestTasks(input) {
3919
3933
  if (!input.tasks || input.tasks.length === 0) {
3920
3934
  throw new DeskFreeError(
@@ -3924,7 +3938,28 @@ var DeskFreeClient = class {
3924
3938
  "Missing required parameter: tasks. Please provide at least one task to suggest."
3925
3939
  );
3926
3940
  }
3927
- return this.request("POST", "taskSuggestions.create", input);
3941
+ return this.sendMessage({
3942
+ suggestions: input.tasks,
3943
+ taskId: input.taskId
3944
+ });
3945
+ }
3946
+ /**
3947
+ * Claim a pending evaluation for a task. Atomically sets isWorking=true where
3948
+ * evaluationPending=true and isWorking=false. Returns null if already claimed.
3949
+ */
3950
+ async claimEvaluation(input) {
3951
+ this.requireNonEmpty(input.taskId, "taskId");
3952
+ return this.request("POST", "waysOfWorking.claim", input);
3953
+ }
3954
+ /**
3955
+ * Submit the result of a ways-of-working evaluation.
3956
+ * If hasChanges is true and updatedContent is provided, inserts a new version.
3957
+ * Clears evaluationPending and isWorking on the task.
3958
+ */
3959
+ async submitEvaluation(input) {
3960
+ this.requireNonEmpty(input.taskId, "taskId");
3961
+ this.requireNonEmpty(input.reasoning, "reasoning");
3962
+ return this.request("POST", "waysOfWorking.evaluate", input);
3928
3963
  }
3929
3964
  /**
3930
3965
  * Lightweight health check that verifies connectivity and authentication.
@@ -4528,29 +4563,8 @@ var PLUGIN_VERSION = JSON.parse(
4528
4563
  readFileSync(resolve(__dirname, "..", "package.json"), "utf-8")
4529
4564
  ).version;
4530
4565
 
4531
- // src/workspace.ts
4532
- import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
4533
- import { join as join2, relative } from "path";
4534
- var MAX_FILE_SIZE = 100 * 1024;
4535
- function resolveWorkspacePath(cfg) {
4536
- try {
4537
- const agents = cfg.agents;
4538
- if (!agents) return null;
4539
- const defaults = agents.defaults;
4540
- if (!defaults) return null;
4541
- const workspace = defaults.workspace;
4542
- if (typeof workspace === "string" && workspace.length > 0) {
4543
- return workspace;
4544
- }
4545
- return null;
4546
- } catch {
4547
- return null;
4548
- }
4549
- }
4550
-
4551
4566
  // src/gateway.ts
4552
- import { spawn } from "child_process";
4553
- import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
4567
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4554
4568
  import { dirname as dirname2 } from "path";
4555
4569
 
4556
4570
  // ../node_modules/ws/wrapper.mjs
@@ -4576,65 +4590,6 @@ function getActiveTaskId() {
4576
4590
  function clearCompletedTaskId() {
4577
4591
  completedTaskId = null;
4578
4592
  }
4579
- async function runS3CommandWithCredentials(client, buildCommand, workspacePath, log) {
4580
- try {
4581
- const creds = await client.workspaceCredentials();
4582
- const command = buildCommand(creds.s3Uri);
4583
- const env = {
4584
- ...process.env,
4585
- AWS_ACCESS_KEY_ID: creds.accessKeyId,
4586
- AWS_SECRET_ACCESS_KEY: creds.secretAccessKey,
4587
- AWS_SESSION_TOKEN: creds.sessionToken,
4588
- AWS_DEFAULT_REGION: creds.region
4589
- };
4590
- return new Promise((resolve2, reject) => {
4591
- const child = spawn("aws", command, {
4592
- env,
4593
- cwd: workspacePath,
4594
- stdio: "pipe"
4595
- });
4596
- let stdout = "";
4597
- let stderr = "";
4598
- child.stdout?.on("data", (data) => {
4599
- stdout += data.toString();
4600
- });
4601
- child.stderr?.on("data", (data) => {
4602
- stderr += data.toString();
4603
- });
4604
- child.on("close", (code) => {
4605
- if (code === 0) {
4606
- log.info(`S3 command succeeded: ${command.join(" ")}`);
4607
- if (stdout.trim()) {
4608
- log.debug(`S3 stdout: ${stdout.trim()}`);
4609
- }
4610
- resolve2(true);
4611
- } else {
4612
- log.warn(`S3 command failed (exit ${code}): ${command.join(" ")}`);
4613
- if (stderr.trim()) {
4614
- log.warn(`S3 stderr: ${stderr.trim()}`);
4615
- }
4616
- reject(
4617
- new Error(
4618
- `AWS CLI command failed with exit code ${code}: ${stderr}`
4619
- )
4620
- );
4621
- }
4622
- });
4623
- child.on("error", (err) => {
4624
- log.warn(`S3 command spawn error: ${err.message}`);
4625
- reject(err);
4626
- });
4627
- });
4628
- } catch (err) {
4629
- const message = err instanceof Error ? err.message : String(err);
4630
- log.warn(`Failed to get S3 credentials or run command: ${message}`);
4631
- reportError("warn", `S3 workspace command failed: ${message}`, {
4632
- component: "gateway",
4633
- event: "workspace_sync_failed"
4634
- });
4635
- return false;
4636
- }
4637
- }
4638
4593
  var PING_INTERVAL_MS = 5 * 60 * 1e3;
4639
4594
  var POLL_FALLBACK_INTERVAL_MS = 30 * 1e3;
4640
4595
  var WS_CONNECTION_TIMEOUT_MS = 30 * 1e3;
@@ -4709,14 +4664,14 @@ function enqueuePoll(client, ctx, getCursor, setCursor, log, account) {
4709
4664
  const accountId = ctx.accountId;
4710
4665
  const prev = pollChains.get(accountId) ?? Promise.resolve();
4711
4666
  const next = prev.then(async () => {
4712
- const newCursor = await pollAndDeliver(
4667
+ const result = await pollAndDeliver(
4713
4668
  client,
4714
4669
  ctx,
4715
4670
  getCursor(),
4716
4671
  log,
4717
4672
  account
4718
4673
  );
4719
- if (newCursor) setCursor(newCursor);
4674
+ if (result.cursor) setCursor(result.cursor);
4720
4675
  }).catch((err) => {
4721
4676
  const message = err instanceof Error ? err.message : String(err);
4722
4677
  log.error(`Poll error: ${message}`);
@@ -4753,7 +4708,7 @@ function loadCursor(ctx) {
4753
4708
  const cursorPath = resolvePluginStorePath(
4754
4709
  `cursors/${ctx.accountId}/cursor`
4755
4710
  );
4756
- return readFileSync3(cursorPath, "utf-8").trim() || null;
4711
+ return readFileSync2(cursorPath, "utf-8").trim() || null;
4757
4712
  } catch {
4758
4713
  return null;
4759
4714
  }
@@ -4930,34 +4885,6 @@ async function runWebSocketConnection(opts) {
4930
4885
  const msg = err instanceof Error ? err.message : String(err);
4931
4886
  log.warn(`Failed to send statusUpdate: ${msg}`);
4932
4887
  });
4933
- try {
4934
- const cfg = getDeskFreeRuntime().config.loadConfig();
4935
- const workspacePath = resolveWorkspacePath(
4936
- cfg
4937
- );
4938
- if (workspacePath) {
4939
- const success = await runS3CommandWithCredentials(
4940
- client,
4941
- (s3Uri) => ["s3", "sync", ".", s3Uri, "--only-show-errors"],
4942
- workspacePath,
4943
- log
4944
- );
4945
- if (success) {
4946
- log.info(
4947
- `Workspace sync: uploaded files from ${workspacePath} to S3`
4948
- );
4949
- } else {
4950
- log.warn("Workspace sync: failed to upload files to S3");
4951
- }
4952
- } else {
4953
- log.debug(
4954
- "Workspace sync: no workspace path configured (agents.defaults.workspace)"
4955
- );
4956
- }
4957
- } catch (err) {
4958
- const msg = err instanceof Error ? err.message : String(err);
4959
- log.warn(`Workspace sync failed: ${msg}`);
4960
- }
4961
4888
  enqueuePoll(
4962
4889
  client,
4963
4890
  ctx,
@@ -4984,85 +4911,6 @@ async function runWebSocketConnection(opts) {
4984
4911
  return;
4985
4912
  }
4986
4913
  if (msg.action === "notify") {
4987
- const notifyMsg = msg;
4988
- if (notifyMsg.hint === "workspace.fileChanged") {
4989
- const paths = notifyMsg.paths ?? [];
4990
- log.info(
4991
- `Workspace file(s) changed by human: ${paths.join(", ") || "(all)"}`
4992
- );
4993
- try {
4994
- const cfg = getDeskFreeRuntime().config.loadConfig();
4995
- const workspacePath = resolveWorkspacePath(
4996
- cfg
4997
- );
4998
- if (workspacePath && paths.length > 0) {
4999
- for (const filePath of paths) {
5000
- try {
5001
- const success = await runS3CommandWithCredentials(
5002
- client,
5003
- (s3Uri) => [
5004
- "s3",
5005
- "cp",
5006
- `${s3Uri}/${filePath}`,
5007
- filePath,
5008
- "--only-show-errors"
5009
- ],
5010
- workspacePath,
5011
- log
5012
- );
5013
- if (success) {
5014
- log.info(`Updated local file: ${filePath}`);
5015
- } else {
5016
- log.warn(
5017
- `Failed to download workspace file: ${filePath}`
5018
- );
5019
- }
5020
- } catch (err) {
5021
- const errMsg = err instanceof Error ? err.message : String(err);
5022
- log.warn(
5023
- `Failed to download workspace file ${filePath}: ${errMsg}`
5024
- );
5025
- }
5026
- }
5027
- } else if (!workspacePath) {
5028
- log.warn(
5029
- "Cannot sync workspace files: workspace path not configured"
5030
- );
5031
- }
5032
- } catch (err) {
5033
- const errMsg = err instanceof Error ? err.message : String(err);
5034
- log.warn(`Workspace file change handling failed: ${errMsg}`);
5035
- }
5036
- }
5037
- if (notifyMsg.hint === "workspace.uploadRequested") {
5038
- log.info("Workspace upload requested");
5039
- try {
5040
- const cfg = getDeskFreeRuntime().config.loadConfig();
5041
- const workspacePath = resolveWorkspacePath(
5042
- cfg
5043
- );
5044
- if (workspacePath) {
5045
- const success = await runS3CommandWithCredentials(
5046
- client,
5047
- (s3Uri) => ["s3", "sync", ".", s3Uri, "--only-show-errors"],
5048
- workspacePath,
5049
- log
5050
- );
5051
- if (success) {
5052
- log.info(`Workspace upload: synced ${workspacePath} to S3`);
5053
- } else {
5054
- log.warn("Workspace upload: failed to sync to S3");
5055
- }
5056
- } else {
5057
- log.warn(
5058
- "Cannot upload workspace: workspace path not configured"
5059
- );
5060
- }
5061
- } catch (err) {
5062
- const errMsg = err instanceof Error ? err.message : String(err);
5063
- log.warn(`Workspace upload failed: ${errMsg}`);
5064
- }
5065
- }
5066
4914
  enqueuePoll(
5067
4915
  client,
5068
4916
  ctx,
@@ -5176,11 +5024,9 @@ async function runPollingFallback(opts) {
5176
5024
  log.info("Running in polling fallback mode.");
5177
5025
  let consecutiveFailures = 0;
5178
5026
  while (!ctx.abortSignal.aborted && iterations < maxIterations) {
5179
- const newCursor = await pollAndDeliver(client, ctx, cursor, log);
5180
- if (newCursor) {
5181
- cursor = newCursor;
5182
- consecutiveFailures = 0;
5183
- } else if (newCursor === null && cursor) {
5027
+ const result = await pollAndDeliver(client, ctx, cursor, log);
5028
+ if (result.ok) {
5029
+ if (result.cursor) cursor = result.cursor;
5184
5030
  consecutiveFailures = 0;
5185
5031
  } else {
5186
5032
  consecutiveFailures++;
@@ -5244,9 +5090,9 @@ async function pollAndDeliver(client, ctx, cursor, log, account) {
5244
5090
  const msg = err instanceof Error ? err.message : String(err);
5245
5091
  log.warn(`Failed to send welcome message: ${msg}`);
5246
5092
  }
5247
- return response.cursor ?? SEED;
5093
+ return { cursor: response.cursor ?? SEED, ok: true };
5248
5094
  }
5249
- if (response.items.length === 0) return null;
5095
+ if (response.items.length === 0) return { cursor: null, ok: true };
5250
5096
  const newItems = response.items.filter(
5251
5097
  (m) => !deliveredMessageIds.has(m.messageId)
5252
5098
  );
@@ -5257,7 +5103,7 @@ async function pollAndDeliver(client, ctx, cursor, log, account) {
5257
5103
  if (response.cursor) {
5258
5104
  saveCursor(ctx, response.cursor, log);
5259
5105
  }
5260
- return response.cursor;
5106
+ return { cursor: response.cursor, ok: true };
5261
5107
  }
5262
5108
  log.info(
5263
5109
  `Received ${newItems.length} new message(s) (${response.items.length - newItems.length} duplicate(s) skipped).`
@@ -5287,7 +5133,7 @@ async function pollAndDeliver(client, ctx, cursor, log, account) {
5287
5133
  if (response.cursor) {
5288
5134
  saveCursor(ctx, response.cursor, log);
5289
5135
  }
5290
- return response.cursor;
5136
+ return { cursor: response.cursor, ok: true };
5291
5137
  } catch (err) {
5292
5138
  const message = err instanceof Error ? err.message : String(err);
5293
5139
  log.warn(`Poll failed: ${message}`);
@@ -5295,7 +5141,7 @@ async function pollAndDeliver(client, ctx, cursor, log, account) {
5295
5141
  component: "gateway",
5296
5142
  event: "poll_failed"
5297
5143
  });
5298
- return null;
5144
+ return { cursor: null, ok: false };
5299
5145
  }
5300
5146
  }
5301
5147
 
@@ -7909,68 +7755,12 @@ var Type = type_exports2;
7909
7755
  var ORCHESTRATOR_TOOLS = {
7910
7756
  STATE: {
7911
7757
  name: "deskfree_state",
7912
- description: "Get full workspace state \u2014 all activities, tasks, recently done tasks. Use to assess what needs attention.",
7758
+ description: "Get full workspace state \u2014 all tasks, recently done tasks. Use to assess what needs attention.",
7913
7759
  parameters: Type.Object({})
7914
7760
  },
7915
- CREATE_ACTIVITY: {
7916
- name: "deskfree_create_activity",
7917
- description: "Create an activity \u2014 a knowledge container for a type of work. Activities accumulate learnings from tasks. Returns the created activity.",
7918
- parameters: Type.Object({
7919
- name: Type.String({ description: "Activity name (max 200 chars)" }),
7920
- description: Type.Optional(
7921
- Type.String({
7922
- description: "What this activity covers"
7923
- })
7924
- ),
7925
- instructions: Type.Optional(
7926
- Type.String({
7927
- description: "Initial instructions/knowledge for this activity"
7928
- })
7929
- )
7930
- })
7931
- },
7932
- UPDATE_ACTIVITY: {
7933
- name: "deskfree_update_activity",
7934
- description: "Update an activity's name, description, or instructions.",
7935
- parameters: Type.Object({
7936
- activityId: Type.String({ description: "Activity ID to update" }),
7937
- name: Type.Optional(
7938
- Type.String({ description: "New name (max 200 chars)" })
7939
- ),
7940
- description: Type.Optional(
7941
- Type.String({ description: "Updated description" })
7942
- ),
7943
- instructions: Type.Optional(
7944
- Type.String({ description: "Updated instructions" })
7945
- )
7946
- })
7947
- },
7948
- CLASSIFY_TASK: {
7949
- name: "deskfree_classify_task",
7950
- description: "Find activities relevant to a task. Returns matching activities whose instructions should inform the task.",
7951
- parameters: Type.Object({
7952
- taskTitle: Type.String({ description: "Task title to classify" }),
7953
- taskInstructions: Type.Optional(
7954
- Type.String({ description: "Task instructions for better matching" })
7955
- )
7956
- })
7957
- },
7958
- LEARN_FROM_TASK: {
7959
- name: "deskfree_learn_from_task",
7960
- description: "After completing a task, link it to an activity and record what was learned. Appends new instructions to the activity.",
7961
- parameters: Type.Object({
7962
- taskId: Type.String({ description: "Task UUID" }),
7963
- activityId: Type.String({ description: "Activity ID to learn into" }),
7964
- additionalInstructions: Type.Optional(
7965
- Type.String({
7966
- description: "New learnings to append to the activity instructions"
7967
- })
7968
- )
7969
- })
7970
- },
7971
7761
  CREATE_TASK: {
7972
7762
  name: "deskfree_create_task",
7973
- description: "Create a new task (starts as ready_for_bot).",
7763
+ description: "Create a new task (starts as bot).",
7974
7764
  parameters: Type.Object({
7975
7765
  title: Type.String({ description: "Task title (max 200 chars)" }),
7976
7766
  instructions: Type.Optional(
@@ -7980,22 +7770,29 @@ var ORCHESTRATOR_TOOLS = {
7980
7770
  },
7981
7771
  START_TASK: {
7982
7772
  name: "deskfree_start_task",
7983
- description: "Claim a ready_for_bot task and start working. Returns full context (instructions, deliverable, message history).",
7773
+ description: "Claim a bot task (isWorking=false) and start working. Returns full context (instructions, deliverable, message history).",
7984
7774
  parameters: Type.Object({
7985
7775
  taskId: Type.String({ description: "Task UUID to claim" })
7986
7776
  })
7987
7777
  },
7988
7778
  UPDATE_DELIVERABLE: {
7989
7779
  name: "deskfree_update_deliverable",
7990
- description: "Update task deliverable markdown. Build incrementally as you work.",
7780
+ 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.',
7991
7781
  parameters: Type.Object({
7992
7782
  taskId: Type.String({ description: "Task UUID" }),
7993
- deliverable: Type.String({ description: "Markdown deliverable content" })
7783
+ deliverable: Type.String({
7784
+ description: "Deliverable content (markdown or HTML depending on format)"
7785
+ }),
7786
+ format: Type.Optional(
7787
+ Type.Union([Type.Literal("markdown"), Type.Literal("html")], {
7788
+ description: '"markdown" (default) for text/documents, "html" for rich web content. HTML is rendered in a sandboxed iframe.'
7789
+ })
7790
+ )
7994
7791
  })
7995
7792
  },
7996
7793
  COMPLETE_TASK: {
7997
7794
  name: "deskfree_complete_task",
7998
- description: 'Finish a task. Outcome "done" = work complete for review. Outcome "blocked" = need human input. Both move to waiting_for_human.',
7795
+ description: 'Finish a task. Outcome "done" = work complete for review. Outcome "blocked" = need human input. Both move to human.',
7999
7796
  parameters: Type.Object({
8000
7797
  taskId: Type.String({ description: "Task UUID" }),
8001
7798
  outcome: Type.Union([Type.Literal("done"), Type.Literal("blocked")], {
@@ -8005,38 +7802,58 @@ var ORCHESTRATOR_TOOLS = {
8005
7802
  },
8006
7803
  SEND_MESSAGE: {
8007
7804
  name: "deskfree_send_message",
8008
- description: "Send a message in the task thread (progress update, question, status report).",
7805
+ 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.",
8009
7806
  parameters: Type.Object({
8010
- content: Type.String({ description: "Message content" }),
7807
+ content: Type.Optional(
7808
+ Type.String({
7809
+ description: "Message content. Required unless suggestions is provided."
7810
+ })
7811
+ ),
8011
7812
  taskId: Type.Optional(
8012
7813
  Type.String({
8013
7814
  description: "Task UUID (optional if context provides it)"
8014
7815
  })
7816
+ ),
7817
+ suggestions: Type.Optional(
7818
+ Type.Array(
7819
+ Type.Object({
7820
+ title: Type.String({ description: "Task title (max 200 chars)" }),
7821
+ instructions: Type.Optional(
7822
+ Type.String({
7823
+ description: "Detailed instructions for the suggested task"
7824
+ })
7825
+ )
7826
+ }),
7827
+ {
7828
+ description: "Suggest tasks for human review (1-10). The human will see approve/reject buttons for each. Provide this instead of content.",
7829
+ minItems: 1,
7830
+ maxItems: 10
7831
+ }
7832
+ )
8015
7833
  )
8016
7834
  })
8017
7835
  },
8018
- SUGGEST_TASKS: {
8019
- name: "deskfree_suggest_tasks",
8020
- description: "Suggest new tasks for the human to review and approve. Use when you notice work that should be done but is outside your current task scope. The human will see approve/reject buttons for each suggestion.",
7836
+ CLAIM_EVALUATION: {
7837
+ name: "deskfree_claim_evaluation",
7838
+ description: "Claim a pending ways-of-working evaluation for a task. Returns the task, its message history, and current ways_of_working content so you can decide whether updates are needed. Returns null if already claimed by another process.",
8021
7839
  parameters: Type.Object({
8022
- tasks: Type.Array(
8023
- Type.Object({
8024
- title: Type.String({ description: "Task title (max 200 chars)" }),
8025
- instructions: Type.Optional(
8026
- Type.String({
8027
- description: "Detailed instructions for the suggested task"
8028
- })
8029
- )
8030
- }),
8031
- {
8032
- description: "List of tasks to suggest (1-10)",
8033
- minItems: 1,
8034
- maxItems: 10
8035
- }
8036
- ),
8037
- taskId: Type.Optional(
7840
+ taskId: Type.String({ description: "Task UUID to claim evaluation for" })
7841
+ })
7842
+ },
7843
+ SUBMIT_EVALUATION: {
7844
+ name: "deskfree_submit_evaluation",
7845
+ description: "Submit the result of a ways-of-working evaluation. Provide reasoning explaining your analysis. If the task revealed new patterns or learnings worth capturing, set hasChanges=true and provide updatedContent with the full updated markdown. Otherwise set hasChanges=false.",
7846
+ parameters: Type.Object({
7847
+ taskId: Type.String({ description: "Task UUID being evaluated" }),
7848
+ reasoning: Type.String({
7849
+ description: "Explanation of your evaluation \u2014 what you analyzed and why you did or did not update the ways of working"
7850
+ }),
7851
+ hasChanges: Type.Boolean({
7852
+ description: "Whether the ways of working should be updated"
7853
+ }),
7854
+ updatedContent: Type.Optional(
8038
7855
  Type.String({
8039
- description: "Current task UUID \u2014 links suggestions to the task you are working on"
7856
+ description: "Full updated ways-of-working markdown (required if hasChanges=true)"
8040
7857
  })
8041
7858
  )
8042
7859
  })
@@ -8046,8 +7863,7 @@ var WORKER_TOOLS = {
8046
7863
  UPDATE_DELIVERABLE: ORCHESTRATOR_TOOLS.UPDATE_DELIVERABLE,
8047
7864
  COMPLETE_TASK: ORCHESTRATOR_TOOLS.COMPLETE_TASK,
8048
7865
  SEND_MESSAGE: ORCHESTRATOR_TOOLS.SEND_MESSAGE,
8049
- SUGGEST_TASKS: ORCHESTRATOR_TOOLS.SUGGEST_TASKS,
8050
- LEARN_FROM_TASK: ORCHESTRATOR_TOOLS.LEARN_FROM_TASK
7866
+ SUBMIT_EVALUATION: ORCHESTRATOR_TOOLS.SUBMIT_EVALUATION
8051
7867
  };
8052
7868
  var CHANNEL_META = {
8053
7869
  name: "DeskFree",
@@ -8079,6 +7895,131 @@ var STATUS_MESSAGES = {
8079
7895
  function getChannelConfig(cfg) {
8080
7896
  return cfg?.channels?.deskfree ?? null;
8081
7897
  }
7898
+ function validateStringField(value, fieldName) {
7899
+ if (!value) {
7900
+ return { trimmed: "", error: `${fieldName} is required` };
7901
+ }
7902
+ if (typeof value !== "string") {
7903
+ return { trimmed: "", error: `${fieldName} must be a string` };
7904
+ }
7905
+ const trimmed = value.trim();
7906
+ if (trimmed !== value) {
7907
+ return {
7908
+ trimmed: "",
7909
+ error: `${fieldName} must not have leading or trailing whitespace`
7910
+ };
7911
+ }
7912
+ return { trimmed };
7913
+ }
7914
+ function isLocalDevelopmentHost(hostname) {
7915
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.endsWith(".local") || hostname.endsWith(".localhost") || /^192\.168\./.test(hostname) || /^10\./.test(hostname) || /^172\.(1[6-9]|2\d|3[01])\./.test(hostname);
7916
+ }
7917
+ function validateBotToken(value) {
7918
+ const { trimmed, error } = validateStringField(value, "Bot token");
7919
+ if (error) return error;
7920
+ if (!trimmed.startsWith("bot_")) {
7921
+ return 'Bot token must start with "bot_" (check your DeskFree bot configuration)';
7922
+ }
7923
+ if (trimmed.length < 10) {
7924
+ return "Bot token appears to be incomplete (minimum 10 characters expected)";
7925
+ }
7926
+ if (trimmed.length > 200) {
7927
+ return "Bot token appears to be invalid (maximum 200 characters expected)";
7928
+ }
7929
+ if (!/^bot_[a-zA-Z0-9_-]+$/.test(trimmed)) {
7930
+ return 'Bot token contains invalid characters. Only alphanumeric, underscore, and dash are allowed after "bot_"';
7931
+ }
7932
+ if (trimmed.includes(" ") || trimmed.includes("\n") || trimmed.includes(" ")) {
7933
+ return "Bot token contains whitespace characters. Please copy the token exactly as shown in DeskFree.";
7934
+ }
7935
+ if (trimmed === "bot_your_token_here" || trimmed === "bot_example") {
7936
+ return "Please replace the placeholder with your actual bot token from DeskFree";
7937
+ }
7938
+ return null;
7939
+ }
7940
+ function validateApiUrl(value) {
7941
+ const { trimmed, error } = validateStringField(value, "API URL");
7942
+ if (error) return error;
7943
+ let url;
7944
+ try {
7945
+ url = new URL(trimmed);
7946
+ } catch (err) {
7947
+ const message = err instanceof Error ? err.message : "Invalid URL format";
7948
+ return `API URL must be a valid URL: ${message}`;
7949
+ }
7950
+ if (url.protocol !== "https:") {
7951
+ return "API URL must use HTTPS protocol for security. Make sure your DeskFree deployment supports HTTPS.";
7952
+ }
7953
+ if (!url.hostname) {
7954
+ return "API URL must have a valid hostname";
7955
+ }
7956
+ if (isLocalDevelopmentHost(url.hostname)) {
7957
+ if (process.env.NODE_ENV === "production") {
7958
+ return "API URL cannot use localhost or private IP addresses in production. Please use a publicly accessible URL.";
7959
+ }
7960
+ }
7961
+ if (url.hostname.includes("..") || url.hostname.startsWith(".")) {
7962
+ return "API URL hostname appears to be malformed. Please check for typos.";
7963
+ }
7964
+ if (url.pathname && url.pathname !== "/" && !url.pathname.includes("/api") && !url.pathname.includes("/v1")) {
7965
+ }
7966
+ return null;
7967
+ }
7968
+ function validateWebSocketUrl(value) {
7969
+ const { trimmed, error } = validateStringField(value, "WebSocket URL");
7970
+ if (error) return error;
7971
+ let url;
7972
+ try {
7973
+ url = new URL(trimmed);
7974
+ } catch (err) {
7975
+ const message = err instanceof Error ? err.message : "Invalid URL format";
7976
+ return `WebSocket URL must be a valid URL: ${message}`;
7977
+ }
7978
+ if (!["ws:", "wss:"].includes(url.protocol)) {
7979
+ return "WebSocket URL must use ws:// or wss:// protocol";
7980
+ }
7981
+ if (!url.hostname) {
7982
+ return "WebSocket URL must have a valid hostname";
7983
+ }
7984
+ if (isLocalDevelopmentHost(url.hostname)) {
7985
+ if (process.env.NODE_ENV === "production") {
7986
+ return "WebSocket URL cannot use localhost or private IP addresses in production. Please use a publicly accessible URL.";
7987
+ }
7988
+ }
7989
+ if (url.protocol === "ws:" && !isLocalDevelopmentHost(url.hostname)) {
7990
+ }
7991
+ if (url.hostname.includes("..") || url.hostname.startsWith(".")) {
7992
+ return "WebSocket URL hostname appears to be malformed. Please check for typos.";
7993
+ }
7994
+ return null;
7995
+ }
7996
+ function validateUserId(value) {
7997
+ const { trimmed, error } = validateStringField(value, "User ID");
7998
+ if (error) return error;
7999
+ const normalizedUserId = trimmed.toUpperCase();
8000
+ if (normalizedUserId !== trimmed) {
8001
+ return "User ID should be uppercase (will be automatically converted)";
8002
+ }
8003
+ if (!/^[UBPT][A-Z0-9]{10}$/.test(normalizedUserId)) {
8004
+ if (normalizedUserId.length !== 11) {
8005
+ return `User ID must be exactly 11 characters long (got: ${normalizedUserId.length}). Example: U9QF3C6X1A`;
8006
+ }
8007
+ const prefix = normalizedUserId.charAt(0);
8008
+ if (!"UBPT".includes(prefix)) {
8009
+ return `User ID must start with U (user), B (bot), P (project), or T (team). Got: "${prefix}". Check your DeskFree account settings.`;
8010
+ }
8011
+ if (!/^[A-Z0-9]+$/.test(normalizedUserId.slice(1))) {
8012
+ return "User ID contains invalid characters. Only letters A-Z and numbers 0-9 are allowed after the prefix.";
8013
+ }
8014
+ return "User ID must be a valid DeskFree ID format: one letter (U/B/P/T) + 10 alphanumeric characters (e.g. U9QF3C6X1A)";
8015
+ }
8016
+ if (["U0000000000", "B0000000000", "P0000000000", "T0000000000"].includes(
8017
+ normalizedUserId
8018
+ )) {
8019
+ return "Please replace the placeholder with your actual User ID from DeskFree account settings";
8020
+ }
8021
+ return null;
8022
+ }
8082
8023
  var deskFreePlugin = {
8083
8024
  id: "deskfree",
8084
8025
  meta: CHANNEL_META,
@@ -8265,102 +8206,14 @@ var deskFreePlugin = {
8265
8206
  },
8266
8207
  validateInput(params) {
8267
8208
  const { input } = params;
8268
- if (!input.botToken) {
8269
- return "Bot token is required";
8270
- }
8271
- if (typeof input.botToken !== "string") {
8272
- return "Bot token must be a string";
8273
- }
8274
- const botTokenTrimmed = input.botToken.trim();
8275
- if (botTokenTrimmed !== input.botToken) {
8276
- return "Bot token must not have leading or trailing whitespace";
8277
- }
8278
- if (!botTokenTrimmed.startsWith("bot_")) {
8279
- return 'Bot token must start with "bot_"';
8280
- }
8281
- if (botTokenTrimmed.length < 10) {
8282
- return "Bot token appears to be too short (minimum 10 characters)";
8283
- }
8284
- if (botTokenTrimmed.length > 100) {
8285
- return "Bot token appears to be too long (maximum 100 characters)";
8286
- }
8287
- if (!/^bot_[a-zA-Z0-9_-]+$/.test(botTokenTrimmed)) {
8288
- return 'Bot token contains invalid characters (only alphanumeric, underscore, and dash allowed after "bot_")';
8289
- }
8290
- if (!input.apiUrl) {
8291
- return "API URL is required";
8292
- }
8293
- if (typeof input.apiUrl !== "string") {
8294
- return "API URL must be a string";
8295
- }
8296
- const apiUrlTrimmed = input.apiUrl.trim();
8297
- if (apiUrlTrimmed !== input.apiUrl) {
8298
- return "API URL must not have leading or trailing whitespace";
8299
- }
8300
- try {
8301
- const apiUrl = new URL(apiUrlTrimmed);
8302
- if (apiUrl.protocol !== "https:") {
8303
- return "API URL must use HTTPS protocol for security";
8304
- }
8305
- if (!apiUrl.hostname) {
8306
- return "API URL must have a valid hostname";
8307
- }
8308
- if (apiUrl.hostname === "localhost" || apiUrl.hostname === "127.0.0.1") {
8309
- return "API URL cannot use localhost (use a publicly accessible URL)";
8310
- }
8311
- if (apiUrl.pathname && !apiUrl.pathname.endsWith("/")) {
8312
- }
8313
- } catch (err) {
8314
- const message = err instanceof Error ? err.message : "Invalid URL format";
8315
- return `API URL must be a valid URL: ${message}`;
8316
- }
8317
- if (!input.wsUrl) {
8318
- return "WebSocket URL is required";
8319
- }
8320
- if (typeof input.wsUrl !== "string") {
8321
- return "WebSocket URL must be a string";
8322
- }
8323
- const wsUrlTrimmed = input.wsUrl.trim();
8324
- if (wsUrlTrimmed !== input.wsUrl) {
8325
- return "WebSocket URL must not have leading or trailing whitespace";
8326
- }
8327
- try {
8328
- const wsUrl = new URL(wsUrlTrimmed);
8329
- if (!["ws:", "wss:"].includes(wsUrl.protocol)) {
8330
- return "WebSocket URL must use ws:// or wss:// protocol";
8331
- }
8332
- if (!wsUrl.hostname) {
8333
- return "WebSocket URL must have a valid hostname";
8334
- }
8335
- if (wsUrl.hostname === "localhost" || wsUrl.hostname === "127.0.0.1") {
8336
- return "WebSocket URL cannot use localhost (use a publicly accessible URL)";
8337
- }
8338
- if (wsUrl.protocol === "ws:" && wsUrl.hostname !== "localhost") {
8339
- }
8340
- } catch (err) {
8341
- const message = err instanceof Error ? err.message : "Invalid URL format";
8342
- return `WebSocket URL must be a valid URL: ${message}`;
8343
- }
8344
- if (!input.userId) {
8345
- return "User ID is required";
8346
- }
8347
- if (typeof input.userId !== "string") {
8348
- return "User ID must be a string";
8349
- }
8350
- const userIdTrimmed = input.userId.trim().toUpperCase();
8351
- if (userIdTrimmed !== input.userId.toUpperCase()) {
8352
- return "User ID must not have leading or trailing whitespace and should be uppercase";
8353
- }
8354
- if (!/^[UBPT][A-Z0-9]{10}$/.test(userIdTrimmed)) {
8355
- const prefix = userIdTrimmed.charAt(0);
8356
- if (!"UBPT".includes(prefix)) {
8357
- return "User ID must start with U, B, P, or T (got: " + prefix + ")";
8358
- }
8359
- if (userIdTrimmed.length !== 11) {
8360
- return `User ID must be exactly 11 characters long (got: ${userIdTrimmed.length})`;
8361
- }
8362
- return "User ID must be a valid DeskFree ID format: one letter (U/B/P/T) + 10 alphanumeric characters (e.g. U9QF3C6X1A)";
8363
- }
8209
+ const botTokenError = validateBotToken(input.botToken);
8210
+ if (botTokenError) return botTokenError;
8211
+ const apiUrlError = validateApiUrl(input.apiUrl);
8212
+ if (apiUrlError) return apiUrlError;
8213
+ const wsUrlError = validateWebSocketUrl(input.wsUrl);
8214
+ if (wsUrlError) return wsUrlError;
8215
+ const userIdError = validateUserId(input.userId);
8216
+ if (userIdError) return userIdError;
8364
8217
  return null;
8365
8218
  }
8366
8219
  },
@@ -8570,11 +8423,11 @@ var deskFreePlugin = {
8570
8423
  var DESKFREE_AGENT_DIRECTIVE = `## DeskFree
8571
8424
  Always read the deskfree skill (SKILL.md) at startup. Follow its workflow for task tracking and messaging.
8572
8425
  1. ALL work requires a task \u2014 use \`deskfree_create_task\` then \`deskfree_start_task\`.
8573
- 2. Activities are memory \u2014 use \`deskfree_classify_task\` to find relevant knowledge, \`deskfree_learn_from_task\` to record learnings.
8426
+ 2. Ways of Working is your evolving playbook \u2014 it's injected automatically via state.get. When tasks are approved by humans, you'll evaluate them and update the ways of working.
8574
8427
  3. Build deliverables incrementally \u2014 use \`deskfree_update_deliverable\` from the start.
8575
8428
  4. Always complete tasks \u2014 use \`deskfree_complete_task\` with outcome "done" or "blocked".
8576
8429
  5. Auto-threading works \u2014 messages sent while a task is active are threaded automatically.
8577
- 6. Sub-agents get 5 tools: update_deliverable, complete_task, send_message, suggest_tasks, learn_from_task.`;
8430
+ 6. Sub-agents get 4 tools: update_deliverable, complete_task, send_message (also supports suggestions), submit_evaluation.`;
8578
8431
  function getDeskFreeContext() {
8579
8432
  return `${DESKFREE_AGENT_DIRECTIVE}
8580
8433
 
@@ -8781,121 +8634,6 @@ function createOrchestratorTools(api) {
8781
8634
  }
8782
8635
  }
8783
8636
  },
8784
- {
8785
- ...ORCHESTRATOR_TOOLS.CREATE_ACTIVITY,
8786
- async execute(_id, params) {
8787
- try {
8788
- const name = validateStringParam(params, "name", true);
8789
- const description = validateStringParam(params, "description", false);
8790
- const instructions = validateStringParam(
8791
- params,
8792
- "instructions",
8793
- false
8794
- );
8795
- const result = await client.createActivity({
8796
- name,
8797
- description,
8798
- instructions
8799
- });
8800
- return formatConfirmation(
8801
- `Created activity "${result.activity.name}" (${result.activity.id})`,
8802
- [
8803
- "Activity is ready to accumulate learnings from tasks",
8804
- "Use deskfree_classify_task to match tasks to this activity"
8805
- ],
8806
- result.activity
8807
- );
8808
- } catch (err) {
8809
- return errorResult(err);
8810
- }
8811
- }
8812
- },
8813
- {
8814
- ...ORCHESTRATOR_TOOLS.UPDATE_ACTIVITY,
8815
- async execute(_id, params) {
8816
- try {
8817
- const activityId = validateStringParam(params, "activityId", true);
8818
- const name = validateStringParam(params, "name", false);
8819
- const description = validateStringParam(params, "description", false);
8820
- const instructions = validateStringParam(
8821
- params,
8822
- "instructions",
8823
- false
8824
- );
8825
- const result = await client.updateActivity({
8826
- activityId,
8827
- name,
8828
- description,
8829
- instructions
8830
- });
8831
- return formatConfirmation(
8832
- `Updated activity "${result.activity.name}"`,
8833
- ["Activity instructions updated"],
8834
- result.activity
8835
- );
8836
- } catch (err) {
8837
- return errorResult(err);
8838
- }
8839
- }
8840
- },
8841
- {
8842
- ...ORCHESTRATOR_TOOLS.CLASSIFY_TASK,
8843
- async execute(_id, params) {
8844
- try {
8845
- const taskTitle = validateStringParam(params, "taskTitle", true);
8846
- const taskInstructions = validateStringParam(
8847
- params,
8848
- "taskInstructions",
8849
- false
8850
- );
8851
- const result = await client.classifyTask({
8852
- taskTitle,
8853
- taskInstructions
8854
- });
8855
- return formatConfirmation(
8856
- `Found ${result.matches.length} matching activit${result.matches.length === 1 ? "y" : "ies"}`,
8857
- result.matches.length > 0 ? [
8858
- "Use activity instructions to inform the task",
8859
- "After completing the task, use deskfree_learn_from_task to record learnings"
8860
- ] : [
8861
- "No matching activities found \u2014 consider creating one with deskfree_create_activity"
8862
- ],
8863
- { matches: result.matches, suggestNew: result.suggestNew }
8864
- );
8865
- } catch (err) {
8866
- return errorResult(err);
8867
- }
8868
- }
8869
- },
8870
- {
8871
- ...ORCHESTRATOR_TOOLS.LEARN_FROM_TASK,
8872
- async execute(_id, params) {
8873
- try {
8874
- const taskId = validateStringParam(params, "taskId", true);
8875
- const activityId = validateStringParam(params, "activityId", true);
8876
- const additionalInstructions = validateStringParam(
8877
- params,
8878
- "additionalInstructions",
8879
- false
8880
- );
8881
- const result = await client.learnFromTask({
8882
- taskId,
8883
- activityId,
8884
- additionalInstructions
8885
- });
8886
- return formatConfirmation(
8887
- `Recorded learnings to activity "${result.activity.name}"`,
8888
- [
8889
- "Activity instructions have been updated with new learnings",
8890
- "Future tasks matching this activity will benefit from these learnings"
8891
- ],
8892
- result.activity
8893
- );
8894
- } catch (err) {
8895
- return errorResult(err);
8896
- }
8897
- }
8898
- },
8899
8637
  {
8900
8638
  ...ORCHESTRATOR_TOOLS.CREATE_TASK,
8901
8639
  async execute(_id, params) {
@@ -8965,7 +8703,13 @@ function createOrchestratorTools(api) {
8965
8703
  try {
8966
8704
  const taskId = validateStringParam(params, "taskId", true);
8967
8705
  const deliverable = validateStringParam(params, "deliverable", true);
8968
- await client.updateDeliverable({ taskId, deliverable });
8706
+ const format = validateEnumParam(
8707
+ params,
8708
+ "format",
8709
+ ["markdown", "html"],
8710
+ false
8711
+ );
8712
+ await client.updateDeliverable({ taskId, deliverable, format });
8969
8713
  return formatConfirmation(`Updated deliverable for task ${taskId}`, [
8970
8714
  "Deliverable has been saved",
8971
8715
  "Complete with deskfree_complete_task when ready"
@@ -9011,8 +8755,44 @@ function createOrchestratorTools(api) {
9011
8755
  ...ORCHESTRATOR_TOOLS.SEND_MESSAGE,
9012
8756
  async execute(_id, params) {
9013
8757
  try {
9014
- const content = validateStringParam(params, "content", true);
8758
+ const content = validateStringParam(params, "content", false);
9015
8759
  const taskId = validateStringParam(params, "taskId", false);
8760
+ const rawSuggestions = params?.suggestions;
8761
+ let suggestions;
8762
+ if (Array.isArray(rawSuggestions) && rawSuggestions.length > 0) {
8763
+ suggestions = rawSuggestions.map((t, i) => {
8764
+ if (typeof t !== "object" || t === null) {
8765
+ throw new Error(`suggestions[${i}] must be an object`);
8766
+ }
8767
+ const item = t;
8768
+ const title = item["title"];
8769
+ if (typeof title !== "string" || title.trim() === "") {
8770
+ throw new Error(
8771
+ `suggestions[${i}].title must be a non-empty string`
8772
+ );
8773
+ }
8774
+ const instructions = item["instructions"];
8775
+ return {
8776
+ title: title.trim(),
8777
+ instructions: typeof instructions === "string" ? instructions : void 0
8778
+ };
8779
+ });
8780
+ }
8781
+ if (!content && !suggestions) {
8782
+ throw new Error(
8783
+ 'Either "content" or "suggestions" parameter is required'
8784
+ );
8785
+ }
8786
+ if (suggestions) {
8787
+ await client.sendMessage({ suggestions, taskId });
8788
+ return formatConfirmation(
8789
+ `Suggested ${suggestions.length} task${suggestions.length === 1 ? "" : "s"} for human review`,
8790
+ [
8791
+ "The human will see approve/reject buttons for each suggestion",
8792
+ "Continue working on the current task"
8793
+ ]
8794
+ );
8795
+ }
9016
8796
  await client.sendMessage({ content, taskId });
9017
8797
  return formatConfirmation(
9018
8798
  `Message sent${taskId ? ` to task ${taskId}` : ""}`,
@@ -9027,37 +8807,73 @@ function createOrchestratorTools(api) {
9027
8807
  }
9028
8808
  },
9029
8809
  {
9030
- ...ORCHESTRATOR_TOOLS.SUGGEST_TASKS,
8810
+ ...ORCHESTRATOR_TOOLS.CLAIM_EVALUATION,
9031
8811
  async execute(_id, params) {
9032
8812
  try {
9033
- const rawTasks = params?.tasks;
9034
- if (!Array.isArray(rawTasks) || rawTasks.length === 0) {
9035
- throw new Error('Parameter "tasks" must be a non-empty array');
8813
+ const taskId = validateStringParam(params, "taskId", true);
8814
+ const result = await client.claimEvaluation({ taskId });
8815
+ if (!result) {
8816
+ return formatConfirmation(
8817
+ "Evaluation already claimed by another process",
8818
+ ["Use deskfree_state to check for other pending evaluations"]
8819
+ );
9036
8820
  }
9037
- const tasks = rawTasks.map((t, i) => {
9038
- if (typeof t !== "object" || t === null) {
9039
- throw new Error(`tasks[${i}] must be an object`);
9040
- }
9041
- const task = t;
9042
- const title = task["title"];
9043
- if (typeof title !== "string" || title.trim() === "") {
9044
- throw new Error(`tasks[${i}].title must be a non-empty string`);
9045
- }
9046
- const instructions = task["instructions"];
9047
- return {
9048
- title: title.trim(),
9049
- instructions: typeof instructions === "string" ? instructions : void 0
9050
- };
8821
+ return {
8822
+ content: [
8823
+ {
8824
+ type: "text",
8825
+ text: JSON.stringify(
8826
+ {
8827
+ summary: `Claimed evaluation for task "${result.task.title}"`,
8828
+ nextActions: [
8829
+ "Review the task messages and current ways of working",
8830
+ "Decide if patterns/learnings should update ways of working",
8831
+ "Call deskfree_submit_evaluation with your analysis"
8832
+ ],
8833
+ task: result.task,
8834
+ waysOfWorking: result.waysOfWorking,
8835
+ currentVersion: result.currentVersion,
8836
+ messages: result.messages
8837
+ },
8838
+ null,
8839
+ 2
8840
+ )
8841
+ }
8842
+ ]
8843
+ };
8844
+ } catch (err) {
8845
+ return errorResult(err);
8846
+ }
8847
+ }
8848
+ },
8849
+ {
8850
+ ...ORCHESTRATOR_TOOLS.SUBMIT_EVALUATION,
8851
+ async execute(_id, params) {
8852
+ try {
8853
+ const taskId = validateStringParam(params, "taskId", true);
8854
+ const reasoning = validateStringParam(params, "reasoning", true);
8855
+ const hasChanges = params?.hasChanges;
8856
+ if (typeof hasChanges !== "boolean") {
8857
+ throw new Error('Parameter "hasChanges" must be a boolean');
8858
+ }
8859
+ const updatedContent = validateStringParam(
8860
+ params,
8861
+ "updatedContent",
8862
+ false
8863
+ );
8864
+ const result = await client.submitEvaluation({
8865
+ taskId,
8866
+ reasoning,
8867
+ hasChanges,
8868
+ updatedContent
8869
+ });
8870
+ const messageContent = hasChanges ? `\u{1F4DD} Updated ways of working (v${result.version}): ${reasoning}` : `\u{1F4DD} No updates to ways of working: ${reasoning}`;
8871
+ await client.sendMessage({ content: messageContent, taskId }).catch(() => {
9051
8872
  });
9052
- const taskId = validateStringParam(params, "taskId", false);
9053
- const result = await client.suggestTasks({ tasks, taskId });
9054
8873
  return formatConfirmation(
9055
- `Suggested ${result.suggested} task${result.suggested === 1 ? "" : "s"} for human review`,
9056
- [
9057
- "The human will see approve/reject buttons for each suggestion",
9058
- "Continue working on the current task"
9059
- ],
9060
- { suggestionIds: result.suggestionIds }
8874
+ hasChanges ? `Ways of working updated to v${result.version}` : "Evaluation complete \u2014 no changes needed",
8875
+ ["Use deskfree_state to check for other pending evaluations"],
8876
+ result
9061
8877
  );
9062
8878
  } catch (err) {
9063
8879
  return errorResult(err);