@questionbase/deskfree 0.3.0-alpha.34 → 0.3.0-alpha.36
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 +176 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/deskfree/SKILL.md +115 -149
package/dist/index.js
CHANGED
|
@@ -8018,7 +8018,7 @@ var ORCHESTRATOR_TOOLS = {
|
|
|
8018
8018
|
},
|
|
8019
8019
|
SEND_MESSAGE: {
|
|
8020
8020
|
name: "deskfree_send_message",
|
|
8021
|
-
description: "Send a message
|
|
8021
|
+
description: "Send a message to the human. Keep it short \u2014 1-3 sentences. No walls of text.",
|
|
8022
8022
|
parameters: Type.Object({
|
|
8023
8023
|
content: Type.String({
|
|
8024
8024
|
description: "Message content."
|
|
@@ -8046,7 +8046,7 @@ var ORCHESTRATOR_TOOLS = {
|
|
|
8046
8046
|
description: "New initiative title (max 200 chars)"
|
|
8047
8047
|
}),
|
|
8048
8048
|
content: Type.String({
|
|
8049
|
-
description: "
|
|
8049
|
+
description: "2-3 sentences \u2014 what we're doing and why. Aspirational, not operational. No headers or formal structure."
|
|
8050
8050
|
})
|
|
8051
8051
|
},
|
|
8052
8052
|
{
|
|
@@ -8071,14 +8071,7 @@ var ORCHESTRATOR_TOOLS = {
|
|
|
8071
8071
|
}),
|
|
8072
8072
|
instructions: Type.Optional(
|
|
8073
8073
|
Type.String({
|
|
8074
|
-
description: "
|
|
8075
|
-
})
|
|
8076
|
-
),
|
|
8077
|
-
substeps: Type.Optional(
|
|
8078
|
-
Type.Array(Type.String(), {
|
|
8079
|
-
description: "DEPRECATED \u2014 put steps in the instructions field as markdown instead. This field is ignored.",
|
|
8080
|
-
minItems: 1,
|
|
8081
|
-
maxItems: 20
|
|
8074
|
+
description: "Concise instructions in simple markdown (bold, lists, inline code \u2014 no # headers). Brief a contractor: what to do, what done looks like. Skip obvious context."
|
|
8082
8075
|
})
|
|
8083
8076
|
),
|
|
8084
8077
|
file: Type.Optional(
|
|
@@ -8169,7 +8162,7 @@ var SHARED_TOOLS = {
|
|
|
8169
8162
|
),
|
|
8170
8163
|
summary: Type.Optional(
|
|
8171
8164
|
Type.String({
|
|
8172
|
-
description: "
|
|
8165
|
+
description: "1-2 sentence summary of what was done and the outcome."
|
|
8173
8166
|
})
|
|
8174
8167
|
),
|
|
8175
8168
|
learnings: Type.Optional(
|
|
@@ -8181,7 +8174,7 @@ var SHARED_TOOLS = {
|
|
|
8181
8174
|
},
|
|
8182
8175
|
SEND_MESSAGE: {
|
|
8183
8176
|
name: "deskfree_send_message",
|
|
8184
|
-
description: "Send a message in the task thread
|
|
8177
|
+
description: "Send a message in the task thread. Keep it short \u2014 1-3 sentences. No walls of text.",
|
|
8185
8178
|
parameters: Type.Object({
|
|
8186
8179
|
content: Type.String({
|
|
8187
8180
|
description: "Message content."
|
|
@@ -8209,7 +8202,7 @@ var SHARED_TOOLS = {
|
|
|
8209
8202
|
description: "New initiative title (max 200 chars)"
|
|
8210
8203
|
}),
|
|
8211
8204
|
content: Type.String({
|
|
8212
|
-
description: "
|
|
8205
|
+
description: "2-3 sentences \u2014 what we're doing and why. Aspirational, not operational. No headers or formal structure."
|
|
8213
8206
|
})
|
|
8214
8207
|
},
|
|
8215
8208
|
{
|
|
@@ -8234,14 +8227,7 @@ var SHARED_TOOLS = {
|
|
|
8234
8227
|
}),
|
|
8235
8228
|
instructions: Type.Optional(
|
|
8236
8229
|
Type.String({
|
|
8237
|
-
description: "
|
|
8238
|
-
})
|
|
8239
|
-
),
|
|
8240
|
-
substeps: Type.Optional(
|
|
8241
|
-
Type.Array(Type.String(), {
|
|
8242
|
-
description: "DEPRECATED \u2014 put steps in the instructions field as markdown instead. This field is ignored.",
|
|
8243
|
-
minItems: 1,
|
|
8244
|
-
maxItems: 20
|
|
8230
|
+
description: "Concise instructions in simple markdown (bold, lists, inline code \u2014 no # headers). Brief a contractor: what to do, what done looks like. Skip obvious context."
|
|
8245
8231
|
})
|
|
8246
8232
|
),
|
|
8247
8233
|
file: Type.Optional(
|
|
@@ -8314,7 +8300,14 @@ var WORKER_TOOLS = {
|
|
|
8314
8300
|
UPDATE_FILE: SHARED_TOOLS.UPDATE_FILE,
|
|
8315
8301
|
COMPLETE_TASK: SHARED_TOOLS.COMPLETE_TASK,
|
|
8316
8302
|
SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
|
|
8317
|
-
PROPOSE: SHARED_TOOLS.PROPOSE
|
|
8303
|
+
PROPOSE: SHARED_TOOLS.PROPOSE,
|
|
8304
|
+
READ_SKILL: {
|
|
8305
|
+
name: "deskfree_read_skill_section",
|
|
8306
|
+
description: "Load full instructions for a skill. Use after deskfree_start_task when you need the complete skill guide beyond the critical section summary.",
|
|
8307
|
+
parameters: Type.Object({
|
|
8308
|
+
skillId: Type.String({ description: "Skill ID to load instructions for" })
|
|
8309
|
+
})
|
|
8310
|
+
}
|
|
8318
8311
|
};
|
|
8319
8312
|
var CHANNEL_META = {
|
|
8320
8313
|
name: "DeskFree",
|
|
@@ -8874,25 +8867,30 @@ var deskFreePlugin = {
|
|
|
8874
8867
|
var DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 Orchestrator
|
|
8875
8868
|
You are the orchestrator. Your job: turn human intent into approved tasks, then dispatch work.
|
|
8876
8869
|
|
|
8877
|
-
**
|
|
8870
|
+
**Main thread = short and snappy.** Keep responses to 1-3 sentences. Quick back-and-forth conversation is great \u2014 clarify, riff, brainstorm in short messages like a real chat. But if something needs deep research, multiple rounds of clarification, or a deliverable \u2014 propose a task and move the work to a thread.
|
|
8871
|
+
|
|
8872
|
+
**The core loop: propose \u2192 approve \u2192 work.**
|
|
8878
8873
|
|
|
8879
8874
|
1. **Check state** \u2192 \`deskfree_state\` \u2014 see tasks, initiatives, ways of working.
|
|
8880
8875
|
2. **Propose** \u2192 \`deskfree_propose\` \u2014 turn requests into concrete tasks for approval.
|
|
8881
8876
|
3. **Dispatch** \u2192 spawn a sub-agent for each approved task. Pass the taskId.
|
|
8882
8877
|
4. **Communicate** \u2192 \`deskfree_send_message\` for updates outside task threads.
|
|
8883
8878
|
|
|
8884
|
-
**
|
|
8885
|
-
-
|
|
8886
|
-
-
|
|
8887
|
-
-
|
|
8888
|
-
-
|
|
8889
|
-
|
|
8879
|
+
**Before proposing, qualify the request.** Figure out what kind of thing this is:
|
|
8880
|
+
- **One-off task** ("proofread this") \u2192 propose a task directly, no initiative needed.
|
|
8881
|
+
- **New aspiration** ("I want to start posting on LinkedIn") \u2192 don't rush to propose. Ask 1-2 short qualifying questions to understand the real goal. Is this about thought leadership? Lead gen? Personal brand? Then propose an initiative named after the outcome, not the activity.
|
|
8882
|
+
- **Task in existing initiative** \u2192 add it to the right initiative.
|
|
8883
|
+
- Never call \`deskfree_propose\` as your very first action \u2014 qualify first, even if briefly.
|
|
8884
|
+
|
|
8885
|
+
**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.
|
|
8890
8886
|
|
|
8891
8887
|
You do NOT claim tasks or do work directly. Sub-agents handle execution.
|
|
8892
8888
|
- When a human writes in a task thread, you receive it with recent context. Use \`deskfree_reopen_task\` if it needs more work.
|
|
8893
8889
|
- Write task instructions as rich markdown (bold, lists, inline code \u2014 no # headers). Brief a contractor who has never seen the codebase.
|
|
8894
8890
|
- Estimate token cost per task \u2014 consider files to read, reasoning, output.
|
|
8895
|
-
- One initiative per proposal \u2014 make multiple calls for multiple initiatives
|
|
8891
|
+
- One initiative per proposal \u2014 make multiple calls for multiple initiatives.
|
|
8892
|
+
- Initiative titles should reflect aspirations and outcomes, not activities. "AI Thought Leadership on LinkedIn" over "LinkedIn Content."
|
|
8893
|
+
- Initiative descriptions should be 2-3 sentences that make the human think "wow, this will enable my dreams." Reflect their ambition back to them. No headers, no phases, no formal structure. Aspirational, not operational.`;
|
|
8896
8894
|
var DESKFREE_WORKER_DIRECTIVE = `## DeskFree Worker
|
|
8897
8895
|
You are a worker sub-agent. Call \`deskfree_start_task\` with your taskId to claim and load context.
|
|
8898
8896
|
Tools: deskfree_start_task, deskfree_update_file, deskfree_complete_task, deskfree_send_message, deskfree_propose.
|
|
@@ -8900,7 +8898,8 @@ Tools: deskfree_start_task, deskfree_update_file, deskfree_complete_task, deskfr
|
|
|
8900
8898
|
- Save work to linked files with deskfree_update_file (incrementally).
|
|
8901
8899
|
- Complete with deskfree_complete_task when done (summary required for outcome "done"). Include learnings if applicable.
|
|
8902
8900
|
- 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.
|
|
8903
|
-
- Propose follow-up tasks with deskfree_propose if you discover more work
|
|
8901
|
+
- Propose follow-up tasks with deskfree_propose if you discover more work.
|
|
8902
|
+
- 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.`;
|
|
8904
8903
|
function getDeskFreeContext(sessionKey) {
|
|
8905
8904
|
const isWorker = sessionKey && (sessionKey.includes(":sub:") || sessionKey.includes(":spawn:") || sessionKey.includes(":run:"));
|
|
8906
8905
|
const directive = isWorker ? DESKFREE_WORKER_DIRECTIVE : DESKFREE_AGENT_DIRECTIVE;
|
|
@@ -8909,6 +8908,91 @@ function getDeskFreeContext(sessionKey) {
|
|
|
8909
8908
|
<!-- deskfree-plugin:${PLUGIN_VERSION} -->`;
|
|
8910
8909
|
}
|
|
8911
8910
|
|
|
8911
|
+
// src/skill-scan-hook.ts
|
|
8912
|
+
var SCANNABLE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".sh", ".py", ".js", ".ts"]);
|
|
8913
|
+
var SKILLS_DIR_PATTERN = /(?:^|\/|\\)skills\/[^/\\]+\//;
|
|
8914
|
+
function isSkillFile(filePath) {
|
|
8915
|
+
if (!filePath || typeof filePath !== "string") return false;
|
|
8916
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
8917
|
+
if (!SKILLS_DIR_PATTERN.test(normalized)) return false;
|
|
8918
|
+
const lastDot = normalized.lastIndexOf(".");
|
|
8919
|
+
if (lastDot === -1) return false;
|
|
8920
|
+
const ext = normalized.slice(lastDot).toLowerCase();
|
|
8921
|
+
return SCANNABLE_EXTENSIONS.has(ext);
|
|
8922
|
+
}
|
|
8923
|
+
var WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["write", "Write"]);
|
|
8924
|
+
var EDIT_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "Edit"]);
|
|
8925
|
+
function extractPath(params) {
|
|
8926
|
+
const p = params["path"] ?? params["file_path"];
|
|
8927
|
+
return typeof p === "string" && p.length > 0 ? p : null;
|
|
8928
|
+
}
|
|
8929
|
+
function extractContent(toolName, params) {
|
|
8930
|
+
if (WRITE_TOOL_NAMES.has(toolName)) {
|
|
8931
|
+
const c = params["content"];
|
|
8932
|
+
return typeof c === "string" ? c : null;
|
|
8933
|
+
}
|
|
8934
|
+
if (EDIT_TOOL_NAMES.has(toolName)) {
|
|
8935
|
+
const c = params["newText"] ?? params["new_string"];
|
|
8936
|
+
return typeof c === "string" ? c : null;
|
|
8937
|
+
}
|
|
8938
|
+
return null;
|
|
8939
|
+
}
|
|
8940
|
+
function formatFindings(result) {
|
|
8941
|
+
return result.findings.map(
|
|
8942
|
+
(f) => ` [${f.severity.toUpperCase()}] ${f.description}` + (f.line != null ? ` (line ${f.line})` : "")
|
|
8943
|
+
).join("\n");
|
|
8944
|
+
}
|
|
8945
|
+
function createSkillScanHook(scanner, log) {
|
|
8946
|
+
return (event, _ctx) => {
|
|
8947
|
+
const toolName = event["toolName"];
|
|
8948
|
+
if (typeof toolName !== "string") return;
|
|
8949
|
+
if (!WRITE_TOOL_NAMES.has(toolName) && !EDIT_TOOL_NAMES.has(toolName)) {
|
|
8950
|
+
return;
|
|
8951
|
+
}
|
|
8952
|
+
const params = event["params"];
|
|
8953
|
+
if (!params || typeof params !== "object") return;
|
|
8954
|
+
const p = params;
|
|
8955
|
+
const filePath = extractPath(p);
|
|
8956
|
+
if (!filePath) return;
|
|
8957
|
+
if (!isSkillFile(filePath)) return;
|
|
8958
|
+
const content = extractContent(toolName, p);
|
|
8959
|
+
if (!content || content.length === 0) return;
|
|
8960
|
+
const filename = filePath.split("/").pop() ?? "unknown";
|
|
8961
|
+
const inputs = [{ filename, content }];
|
|
8962
|
+
let result;
|
|
8963
|
+
try {
|
|
8964
|
+
result = scanner(inputs);
|
|
8965
|
+
} catch (err) {
|
|
8966
|
+
log.warn(
|
|
8967
|
+
`[skill-scan] Scanner error for ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
8968
|
+
);
|
|
8969
|
+
return;
|
|
8970
|
+
}
|
|
8971
|
+
log.info(
|
|
8972
|
+
`[skill-scan] ${filePath}: verdict=${result.verdict} score=${result.score} findings=${result.findings.length}`
|
|
8973
|
+
);
|
|
8974
|
+
if (result.verdict === "dangerous") {
|
|
8975
|
+
const findings = formatFindings(result);
|
|
8976
|
+
log.warn(`[skill-scan] BLOCKED write to ${filePath}:
|
|
8977
|
+
${findings}`);
|
|
8978
|
+
return {
|
|
8979
|
+
block: true,
|
|
8980
|
+
blockReason: `\u{1F6AB} Security scan BLOCKED write to "${filename}" \u2014 dangerous (score: ${result.score}/100)
|
|
8981
|
+
|
|
8982
|
+
Findings:
|
|
8983
|
+
${findings}
|
|
8984
|
+
|
|
8985
|
+
Remove the dangerous patterns and retry.`
|
|
8986
|
+
};
|
|
8987
|
+
}
|
|
8988
|
+
if (result.verdict === "suspicious") {
|
|
8989
|
+
const findings = formatFindings(result);
|
|
8990
|
+
log.warn(`[skill-scan] Suspicious write to ${filePath}:
|
|
8991
|
+
${findings}`);
|
|
8992
|
+
}
|
|
8993
|
+
};
|
|
8994
|
+
}
|
|
8995
|
+
|
|
8912
8996
|
// src/tools.ts
|
|
8913
8997
|
function resolveAccountFromConfig(api) {
|
|
8914
8998
|
const cfg = api.runtime.config.loadConfig();
|
|
@@ -9400,6 +9484,7 @@ function createWorkerTools(api) {
|
|
|
9400
9484
|
const account = resolveAccountFromConfig(api);
|
|
9401
9485
|
if (!account) return null;
|
|
9402
9486
|
const client = new DeskFreeClient(account.botToken, account.apiUrl);
|
|
9487
|
+
const cachedSkillContext = /* @__PURE__ */ new Map();
|
|
9403
9488
|
return [
|
|
9404
9489
|
{
|
|
9405
9490
|
...WORKER_TOOLS.START_TASK,
|
|
@@ -9410,19 +9495,21 @@ function createWorkerTools(api) {
|
|
|
9410
9495
|
const result = await client.claimTask({ taskId, runnerId });
|
|
9411
9496
|
setActiveTaskId(taskId);
|
|
9412
9497
|
let skillInstructions = "";
|
|
9498
|
+
cachedSkillContext.clear();
|
|
9413
9499
|
if (result.skillContext?.length) {
|
|
9500
|
+
for (const s of result.skillContext) {
|
|
9501
|
+
cachedSkillContext.set(s.skillId, { displayName: s.displayName, instructions: s.instructions });
|
|
9502
|
+
}
|
|
9414
9503
|
skillInstructions = result.skillContext.map(
|
|
9415
9504
|
(s) => `
|
|
9416
|
-
\u26A0\uFE0F SKILL: ${s.displayName}
|
|
9417
|
-
${s.criticalSection}
|
|
9418
|
-
|
|
9419
|
-
${s.instructions}`
|
|
9505
|
+
\u26A0\uFE0F SKILL: ${s.displayName} (ID: ${s.skillId})
|
|
9506
|
+
${s.criticalSection}`
|
|
9420
9507
|
).join("\n\n---\n");
|
|
9421
9508
|
}
|
|
9422
9509
|
const trimmedTask = trimTaskContext(result);
|
|
9423
9510
|
const taskJson = JSON.stringify(
|
|
9424
9511
|
{
|
|
9425
|
-
summary: `Claimed task "${result.title}" \u2014 full context loaded${result.skillContext?.length ? ` (${result.skillContext.length} skill${result.skillContext.length > 1 ? "s" : ""}
|
|
9512
|
+
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)` : ""}`,
|
|
9426
9513
|
mode: trimmedTask.mode ?? "work",
|
|
9427
9514
|
nextActions: [
|
|
9428
9515
|
"Read the instructions and message history carefully",
|
|
@@ -9471,6 +9558,28 @@ ${s.instructions}`
|
|
|
9471
9558
|
{
|
|
9472
9559
|
...WORKER_TOOLS.PROPOSE,
|
|
9473
9560
|
execute: makeProposeHandler(client)
|
|
9561
|
+
},
|
|
9562
|
+
{
|
|
9563
|
+
...WORKER_TOOLS.READ_SKILL,
|
|
9564
|
+
async execute(_id, params) {
|
|
9565
|
+
try {
|
|
9566
|
+
const skillId = validateStringParam(params, "skillId", true);
|
|
9567
|
+
const cached = cachedSkillContext.get(skillId);
|
|
9568
|
+
if (!cached) {
|
|
9569
|
+
return { content: [{ type: "text", text: `Skill ${skillId} not found in current task context. Available: ${[...cachedSkillContext.keys()].join(", ") || "none"}` }] };
|
|
9570
|
+
}
|
|
9571
|
+
return {
|
|
9572
|
+
content: [{
|
|
9573
|
+
type: "text",
|
|
9574
|
+
text: `## ${cached.displayName} \u2014 Full Instructions
|
|
9575
|
+
|
|
9576
|
+
${cached.instructions}`
|
|
9577
|
+
}]
|
|
9578
|
+
};
|
|
9579
|
+
} catch (err) {
|
|
9580
|
+
return errorResult(err);
|
|
9581
|
+
}
|
|
9582
|
+
}
|
|
9474
9583
|
}
|
|
9475
9584
|
];
|
|
9476
9585
|
}
|
|
@@ -9582,6 +9691,35 @@ var OfflineQueue = class {
|
|
|
9582
9691
|
};
|
|
9583
9692
|
|
|
9584
9693
|
// src/index.ts
|
|
9694
|
+
function createLazyScanner(log) {
|
|
9695
|
+
let resolved = false;
|
|
9696
|
+
let scanFn = null;
|
|
9697
|
+
const scannerPath = ["..", "..", "backend", "src", "util", "util.skillScanner"].join("/");
|
|
9698
|
+
import(
|
|
9699
|
+
/* @vite-ignore */
|
|
9700
|
+
scannerPath
|
|
9701
|
+
).then((mod) => {
|
|
9702
|
+
if (typeof mod.scanSkill === "function") {
|
|
9703
|
+
scanFn = mod.scanSkill;
|
|
9704
|
+
log.info("[deskfree] Skill security scanner loaded");
|
|
9705
|
+
}
|
|
9706
|
+
resolved = true;
|
|
9707
|
+
}).catch(() => {
|
|
9708
|
+
resolved = true;
|
|
9709
|
+
log.warn("[deskfree] Skill scanner not available \u2014 scan hook will pass through");
|
|
9710
|
+
});
|
|
9711
|
+
const safeFallback = (inputs) => ({
|
|
9712
|
+
verdict: "safe",
|
|
9713
|
+
score: 100,
|
|
9714
|
+
findings: [],
|
|
9715
|
+
scannedAt: /* @__PURE__ */ new Date(),
|
|
9716
|
+
filesScanned: inputs.map((i) => i.filename)
|
|
9717
|
+
});
|
|
9718
|
+
return (inputs) => {
|
|
9719
|
+
if (!resolved || !scanFn) return safeFallback(inputs);
|
|
9720
|
+
return scanFn(inputs);
|
|
9721
|
+
};
|
|
9722
|
+
}
|
|
9585
9723
|
var plugin = {
|
|
9586
9724
|
id: "deskfree",
|
|
9587
9725
|
name: "DeskFree",
|
|
@@ -9601,6 +9739,8 @@ var plugin = {
|
|
|
9601
9739
|
}
|
|
9602
9740
|
return allTools.length > 0 ? allTools : null;
|
|
9603
9741
|
});
|
|
9742
|
+
const scanner = createLazyScanner(api.logger);
|
|
9743
|
+
api.on("before_tool_call", createSkillScanHook(scanner, api.logger));
|
|
9604
9744
|
api.on("before_agent_start", (_event, ctx) => {
|
|
9605
9745
|
return { prependContext: getDeskFreeContext(ctx.sessionKey) };
|
|
9606
9746
|
});
|