@questionbase/deskfree 0.3.0-alpha.35 → 0.3.0-alpha.37

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.d.ts CHANGED
@@ -418,7 +418,7 @@ interface WsNotification {
418
418
  interface Task {
419
419
  taskId: string;
420
420
  title: string;
421
- status: 'pending' | 'active' | 'review' | 'done';
421
+ status: 'open' | 'done';
422
422
  instructions?: string;
423
423
  createdAt: string;
424
424
  updatedAt: string;
@@ -431,7 +431,7 @@ interface Task {
431
431
  interface TaskSummary {
432
432
  taskId: string;
433
433
  title: string;
434
- status: 'pending' | 'active' | 'review' | 'done';
434
+ status: 'open' | 'done';
435
435
  instructions: string | null;
436
436
  fileId: string | null;
437
437
  fileName: string | null;
@@ -468,13 +468,20 @@ interface TaskWithContext extends Task {
468
468
  }
469
469
  interface CompleteTaskInput {
470
470
  taskId: string;
471
- outcome: 'done' | 'blocked';
472
- summary?: string;
471
+ learnings?: {
472
+ reasoning: string;
473
+ globalWoW?: string;
474
+ initiativeContent?: string;
475
+ };
476
+ followUps?: Array<{
477
+ title: string;
478
+ instructions?: string;
479
+ }>;
473
480
  }
474
481
  interface WorkspaceStateTask {
475
482
  taskId: string;
476
483
  title: string;
477
- status: 'pending' | 'active' | 'review' | 'done';
484
+ status: 'open' | 'done';
478
485
  instructions?: string | null;
479
486
  fileId?: string | null;
480
487
  fileName?: string | null;
@@ -564,6 +571,7 @@ declare class DeskFreeClient {
564
571
  userId?: string;
565
572
  content: string;
566
573
  taskId?: string;
574
+ messageType?: 'ask' | 'notify';
567
575
  attachments?: Array<{
568
576
  s3Key: string;
569
577
  name: string;
@@ -659,10 +667,8 @@ declare class DeskFreeClient {
659
667
  }): Promise<{
660
668
  success: boolean;
661
669
  }>;
662
- /** Complete a task with an outcome. Moves task to human. */
663
- completeTask(input: CompleteTaskInput): Promise<Task & {
664
- outcome: 'done' | 'blocked';
665
- }>;
670
+ /** Complete a task. Marks it as done. */
671
+ completeTask(input: CompleteTaskInput): Promise<Task>;
666
672
  /** Reopen a completed/human task back to bot status for further work. */
667
673
  reopenTask(input: {
668
674
  taskId: string;
@@ -701,20 +707,10 @@ declare class DeskFreeClient {
701
707
  content: string;
702
708
  };
703
709
  context?: string;
710
+ taskId?: string;
704
711
  }): Promise<{
705
712
  messageId: string;
706
713
  }>;
707
- /** Update ways of working and/or initiative content based on learnings. */
708
- updateKnowledge(input: {
709
- globalWoW?: string;
710
- initiativeId?: string;
711
- initiativeContent?: string;
712
- reasoning: string;
713
- }): Promise<{
714
- success: boolean;
715
- globalVersion?: number;
716
- initiativeVersion?: number;
717
- }>;
718
714
  /**
719
715
  * Lightweight health check that verifies connectivity and authentication.
720
716
  *
package/dist/index.js CHANGED
@@ -3932,7 +3932,7 @@ var DeskFreeClient = class {
3932
3932
  this.requireNonEmpty(input.model, "model");
3933
3933
  return this.request("POST", "tasks.reportUsage", input);
3934
3934
  }
3935
- /** Complete a task with an outcome. Moves task to human. */
3935
+ /** Complete a task. Marks it as done. */
3936
3936
  async completeTask(input) {
3937
3937
  this.requireNonEmpty(input.taskId, "taskId");
3938
3938
  return this.request("POST", "tasks.complete", input);
@@ -3966,11 +3966,6 @@ var DeskFreeClient = class {
3966
3966
  }
3967
3967
  return this.request("POST", "tasks.propose", input);
3968
3968
  }
3969
- /** Update ways of working and/or initiative content based on learnings. */
3970
- async updateKnowledge(input) {
3971
- this.requireNonEmpty(input.reasoning, "reasoning");
3972
- return this.request("POST", "waysOfWorking.update", input);
3973
- }
3974
3969
  /**
3975
3970
  * Lightweight health check that verifies connectivity and authentication.
3976
3971
  *
@@ -4492,10 +4487,10 @@ function buildBodyForAgent(task, content, recentMessages) {
4492
4487
  return prefix + "\n\n" + content;
4493
4488
  }
4494
4489
  function resolveTaskRouting(task, hasAttachments) {
4495
- if (task.status === "active") {
4490
+ if (task.status === "open") {
4496
4491
  return { target: "runner" };
4497
4492
  }
4498
- if (hasAttachments && (task.status === "review" || task.status === "done")) {
4493
+ if (hasAttachments && task.status === "done") {
4499
4494
  return { target: "auto-reopen" };
4500
4495
  }
4501
4496
  return { target: "orchestrator" };
@@ -7982,7 +7977,7 @@ var ORCHESTRATOR_TOOLS = {
7982
7977
  },
7983
7978
  REOPEN_TASK: {
7984
7979
  name: "deskfree_reopen_task",
7985
- description: "Reopen a completed or human-side task back to bot status. Use when a human message in a task thread indicates more work is needed. The task becomes available for a worker to claim.",
7980
+ description: "Reopen a task (from review or done) back to pending. Use when more work is needed on a task. Works on both completed and in-review tasks.",
7986
7981
  parameters: Type.Object({
7987
7982
  taskId: Type.String({ description: "Task UUID to reopen" }),
7988
7983
  reason: Type.Optional(
@@ -7992,30 +7987,6 @@ var ORCHESTRATOR_TOOLS = {
7992
7987
  )
7993
7988
  })
7994
7989
  },
7995
- UPDATE_KNOWLEDGE: {
7996
- name: "deskfree_update_knowledge",
7997
- description: "Update ways of working and/or initiative content based on learnings from completed tasks.",
7998
- parameters: Type.Object({
7999
- globalWoW: Type.Optional(
8000
- Type.String({
8001
- description: "Full updated global ways-of-working markdown content"
8002
- })
8003
- ),
8004
- initiativeId: Type.Optional(
8005
- Type.String({
8006
- description: "Initiative ID to update"
8007
- })
8008
- ),
8009
- initiativeContent: Type.Optional(
8010
- Type.String({
8011
- description: "Full updated initiative content markdown (required if initiativeId provided)"
8012
- })
8013
- ),
8014
- reasoning: Type.String({
8015
- description: "Explanation of why these updates were made and what was learned"
8016
- })
8017
- })
8018
- },
8019
7990
  SEND_MESSAGE: {
8020
7991
  name: "deskfree_send_message",
8021
7992
  description: "Send a message to the human. Keep it short \u2014 1-3 sentences. No walls of text.",
@@ -8124,6 +8095,11 @@ var ORCHESTRATOR_TOOLS = {
8124
8095
  minItems: 1,
8125
8096
  maxItems: 20
8126
8097
  }
8098
+ ),
8099
+ taskId: Type.Optional(
8100
+ Type.String({
8101
+ description: "Task ID to thread this proposal into (for follow-up proposals from within a task)"
8102
+ })
8127
8103
  )
8128
8104
  })
8129
8105
  }
@@ -8146,36 +8122,63 @@ var SHARED_TOOLS = {
8146
8122
  },
8147
8123
  COMPLETE_TASK: {
8148
8124
  name: "deskfree_complete_task",
8149
- description: "Finish a task. Transitions to review/done status based on outcome.",
8125
+ description: "Mark a task as done. Only call when truly finished and human confirmed. Pass learnings to update knowledge atomically with completion. Pass followUps to propose next tasks.",
8150
8126
  parameters: Type.Object({
8151
8127
  taskId: Type.String({ description: "Task UUID" }),
8152
- outcome: Type.Union(
8153
- [
8154
- Type.Literal("review"),
8155
- Type.Literal("done"),
8156
- Type.Literal("blocked"),
8157
- Type.Literal("cancelled")
8158
- ],
8159
- {
8160
- description: '"review" = ready for human review, "done" = work complete, "blocked" = need human input, "cancelled" = task cancelled'
8161
- }
8162
- ),
8163
- summary: Type.Optional(
8164
- Type.String({
8165
- description: "1-2 sentence summary of what was done and the outcome."
8166
- })
8167
- ),
8168
8128
  learnings: Type.Optional(
8169
- Type.String({
8170
- description: "Key insights, patterns, or lessons learned from completing this task. Used to improve future work."
8171
- })
8129
+ Type.Object(
8130
+ {
8131
+ reasoning: Type.String({
8132
+ description: "Why these updates matter \u2014 what was learned from this task"
8133
+ }),
8134
+ globalWoW: Type.Optional(
8135
+ Type.String({
8136
+ description: "Full updated global Ways of Working markdown content (full replacement, not diff)"
8137
+ })
8138
+ ),
8139
+ initiativeId: Type.Optional(
8140
+ Type.String({
8141
+ description: "Initiative ID to update"
8142
+ })
8143
+ ),
8144
+ initiativeContent: Type.Optional(
8145
+ Type.String({
8146
+ description: "Full updated initiative content markdown (full replacement, not diff)"
8147
+ })
8148
+ )
8149
+ },
8150
+ {
8151
+ description: "Knowledge updates to apply atomically with task completion. Include only if there are genuine learnings to record."
8152
+ }
8153
+ )
8154
+ ),
8155
+ followUps: Type.Optional(
8156
+ Type.Array(
8157
+ Type.Object({
8158
+ title: Type.String({
8159
+ description: "Follow-up task title (max 200 chars)"
8160
+ }),
8161
+ instructions: Type.Optional(
8162
+ Type.String({
8163
+ description: "Instructions for the follow-up task"
8164
+ })
8165
+ )
8166
+ }),
8167
+ {
8168
+ description: "Follow-up tasks to propose for human approval (max 10). Use when the work revealed clear next steps.",
8169
+ maxItems: 10
8170
+ }
8171
+ )
8172
8172
  )
8173
8173
  })
8174
8174
  },
8175
8175
  SEND_MESSAGE: {
8176
8176
  name: "deskfree_send_message",
8177
- description: "Send a message in the task thread. Keep it short \u2014 1-3 sentences. No walls of text.",
8177
+ description: "Send a message in the task thread. Keep it short \u2014 1-3 sentences.",
8178
8178
  parameters: Type.Object({
8179
+ type: Type.Union([Type.Literal("notify"), Type.Literal("ask")], {
8180
+ description: "notify = progress update (quiet, collapsible). ask = needs human attention (surfaces to main thread). Terminate after sending an ask."
8181
+ }),
8179
8182
  content: Type.String({
8180
8183
  description: "Message content."
8181
8184
  }),
@@ -8280,6 +8283,11 @@ var SHARED_TOOLS = {
8280
8283
  minItems: 1,
8281
8284
  maxItems: 20
8282
8285
  }
8286
+ ),
8287
+ taskId: Type.Optional(
8288
+ Type.String({
8289
+ description: "Task ID to thread this proposal into (for follow-up proposals from within a task)"
8290
+ })
8283
8291
  )
8284
8292
  })
8285
8293
  }
@@ -8300,7 +8308,16 @@ var WORKER_TOOLS = {
8300
8308
  UPDATE_FILE: SHARED_TOOLS.UPDATE_FILE,
8301
8309
  COMPLETE_TASK: SHARED_TOOLS.COMPLETE_TASK,
8302
8310
  SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
8303
- PROPOSE: SHARED_TOOLS.PROPOSE
8311
+ PROPOSE: SHARED_TOOLS.PROPOSE,
8312
+ READ_SKILL: {
8313
+ name: "deskfree_read_skill_section",
8314
+ description: "Load full instructions for a skill. Use after deskfree_start_task when you need the complete skill guide beyond the critical section summary.",
8315
+ parameters: Type.Object({
8316
+ skillId: Type.String({
8317
+ description: "Skill ID to load instructions for"
8318
+ })
8319
+ })
8320
+ }
8304
8321
  };
8305
8322
  var CHANNEL_META = {
8306
8323
  name: "DeskFree",
@@ -8877,8 +8894,8 @@ You are the orchestrator. Your job: turn human intent into approved tasks, then
8877
8894
 
8878
8895
  **Match the human's energy.** Short message \u2192 short reply. Casual tone \u2192 casual response. Don't over-explain, don't lecture, don't pad responses.
8879
8896
 
8880
- You do NOT claim tasks or do work directly. Sub-agents handle execution.
8881
- - When a human writes in a task thread, you receive it with recent context. Use \`deskfree_reopen_task\` if it needs more work.
8897
+ You do NOT claim tasks or do work directly \u2014 you have no access to deskfree_start_task. Spawn a sub-agent for each approved task and pass it the taskId.
8898
+ - When a human writes in a task thread, decide: does it need bot action? If yes \u2192 reopen and spawn sub-agent. If it's confirmation ("looks good") \u2192 complete the task. If deferred ("I'll check later") \u2192 leave it.
8882
8899
  - Write task instructions as rich markdown (bold, lists, inline code \u2014 no # headers). Brief a contractor who has never seen the codebase.
8883
8900
  - Estimate token cost per task \u2014 consider files to read, reasoning, output.
8884
8901
  - One initiative per proposal \u2014 make multiple calls for multiple initiatives.
@@ -8889,10 +8906,12 @@ You are a worker sub-agent. Call \`deskfree_start_task\` with your taskId to cla
8889
8906
  Tools: deskfree_start_task, deskfree_update_file, deskfree_complete_task, deskfree_send_message, deskfree_propose.
8890
8907
  - Claim your task first with deskfree_start_task \u2014 this loads instructions, messages, and file context.
8891
8908
  - Save work to linked files with deskfree_update_file (incrementally).
8892
- - Complete with deskfree_complete_task when done (summary required for outcome "done"). Include learnings if applicable.
8893
- - If you need human input to proceed, send a message explaining what you need, then complete with outcome "review" \u2014 this marks the task as needing attention.
8894
- - Propose follow-up tasks with deskfree_propose if you discover more work.
8895
- - Keep messages and file updates concise. Write like a senior colleague giving a status update \u2014 not a report. 1-3 sentences for messages unless the human asked for detail.`;
8909
+ - Use deskfree_send_message with type "notify" for progress updates \u2014 what you're doing, what you found. Keep it brief.
8910
+ - Use deskfree_send_message with type "ask" when you need human input OR when your work is done for review. This surfaces to the main thread. Terminate after sending an ask.
8911
+ - When completing: pass "learnings" to deskfree_complete_task if you have knowledge updates (WoW or initiative content). Pass "followUps" if the work revealed clear next steps. Knowledge updates and proposals happen atomically with completion \u2014 no separate tool calls needed.
8912
+ - Only complete when the human has confirmed or no review is needed.
8913
+ - Write like a senior colleague giving a status update \u2014 not a report. 1-3 sentences for messages.
8914
+ - On 409 or 404 errors: STOP. Do not retry the same taskId. Call deskfree_state to find available tasks.`;
8896
8915
  function getDeskFreeContext(sessionKey) {
8897
8916
  const isWorker = sessionKey && (sessionKey.includes(":sub:") || sessionKey.includes(":spawn:") || sessionKey.includes(":run:"));
8898
8917
  const directive = isWorker ? DESKFREE_WORKER_DIRECTIVE : DESKFREE_AGENT_DIRECTIVE;
@@ -8901,6 +8920,91 @@ function getDeskFreeContext(sessionKey) {
8901
8920
  <!-- deskfree-plugin:${PLUGIN_VERSION} -->`;
8902
8921
  }
8903
8922
 
8923
+ // src/skill-scan-hook.ts
8924
+ var SCANNABLE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".sh", ".py", ".js", ".ts"]);
8925
+ var SKILLS_DIR_PATTERN = /(?:^|\/|\\)skills\/[^/\\]+\//;
8926
+ function isSkillFile(filePath) {
8927
+ if (!filePath || typeof filePath !== "string") return false;
8928
+ const normalized = filePath.replace(/\\/g, "/");
8929
+ if (!SKILLS_DIR_PATTERN.test(normalized)) return false;
8930
+ const lastDot = normalized.lastIndexOf(".");
8931
+ if (lastDot === -1) return false;
8932
+ const ext = normalized.slice(lastDot).toLowerCase();
8933
+ return SCANNABLE_EXTENSIONS.has(ext);
8934
+ }
8935
+ var WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["write", "Write"]);
8936
+ var EDIT_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "Edit"]);
8937
+ function extractPath(params) {
8938
+ const p = params["path"] ?? params["file_path"];
8939
+ return typeof p === "string" && p.length > 0 ? p : null;
8940
+ }
8941
+ function extractContent(toolName, params) {
8942
+ if (WRITE_TOOL_NAMES.has(toolName)) {
8943
+ const c = params["content"];
8944
+ return typeof c === "string" ? c : null;
8945
+ }
8946
+ if (EDIT_TOOL_NAMES.has(toolName)) {
8947
+ const c = params["newText"] ?? params["new_string"];
8948
+ return typeof c === "string" ? c : null;
8949
+ }
8950
+ return null;
8951
+ }
8952
+ function formatFindings(result) {
8953
+ return result.findings.map(
8954
+ (f) => ` [${f.severity.toUpperCase()}] ${f.description}` + (f.line != null ? ` (line ${f.line})` : "")
8955
+ ).join("\n");
8956
+ }
8957
+ function createSkillScanHook(scanner, log) {
8958
+ return (event, _ctx) => {
8959
+ const toolName = event["toolName"];
8960
+ if (typeof toolName !== "string") return;
8961
+ if (!WRITE_TOOL_NAMES.has(toolName) && !EDIT_TOOL_NAMES.has(toolName)) {
8962
+ return;
8963
+ }
8964
+ const params = event["params"];
8965
+ if (!params || typeof params !== "object") return;
8966
+ const p = params;
8967
+ const filePath = extractPath(p);
8968
+ if (!filePath) return;
8969
+ if (!isSkillFile(filePath)) return;
8970
+ const content = extractContent(toolName, p);
8971
+ if (!content || content.length === 0) return;
8972
+ const filename = filePath.split("/").pop() ?? "unknown";
8973
+ const inputs = [{ filename, content }];
8974
+ let result;
8975
+ try {
8976
+ result = scanner(inputs);
8977
+ } catch (err) {
8978
+ log.warn(
8979
+ `[skill-scan] Scanner error for ${filePath}: ${err instanceof Error ? err.message : String(err)}`
8980
+ );
8981
+ return;
8982
+ }
8983
+ log.info(
8984
+ `[skill-scan] ${filePath}: verdict=${result.verdict} score=${result.score} findings=${result.findings.length}`
8985
+ );
8986
+ if (result.verdict === "dangerous") {
8987
+ const findings = formatFindings(result);
8988
+ log.warn(`[skill-scan] BLOCKED write to ${filePath}:
8989
+ ${findings}`);
8990
+ return {
8991
+ block: true,
8992
+ blockReason: `\u{1F6AB} Security scan BLOCKED write to "${filename}" \u2014 dangerous (score: ${result.score}/100)
8993
+
8994
+ Findings:
8995
+ ${findings}
8996
+
8997
+ Remove the dangerous patterns and retry.`
8998
+ };
8999
+ }
9000
+ if (result.verdict === "suspicious") {
9001
+ const findings = formatFindings(result);
9002
+ log.warn(`[skill-scan] Suspicious write to ${filePath}:
9003
+ ${findings}`);
9004
+ }
9005
+ };
9006
+ }
9007
+
8904
9008
  // src/tools.ts
8905
9009
  function resolveAccountFromConfig(api) {
8906
9010
  const cfg = api.runtime.config.loadConfig();
@@ -9016,9 +9120,9 @@ function suggestionForErrorType(type, statusCode) {
9016
9120
  if (statusCode === 429)
9017
9121
  return "Too many requests. Wait a moment before trying again.";
9018
9122
  if (statusCode === 409)
9019
- return "Try calling deskfree_state to see available tasks.";
9123
+ return "STOP \u2014 this task is already claimed or completed. Do NOT retry. Call deskfree_state to find available tasks.";
9020
9124
  if (statusCode === 404)
9021
- return "Use deskfree_state to see all available tasks and verify the taskId.";
9125
+ return "STOP \u2014 task not found or not in claimable state. Do NOT retry. Call deskfree_state to find available tasks.";
9022
9126
  return "Check your input parameters and try again.";
9023
9127
  default:
9024
9128
  return "";
@@ -9188,34 +9292,46 @@ function makeCompleteTaskHandler(client) {
9188
9292
  return async (_id, params) => {
9189
9293
  try {
9190
9294
  const taskId = validateStringParam(params, "taskId", true);
9191
- const outcome = validateEnumParam(
9192
- params,
9193
- "outcome",
9194
- ["done", "blocked"],
9195
- true
9196
- );
9197
- const summary = validateStringParam(params, "summary", false);
9198
- const evaluation = params?.evaluation;
9295
+ let learnings;
9296
+ if (params["learnings"] && typeof params["learnings"] === "object") {
9297
+ const raw = params["learnings"];
9298
+ const reasoning = typeof raw["reasoning"] === "string" ? raw["reasoning"] : "";
9299
+ if (reasoning.trim()) {
9300
+ learnings = {
9301
+ reasoning,
9302
+ globalWoW: typeof raw["globalWoW"] === "string" && raw["globalWoW"].trim() ? raw["globalWoW"] : void 0,
9303
+ initiativeContent: typeof raw["initiativeContent"] === "string" && raw["initiativeContent"].trim() ? raw["initiativeContent"] : void 0
9304
+ };
9305
+ }
9306
+ }
9307
+ let followUps;
9308
+ if (Array.isArray(params["followUps"]) && params["followUps"].length > 0) {
9309
+ const parsed = params["followUps"].filter((item) => typeof item === "object" && item !== null).map((item) => {
9310
+ const obj = item;
9311
+ const title = typeof obj["title"] === "string" ? obj["title"].trim() : "";
9312
+ const instructions = typeof obj["instructions"] === "string" ? obj["instructions"] : void 0;
9313
+ return { title, instructions };
9314
+ }).filter((item) => item.title.length > 0);
9315
+ if (parsed.length > 0) followUps = parsed;
9316
+ }
9199
9317
  const result = await client.completeTask({
9200
9318
  taskId,
9201
- outcome,
9202
- summary,
9203
- ...evaluation ? { evaluation } : {}
9319
+ learnings,
9320
+ followUps
9204
9321
  });
9205
9322
  setActiveTaskId(null);
9206
- const summaryVerb = outcome === "done" ? "completed" : "blocked";
9207
- const icon = outcome === "done" ? "\u2705" : "\u{1F6AB}";
9208
- await client.sendMessage({
9209
- content: `${icon} Task ${summaryVerb}: "${result.title}"`
9210
- }).catch(() => {
9211
- });
9323
+ const actions = ["Use deskfree_state to check for other tasks"];
9324
+ if (learnings?.globalWoW) actions.unshift("Ways of Working updated");
9325
+ if (learnings?.initiativeContent)
9326
+ actions.unshift("Initiative content updated");
9327
+ if (followUps)
9328
+ actions.unshift(
9329
+ `${followUps.length} follow-up task(s) proposed for human approval`
9330
+ );
9212
9331
  return formatTaskResponse(
9213
9332
  result,
9214
- `Task "${result.title}" marked as ${summaryVerb} \u2014 waiting for human`,
9215
- [
9216
- outcome === "done" ? "Human will review your work summary" : "Human will review the blocker and provide guidance",
9217
- "Use deskfree_state to check for other tasks"
9218
- ]
9333
+ `Task "${result.title}" marked as done \u2705`,
9334
+ actions
9219
9335
  );
9220
9336
  } catch (err) {
9221
9337
  return errorResult(err);
@@ -9241,13 +9357,16 @@ function makeSendMessageHandler(client) {
9241
9357
  try {
9242
9358
  const content = validateStringParam(params, "content", true);
9243
9359
  const taskId = validateStringParam(params, "taskId", false);
9244
- await client.sendMessage({ content, taskId });
9360
+ const messageType = validateEnumParam(
9361
+ params,
9362
+ "type",
9363
+ ["notify", "ask"],
9364
+ true
9365
+ );
9366
+ await client.sendMessage({ content, taskId, messageType });
9245
9367
  return formatConfirmation(
9246
- `Message sent${taskId ? ` to task ${taskId}` : ""}`,
9247
- [
9248
- "Message delivered to the human",
9249
- taskId ? "Continue working on the task or wait for response" : "Check for response with task messages"
9250
- ]
9368
+ `Message sent (${messageType})${taskId ? ` to task ${taskId}` : ""}`,
9369
+ messageType === "ask" ? ["Message surfaced to human \u2014 terminate and wait for response"] : ["Progress update sent \u2014 continue working"]
9251
9370
  );
9252
9371
  } catch (err) {
9253
9372
  return errorResult(err);
@@ -9260,6 +9379,7 @@ function makeProposeHandler(client) {
9260
9379
  const tasks = parseProposeTasks(params?.tasks);
9261
9380
  const context = validateStringParam(params, "context", false);
9262
9381
  const initiative = parseInitiative(params?.initiative);
9382
+ const taskId = validateStringParam(params, "taskId", false);
9263
9383
  const result = await client.proposePlan({
9264
9384
  tasks: tasks.map((t) => ({
9265
9385
  title: t.title,
@@ -9270,7 +9390,8 @@ function makeProposeHandler(client) {
9270
9390
  scheduledFor: t.scheduledFor
9271
9391
  })),
9272
9392
  initiative,
9273
- context: context ?? void 0
9393
+ context: context ?? void 0,
9394
+ taskId: taskId ?? void 0
9274
9395
  });
9275
9396
  return formatConfirmation(
9276
9397
  `Proposed ${tasks.length} task${tasks.length === 1 ? "" : "s"} for human approval`,
@@ -9343,41 +9464,6 @@ function createOrchestratorTools(api) {
9343
9464
  ...ORCHESTRATOR_TOOLS.REOPEN_TASK,
9344
9465
  execute: makeReopenTaskHandler(client)
9345
9466
  },
9346
- {
9347
- ...ORCHESTRATOR_TOOLS.UPDATE_KNOWLEDGE,
9348
- async execute(_id, params) {
9349
- try {
9350
- const globalWoW = validateStringParam(params, "globalWoW", false);
9351
- const initiativeId = validateStringParam(
9352
- params,
9353
- "initiativeId",
9354
- false
9355
- );
9356
- const initiativeContent = validateStringParam(
9357
- params,
9358
- "initiativeContent",
9359
- false
9360
- );
9361
- const reasoning = validateStringParam(params, "reasoning", true);
9362
- const result = await client.updateKnowledge({
9363
- globalWoW,
9364
- initiativeId,
9365
- initiativeContent,
9366
- reasoning
9367
- });
9368
- return {
9369
- content: [
9370
- {
9371
- type: "text",
9372
- text: `Knowledge updated successfully: ${JSON.stringify(result, null, 2)}`
9373
- }
9374
- ]
9375
- };
9376
- } catch (err) {
9377
- return errorResult(err);
9378
- }
9379
- }
9380
- },
9381
9467
  {
9382
9468
  ...ORCHESTRATOR_TOOLS.SEND_MESSAGE,
9383
9469
  execute: makeSendMessageHandler(client)
@@ -9392,6 +9478,7 @@ function createWorkerTools(api) {
9392
9478
  const account = resolveAccountFromConfig(api);
9393
9479
  if (!account) return null;
9394
9480
  const client = new DeskFreeClient(account.botToken, account.apiUrl);
9481
+ const cachedSkillContext = /* @__PURE__ */ new Map();
9395
9482
  return [
9396
9483
  {
9397
9484
  ...WORKER_TOOLS.START_TASK,
@@ -9402,19 +9489,24 @@ function createWorkerTools(api) {
9402
9489
  const result = await client.claimTask({ taskId, runnerId });
9403
9490
  setActiveTaskId(taskId);
9404
9491
  let skillInstructions = "";
9492
+ cachedSkillContext.clear();
9405
9493
  if (result.skillContext?.length) {
9494
+ for (const s of result.skillContext) {
9495
+ cachedSkillContext.set(s.skillId, {
9496
+ displayName: s.displayName,
9497
+ instructions: s.instructions
9498
+ });
9499
+ }
9406
9500
  skillInstructions = result.skillContext.map(
9407
9501
  (s) => `
9408
- \u26A0\uFE0F SKILL: ${s.displayName}
9409
- ${s.criticalSection}
9410
-
9411
- ${s.instructions}`
9502
+ \u26A0\uFE0F SKILL: ${s.displayName} (ID: ${s.skillId})
9503
+ ${s.criticalSection}`
9412
9504
  ).join("\n\n---\n");
9413
9505
  }
9414
9506
  const trimmedTask = trimTaskContext(result);
9415
9507
  const taskJson = JSON.stringify(
9416
9508
  {
9417
- summary: `Claimed task "${result.title}" \u2014 full context loaded${result.skillContext?.length ? ` (${result.skillContext.length} skill${result.skillContext.length > 1 ? "s" : ""} active)` : ""}`,
9509
+ summary: `Claimed task "${result.title}" \u2014 full context loaded${result.skillContext?.length ? ` (${result.skillContext.length} skill${result.skillContext.length > 1 ? "s" : ""} loaded \u2014 use deskfree_read_skill_section for full details)` : ""}`,
9418
9510
  mode: trimmedTask.mode ?? "work",
9419
9511
  nextActions: [
9420
9512
  "Read the instructions and message history carefully",
@@ -9463,6 +9555,37 @@ ${s.instructions}`
9463
9555
  {
9464
9556
  ...WORKER_TOOLS.PROPOSE,
9465
9557
  execute: makeProposeHandler(client)
9558
+ },
9559
+ {
9560
+ ...WORKER_TOOLS.READ_SKILL,
9561
+ async execute(_id, params) {
9562
+ try {
9563
+ const skillId = validateStringParam(params, "skillId", true);
9564
+ const cached = cachedSkillContext.get(skillId);
9565
+ if (!cached) {
9566
+ return {
9567
+ content: [
9568
+ {
9569
+ type: "text",
9570
+ text: `Skill ${skillId} not found in current task context. Available: ${[...cachedSkillContext.keys()].join(", ") || "none"}`
9571
+ }
9572
+ ]
9573
+ };
9574
+ }
9575
+ return {
9576
+ content: [
9577
+ {
9578
+ type: "text",
9579
+ text: `## ${cached.displayName} \u2014 Full Instructions
9580
+
9581
+ ${cached.instructions}`
9582
+ }
9583
+ ]
9584
+ };
9585
+ } catch (err) {
9586
+ return errorResult(err);
9587
+ }
9588
+ }
9466
9589
  }
9467
9590
  ];
9468
9591
  }
@@ -9574,6 +9697,35 @@ var OfflineQueue = class {
9574
9697
  };
9575
9698
 
9576
9699
  // src/index.ts
9700
+ function createLazyScanner(log) {
9701
+ let resolved = false;
9702
+ let scanFn = null;
9703
+ const scannerPath = ["..", "..", "backend", "src", "util", "util.skillScanner"].join("/");
9704
+ import(
9705
+ /* @vite-ignore */
9706
+ scannerPath
9707
+ ).then((mod) => {
9708
+ if (typeof mod.scanSkill === "function") {
9709
+ scanFn = mod.scanSkill;
9710
+ log.info("[deskfree] Skill security scanner loaded");
9711
+ }
9712
+ resolved = true;
9713
+ }).catch(() => {
9714
+ resolved = true;
9715
+ log.warn("[deskfree] Skill scanner not available \u2014 scan hook will pass through");
9716
+ });
9717
+ const safeFallback = (inputs) => ({
9718
+ verdict: "safe",
9719
+ score: 100,
9720
+ findings: [],
9721
+ scannedAt: /* @__PURE__ */ new Date(),
9722
+ filesScanned: inputs.map((i) => i.filename)
9723
+ });
9724
+ return (inputs) => {
9725
+ if (!resolved || !scanFn) return safeFallback(inputs);
9726
+ return scanFn(inputs);
9727
+ };
9728
+ }
9577
9729
  var plugin = {
9578
9730
  id: "deskfree",
9579
9731
  name: "DeskFree",
@@ -9593,6 +9745,8 @@ var plugin = {
9593
9745
  }
9594
9746
  return allTools.length > 0 ? allTools : null;
9595
9747
  });
9748
+ const scanner = createLazyScanner(api.logger);
9749
+ api.on("before_tool_call", createSkillScanHook(scanner, api.logger));
9596
9750
  api.on("before_agent_start", (_event, ctx) => {
9597
9751
  return { prependContext: getDeskFreeContext(ctx.sessionKey) };
9598
9752
  });