@questionbase/deskfree 0.3.0-alpha.35 → 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 +154 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/deskfree/SKILL.md +115 -149
package/dist/index.js
CHANGED
|
@@ -8300,7 +8300,14 @@ var WORKER_TOOLS = {
|
|
|
8300
8300
|
UPDATE_FILE: SHARED_TOOLS.UPDATE_FILE,
|
|
8301
8301
|
COMPLETE_TASK: SHARED_TOOLS.COMPLETE_TASK,
|
|
8302
8302
|
SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
|
|
8303
|
-
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
|
+
}
|
|
8304
8311
|
};
|
|
8305
8312
|
var CHANNEL_META = {
|
|
8306
8313
|
name: "DeskFree",
|
|
@@ -8901,6 +8908,91 @@ function getDeskFreeContext(sessionKey) {
|
|
|
8901
8908
|
<!-- deskfree-plugin:${PLUGIN_VERSION} -->`;
|
|
8902
8909
|
}
|
|
8903
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
|
+
|
|
8904
8996
|
// src/tools.ts
|
|
8905
8997
|
function resolveAccountFromConfig(api) {
|
|
8906
8998
|
const cfg = api.runtime.config.loadConfig();
|
|
@@ -9392,6 +9484,7 @@ function createWorkerTools(api) {
|
|
|
9392
9484
|
const account = resolveAccountFromConfig(api);
|
|
9393
9485
|
if (!account) return null;
|
|
9394
9486
|
const client = new DeskFreeClient(account.botToken, account.apiUrl);
|
|
9487
|
+
const cachedSkillContext = /* @__PURE__ */ new Map();
|
|
9395
9488
|
return [
|
|
9396
9489
|
{
|
|
9397
9490
|
...WORKER_TOOLS.START_TASK,
|
|
@@ -9402,19 +9495,21 @@ function createWorkerTools(api) {
|
|
|
9402
9495
|
const result = await client.claimTask({ taskId, runnerId });
|
|
9403
9496
|
setActiveTaskId(taskId);
|
|
9404
9497
|
let skillInstructions = "";
|
|
9498
|
+
cachedSkillContext.clear();
|
|
9405
9499
|
if (result.skillContext?.length) {
|
|
9500
|
+
for (const s of result.skillContext) {
|
|
9501
|
+
cachedSkillContext.set(s.skillId, { displayName: s.displayName, instructions: s.instructions });
|
|
9502
|
+
}
|
|
9406
9503
|
skillInstructions = result.skillContext.map(
|
|
9407
9504
|
(s) => `
|
|
9408
|
-
\u26A0\uFE0F SKILL: ${s.displayName}
|
|
9409
|
-
${s.criticalSection}
|
|
9410
|
-
|
|
9411
|
-
${s.instructions}`
|
|
9505
|
+
\u26A0\uFE0F SKILL: ${s.displayName} (ID: ${s.skillId})
|
|
9506
|
+
${s.criticalSection}`
|
|
9412
9507
|
).join("\n\n---\n");
|
|
9413
9508
|
}
|
|
9414
9509
|
const trimmedTask = trimTaskContext(result);
|
|
9415
9510
|
const taskJson = JSON.stringify(
|
|
9416
9511
|
{
|
|
9417
|
-
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)` : ""}`,
|
|
9418
9513
|
mode: trimmedTask.mode ?? "work",
|
|
9419
9514
|
nextActions: [
|
|
9420
9515
|
"Read the instructions and message history carefully",
|
|
@@ -9463,6 +9558,28 @@ ${s.instructions}`
|
|
|
9463
9558
|
{
|
|
9464
9559
|
...WORKER_TOOLS.PROPOSE,
|
|
9465
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
|
+
}
|
|
9466
9583
|
}
|
|
9467
9584
|
];
|
|
9468
9585
|
}
|
|
@@ -9574,6 +9691,35 @@ var OfflineQueue = class {
|
|
|
9574
9691
|
};
|
|
9575
9692
|
|
|
9576
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
|
+
}
|
|
9577
9723
|
var plugin = {
|
|
9578
9724
|
id: "deskfree",
|
|
9579
9725
|
name: "DeskFree",
|
|
@@ -9593,6 +9739,8 @@ var plugin = {
|
|
|
9593
9739
|
}
|
|
9594
9740
|
return allTools.length > 0 ? allTools : null;
|
|
9595
9741
|
});
|
|
9742
|
+
const scanner = createLazyScanner(api.logger);
|
|
9743
|
+
api.on("before_tool_call", createSkillScanHook(scanner, api.logger));
|
|
9596
9744
|
api.on("before_agent_start", (_event, ctx) => {
|
|
9597
9745
|
return { prependContext: getDeskFreeContext(ctx.sessionKey) };
|
|
9598
9746
|
});
|