@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/README.md +3 -4
- package/dist/index.d.ts +61 -80
- package/dist/index.js +388 -572
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/deskfree/SKILL.md +238 -77
- package/skills/deskfree/references/tools.md +32 -70
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
|
|
3690
|
+
if (status === 400) {
|
|
3691
3691
|
return new _DeskFreeError(
|
|
3692
|
-
"
|
|
3692
|
+
"client",
|
|
3693
3693
|
procedure,
|
|
3694
|
-
`
|
|
3695
|
-
"
|
|
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
|
|
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
|
-
`
|
|
3713
|
-
"The request was
|
|
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
|
|
3862
|
+
* @param input - Message content, optional userId, taskId, attachments, and suggestions
|
|
3826
3863
|
*/
|
|
3827
3864
|
async sendMessage(input) {
|
|
3828
|
-
|
|
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
|
|
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.
|
|
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 {
|
|
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
|
|
4667
|
+
const result = await pollAndDeliver(
|
|
4713
4668
|
client,
|
|
4714
4669
|
ctx,
|
|
4715
4670
|
getCursor(),
|
|
4716
4671
|
log,
|
|
4717
4672
|
account
|
|
4718
4673
|
);
|
|
4719
|
-
if (
|
|
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
|
|
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
|
|
5180
|
-
if (
|
|
5181
|
-
cursor =
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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({
|
|
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
|
|
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.
|
|
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
|
-
|
|
8019
|
-
name: "
|
|
8020
|
-
description: "
|
|
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
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
if (
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
const
|
|
8275
|
-
if (
|
|
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.
|
|
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
|
|
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
|
-
|
|
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",
|
|
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.
|
|
8810
|
+
...ORCHESTRATOR_TOOLS.CLAIM_EVALUATION,
|
|
9031
8811
|
async execute(_id, params) {
|
|
9032
8812
|
try {
|
|
9033
|
-
const
|
|
9034
|
-
|
|
9035
|
-
|
|
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
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
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
|
-
`
|
|
9056
|
-
[
|
|
9057
|
-
|
|
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);
|