@questionbase/deskfree 0.3.0-alpha.21 → 0.3.0-alpha.23
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 +36 -16
- package/dist/index.d.ts +107 -38
- package/dist/index.js +646 -597
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/deskfree/SKILL.md +276 -396
- package/skills/deskfree/references/tools.md +43 -40
package/dist/index.js
CHANGED
|
@@ -3857,19 +3857,19 @@ var DeskFreeClient = class {
|
|
|
3857
3857
|
return this.request("POST", "messages.update", input);
|
|
3858
3858
|
}
|
|
3859
3859
|
/**
|
|
3860
|
-
* Send a
|
|
3860
|
+
* Send a streaming chunk (delta only) for an in-progress message.
|
|
3861
|
+
* Broadcasts delta over WS and stores full content in DynamoDB for crash recovery.
|
|
3862
|
+
*/
|
|
3863
|
+
async streamChunk(input) {
|
|
3864
|
+
return this.request("POST", "messages.streamChunk", input);
|
|
3865
|
+
}
|
|
3866
|
+
/**
|
|
3867
|
+
* Send a text message to a DeskFree conversation.
|
|
3861
3868
|
*
|
|
3862
|
-
* @param input - Message content, optional userId, taskId, attachments
|
|
3869
|
+
* @param input - Message content, optional userId, taskId, attachments
|
|
3863
3870
|
*/
|
|
3864
3871
|
async sendMessage(input) {
|
|
3865
|
-
|
|
3866
|
-
throw new DeskFreeError(
|
|
3867
|
-
"client",
|
|
3868
|
-
"content",
|
|
3869
|
-
"content or suggestions is required",
|
|
3870
|
-
"Missing required parameter: provide content or suggestions."
|
|
3871
|
-
);
|
|
3872
|
-
}
|
|
3872
|
+
this.requireNonEmpty(input.content, "content");
|
|
3873
3873
|
return this.request("POST", "messages.send", input);
|
|
3874
3874
|
}
|
|
3875
3875
|
/** Fetch paginated message history for a conversation. */
|
|
@@ -3894,11 +3894,25 @@ var DeskFreeClient = class {
|
|
|
3894
3894
|
this.requireNonEmpty(input.taskId, "taskId");
|
|
3895
3895
|
return this.request("POST", "tasks.claim", input);
|
|
3896
3896
|
}
|
|
3897
|
-
/**
|
|
3898
|
-
async
|
|
3897
|
+
/** Fetch a lightweight task summary by ID. Read-only, no side effects. */
|
|
3898
|
+
async getTask(input) {
|
|
3899
3899
|
this.requireNonEmpty(input.taskId, "taskId");
|
|
3900
|
-
this.
|
|
3901
|
-
|
|
3900
|
+
return this.request("GET", "tasks.get", input);
|
|
3901
|
+
}
|
|
3902
|
+
/** Update the content of an existing file. */
|
|
3903
|
+
async updateFile(input) {
|
|
3904
|
+
this.requireNonEmpty(input.fileId, "fileId");
|
|
3905
|
+
this.requireNonEmpty(input.content, "content");
|
|
3906
|
+
return this.request("POST", "files.update", input);
|
|
3907
|
+
}
|
|
3908
|
+
/** Create a new persistent file. */
|
|
3909
|
+
async createFile(input) {
|
|
3910
|
+
this.requireNonEmpty(input.name, "name");
|
|
3911
|
+
return this.request("POST", "files.create", input);
|
|
3912
|
+
}
|
|
3913
|
+
/** List all files for this bot (metadata only, no content). */
|
|
3914
|
+
async listFiles() {
|
|
3915
|
+
return this.request("GET", "files.list", {});
|
|
3902
3916
|
}
|
|
3903
3917
|
/** Send an agent status update to DeskFree. */
|
|
3904
3918
|
async statusUpdate(input) {
|
|
@@ -3923,32 +3937,22 @@ var DeskFreeClient = class {
|
|
|
3923
3937
|
this.requireNonEmpty(input.taskId, "taskId");
|
|
3924
3938
|
return this.request("POST", "tasks.complete", input);
|
|
3925
3939
|
}
|
|
3926
|
-
/**
|
|
3927
|
-
async
|
|
3940
|
+
/** Reopen a completed/human task back to bot status for further work. */
|
|
3941
|
+
async reopenTask(input) {
|
|
3942
|
+
this.requireNonEmpty(input.taskId, "taskId");
|
|
3943
|
+
return this.request("POST", "tasks.reopen", input);
|
|
3944
|
+
}
|
|
3945
|
+
/** Propose a plan — creates a proposal message with plan metadata. No DB rows until human approves. */
|
|
3946
|
+
async proposePlan(input) {
|
|
3928
3947
|
if (!input.tasks || input.tasks.length === 0) {
|
|
3929
3948
|
throw new DeskFreeError(
|
|
3930
3949
|
"client",
|
|
3931
3950
|
"tasks",
|
|
3932
3951
|
"tasks array is required and cannot be empty",
|
|
3933
|
-
"Missing required parameter: tasks.
|
|
3952
|
+
"Missing required parameter: tasks."
|
|
3934
3953
|
);
|
|
3935
3954
|
}
|
|
3936
|
-
return this.
|
|
3937
|
-
suggestions: input.tasks,
|
|
3938
|
-
taskId: input.taskId
|
|
3939
|
-
});
|
|
3940
|
-
}
|
|
3941
|
-
/** Suggest tasks via the dedicated bot/tasks.suggest endpoint. */
|
|
3942
|
-
async suggestTasksDedicated(input) {
|
|
3943
|
-
if (!input.suggestions || input.suggestions.length === 0) {
|
|
3944
|
-
throw new DeskFreeError(
|
|
3945
|
-
"client",
|
|
3946
|
-
"suggestions",
|
|
3947
|
-
"suggestions array is required and cannot be empty",
|
|
3948
|
-
"Missing required parameter: suggestions."
|
|
3949
|
-
);
|
|
3950
|
-
}
|
|
3951
|
-
return this.request("POST", "tasks.suggest", input);
|
|
3955
|
+
return this.request("POST", "tasks.propose", input);
|
|
3952
3956
|
}
|
|
3953
3957
|
/**
|
|
3954
3958
|
* Claim a pending evaluation for a task. Atomically sets isWorking=true where
|
|
@@ -4121,6 +4125,8 @@ function resolvePluginStorePath(subpath) {
|
|
|
4121
4125
|
|
|
4122
4126
|
// src/streaming.ts
|
|
4123
4127
|
var THROTTLE_MS = 300;
|
|
4128
|
+
var CHAR_BUFFER_SIZE = 256;
|
|
4129
|
+
var CLOSE_MAX_RETRIES = 3;
|
|
4124
4130
|
var DeskFreeStreamingSession = class {
|
|
4125
4131
|
client;
|
|
4126
4132
|
log;
|
|
@@ -4128,9 +4134,11 @@ var DeskFreeStreamingSession = class {
|
|
|
4128
4134
|
currentText = "";
|
|
4129
4135
|
closed = false;
|
|
4130
4136
|
lastUpdateTime = 0;
|
|
4131
|
-
|
|
4137
|
+
pendingDelta = "";
|
|
4132
4138
|
pendingTimer = null;
|
|
4133
4139
|
queue = Promise.resolve();
|
|
4140
|
+
/** Full accumulated text (currentText + pendingDelta) */
|
|
4141
|
+
fullText = "";
|
|
4134
4142
|
constructor(client, log) {
|
|
4135
4143
|
this.client = client;
|
|
4136
4144
|
this.log = log;
|
|
@@ -4146,51 +4154,72 @@ var DeskFreeStreamingSession = class {
|
|
|
4146
4154
|
const result = await this.client.sendMessage({ content, taskId });
|
|
4147
4155
|
this.messageId = result.messageId;
|
|
4148
4156
|
this.currentText = content;
|
|
4157
|
+
this.fullText = content;
|
|
4149
4158
|
this.lastUpdateTime = Date.now();
|
|
4150
4159
|
this.log.info(`Streaming started: messageId=${this.messageId}`);
|
|
4151
4160
|
return this.messageId;
|
|
4152
4161
|
}
|
|
4153
4162
|
/**
|
|
4154
|
-
*
|
|
4155
|
-
* Content should be the FULL message text
|
|
4163
|
+
* Push new text (appended to the stream). Buffers and flushes as delta.
|
|
4164
|
+
* Content should be the FULL accumulated message text.
|
|
4156
4165
|
*/
|
|
4157
4166
|
async update(fullText) {
|
|
4158
4167
|
if (!this.messageId || this.closed) return;
|
|
4159
|
-
if (fullText === this.
|
|
4168
|
+
if (fullText === this.fullText) return;
|
|
4169
|
+
const delta = fullText.slice(this.fullText.length);
|
|
4170
|
+
if (!delta) return;
|
|
4171
|
+
this.fullText = fullText;
|
|
4172
|
+
this.pendingDelta += delta;
|
|
4160
4173
|
const now = Date.now();
|
|
4161
4174
|
const elapsed = now - this.lastUpdateTime;
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
this.
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
this.pendingText = null;
|
|
4172
|
-
await this.sendUpdate(text, true);
|
|
4173
|
-
}
|
|
4174
|
-
}, THROTTLE_MS - elapsed);
|
|
4175
|
-
}
|
|
4175
|
+
const bufferFull = this.pendingDelta.length >= CHAR_BUFFER_SIZE;
|
|
4176
|
+
const throttleExpired = elapsed >= THROTTLE_MS;
|
|
4177
|
+
if (bufferFull || throttleExpired) {
|
|
4178
|
+
this.flush();
|
|
4179
|
+
} else if (!this.pendingTimer) {
|
|
4180
|
+
this.pendingTimer = setTimeout(() => {
|
|
4181
|
+
this.pendingTimer = null;
|
|
4182
|
+
this.flush();
|
|
4183
|
+
}, THROTTLE_MS - elapsed);
|
|
4176
4184
|
}
|
|
4177
4185
|
}
|
|
4178
4186
|
/**
|
|
4179
4187
|
* Finalize the streaming session with the final content.
|
|
4188
|
+
* Retries up to 3 times with exponential backoff.
|
|
4180
4189
|
*/
|
|
4181
4190
|
async close(finalText) {
|
|
4182
4191
|
if (this.closed) return;
|
|
4183
|
-
this.closed = true;
|
|
4184
4192
|
if (this.pendingTimer) {
|
|
4185
4193
|
clearTimeout(this.pendingTimer);
|
|
4186
4194
|
this.pendingTimer = null;
|
|
4187
4195
|
}
|
|
4196
|
+
this.flush();
|
|
4197
|
+
this.closed = true;
|
|
4188
4198
|
await this.queue;
|
|
4189
|
-
const text = finalText ?? this.
|
|
4190
|
-
if (this.messageId
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4199
|
+
const text = finalText ?? (this.fullText || this.currentText);
|
|
4200
|
+
if (this.messageId) {
|
|
4201
|
+
for (let attempt = 0; attempt < CLOSE_MAX_RETRIES; attempt++) {
|
|
4202
|
+
try {
|
|
4203
|
+
await this.client.updateMessage({
|
|
4204
|
+
messageId: this.messageId,
|
|
4205
|
+
content: text,
|
|
4206
|
+
streaming: false
|
|
4207
|
+
});
|
|
4208
|
+
break;
|
|
4209
|
+
} catch (err) {
|
|
4210
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4211
|
+
if (attempt < CLOSE_MAX_RETRIES - 1) {
|
|
4212
|
+
this.log.warn(
|
|
4213
|
+
`Streaming close attempt ${attempt + 1} failed: ${msg}, retrying...`
|
|
4214
|
+
);
|
|
4215
|
+
await new Promise((r) => setTimeout(r, 1e3 * (attempt + 1)));
|
|
4216
|
+
} else {
|
|
4217
|
+
this.log.error(
|
|
4218
|
+
`Streaming close failed after ${CLOSE_MAX_RETRIES} attempts: ${msg}. Recovery worker will finalize.`
|
|
4219
|
+
);
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4194
4223
|
}
|
|
4195
4224
|
this.log.info(`Streaming closed: messageId=${this.messageId}`);
|
|
4196
4225
|
}
|
|
@@ -4200,23 +4229,32 @@ var DeskFreeStreamingSession = class {
|
|
|
4200
4229
|
getMessageId() {
|
|
4201
4230
|
return this.messageId;
|
|
4202
4231
|
}
|
|
4203
|
-
|
|
4204
|
-
|
|
4232
|
+
/**
|
|
4233
|
+
* Flush the pending delta buffer via streamChunk.
|
|
4234
|
+
*/
|
|
4235
|
+
flush() {
|
|
4236
|
+
if (this.pendingTimer) {
|
|
4237
|
+
clearTimeout(this.pendingTimer);
|
|
4238
|
+
this.pendingTimer = null;
|
|
4239
|
+
}
|
|
4240
|
+
const delta = this.pendingDelta;
|
|
4241
|
+
if (!delta || !this.messageId || this.closed) return;
|
|
4242
|
+
this.pendingDelta = "";
|
|
4243
|
+
const fullContent = this.fullText;
|
|
4205
4244
|
this.queue = this.queue.then(async () => {
|
|
4206
4245
|
try {
|
|
4207
|
-
await this.client.
|
|
4246
|
+
await this.client.streamChunk({
|
|
4208
4247
|
messageId: this.messageId,
|
|
4209
|
-
|
|
4210
|
-
|
|
4248
|
+
delta,
|
|
4249
|
+
fullContent
|
|
4211
4250
|
});
|
|
4212
|
-
this.currentText =
|
|
4251
|
+
this.currentText = fullContent;
|
|
4213
4252
|
this.lastUpdateTime = Date.now();
|
|
4214
4253
|
} catch (err) {
|
|
4215
4254
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4216
|
-
this.log.warn(`Streaming
|
|
4255
|
+
this.log.warn(`Streaming delta failed: ${msg}`);
|
|
4217
4256
|
}
|
|
4218
4257
|
});
|
|
4219
|
-
await this.queue;
|
|
4220
4258
|
}
|
|
4221
4259
|
};
|
|
4222
4260
|
|
|
@@ -4429,6 +4467,38 @@ async function fetchAndSaveMedia(attachment) {
|
|
|
4429
4467
|
}
|
|
4430
4468
|
}
|
|
4431
4469
|
}
|
|
4470
|
+
var MAX_INSTRUCTIONS_LENGTH = 500;
|
|
4471
|
+
var MAX_CONTEXT_MESSAGES = 5;
|
|
4472
|
+
function truncateAtWord(text, maxLen) {
|
|
4473
|
+
if (text.length <= maxLen) return text;
|
|
4474
|
+
const truncated = text.slice(0, maxLen);
|
|
4475
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
4476
|
+
return (lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated) + "\u2026";
|
|
4477
|
+
}
|
|
4478
|
+
function buildBodyForAgent(task, content, recentMessages) {
|
|
4479
|
+
let prefix = `[Task thread: "${task.title}" | status: ${task.status} | isWorking: ${task.isWorking}]`;
|
|
4480
|
+
if (task.instructions) {
|
|
4481
|
+
prefix += "\n" + truncateAtWord(task.instructions, MAX_INSTRUCTIONS_LENGTH);
|
|
4482
|
+
}
|
|
4483
|
+
if (recentMessages && recentMessages.length > 0) {
|
|
4484
|
+
prefix += "\n\nRecent thread messages:";
|
|
4485
|
+
for (const msg of recentMessages) {
|
|
4486
|
+
const author = msg.authorType === "bot" ? "\u{1F916} Bot" : msg.userName ?? "Human";
|
|
4487
|
+
prefix += `
|
|
4488
|
+
- ${author}: ${truncateAtWord(msg.content, 200)}`;
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
return prefix + "\n\n" + content;
|
|
4492
|
+
}
|
|
4493
|
+
function resolveTaskRouting(task, hasAttachments) {
|
|
4494
|
+
if (task.isWorking) {
|
|
4495
|
+
return { target: "runner" };
|
|
4496
|
+
}
|
|
4497
|
+
if (hasAttachments && (task.status === "human" || task.status === "done")) {
|
|
4498
|
+
return { target: "auto-reopen" };
|
|
4499
|
+
}
|
|
4500
|
+
return { target: "orchestrator" };
|
|
4501
|
+
}
|
|
4432
4502
|
async function deliverMessageToAgent(ctx, message, client) {
|
|
4433
4503
|
const runtime = getDeskFreeRuntime();
|
|
4434
4504
|
const log = ctx.log ?? runtime.logging.createLogger("deskfree:deliver");
|
|
@@ -4471,9 +4541,55 @@ async function deliverMessageToAgent(ctx, message, client) {
|
|
|
4471
4541
|
);
|
|
4472
4542
|
}
|
|
4473
4543
|
}
|
|
4544
|
+
let bodyForAgent;
|
|
4545
|
+
let routingTarget = "orchestrator";
|
|
4546
|
+
if (message.taskId) {
|
|
4547
|
+
try {
|
|
4548
|
+
const task = await client.getTask({ taskId: message.taskId });
|
|
4549
|
+
const routing = resolveTaskRouting(task, mediaPaths.length > 0);
|
|
4550
|
+
routingTarget = routing.target;
|
|
4551
|
+
if (routingTarget === "auto-reopen") {
|
|
4552
|
+
log.info(
|
|
4553
|
+
`Auto-reopening task ${message.taskId} (attachment on ${task.status} task)`
|
|
4554
|
+
);
|
|
4555
|
+
try {
|
|
4556
|
+
await client.reopenTask({
|
|
4557
|
+
taskId: message.taskId,
|
|
4558
|
+
reason: "Human sent an attachment \u2014 reopening for further work"
|
|
4559
|
+
});
|
|
4560
|
+
} catch (reopenErr) {
|
|
4561
|
+
const reopenMsg = reopenErr instanceof Error ? reopenErr.message : String(reopenErr);
|
|
4562
|
+
log.warn(
|
|
4563
|
+
`Failed to auto-reopen task ${message.taskId}: ${reopenMsg}`
|
|
4564
|
+
);
|
|
4565
|
+
routingTarget = "orchestrator";
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
let recentMessages;
|
|
4569
|
+
try {
|
|
4570
|
+
const msgList = await client.listMessages({
|
|
4571
|
+
taskId: message.taskId,
|
|
4572
|
+
limit: MAX_CONTEXT_MESSAGES
|
|
4573
|
+
});
|
|
4574
|
+
recentMessages = msgList.items.map((m) => ({
|
|
4575
|
+
authorType: m.humanId ? "user" : "bot",
|
|
4576
|
+
content: m.content,
|
|
4577
|
+
userName: m.userName
|
|
4578
|
+
}));
|
|
4579
|
+
} catch {
|
|
4580
|
+
}
|
|
4581
|
+
bodyForAgent = buildBodyForAgent(task, message.content, recentMessages);
|
|
4582
|
+
} catch (err) {
|
|
4583
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4584
|
+
log.warn(`Failed to fetch task context for ${message.taskId}: ${errMsg}`);
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
const sessionKey = routingTarget === "runner" && message.taskId ? `agent:main:deskfree:task:${message.taskId}` : `agent:main:deskfree:dm:${peerId}`;
|
|
4588
|
+
log.info(`Routing message to ${routingTarget} (session: ${sessionKey})`);
|
|
4474
4589
|
const msgCtx = runtime.channel.reply.finalizeInboundContext({
|
|
4475
4590
|
Body: message.content,
|
|
4476
4591
|
RawBody: message.content,
|
|
4592
|
+
...bodyForAgent ? { BodyForAgent: bodyForAgent } : {},
|
|
4477
4593
|
ChatType: "dm",
|
|
4478
4594
|
Provider: "deskfree",
|
|
4479
4595
|
Surface: "deskfree",
|
|
@@ -4485,7 +4601,7 @@ async function deliverMessageToAgent(ctx, message, client) {
|
|
|
4485
4601
|
Timestamp: message.createdAt,
|
|
4486
4602
|
AccountId: ctx.accountId,
|
|
4487
4603
|
FromName: message.userName ?? "Unknown",
|
|
4488
|
-
SessionKey:
|
|
4604
|
+
SessionKey: sessionKey,
|
|
4489
4605
|
// Backward-compatible: first attachment
|
|
4490
4606
|
MediaPath: mediaPaths[0],
|
|
4491
4607
|
MediaType: mediaTypes[0],
|
|
@@ -4496,10 +4612,10 @@ async function deliverMessageToAgent(ctx, message, client) {
|
|
|
4496
4612
|
MediaUrls: mediaUrls,
|
|
4497
4613
|
MediaCount: mediaPaths.length
|
|
4498
4614
|
});
|
|
4615
|
+
let streamingSession = null;
|
|
4616
|
+
let accumulatedText = "";
|
|
4499
4617
|
try {
|
|
4500
4618
|
const cfg = runtime.config.loadConfig();
|
|
4501
|
-
let streamingSession = null;
|
|
4502
|
-
let accumulatedText = "";
|
|
4503
4619
|
const { dispatcher, replyOptions } = runtime.channel.reply.createReplyDispatcherWithTyping({
|
|
4504
4620
|
channel: "deskfree",
|
|
4505
4621
|
accountId: ctx.accountId,
|
|
@@ -4553,6 +4669,14 @@ async function deliverMessageToAgent(ctx, message, client) {
|
|
|
4553
4669
|
});
|
|
4554
4670
|
log.info(`Message ${message.messageId} dispatched successfully.`);
|
|
4555
4671
|
} catch (err) {
|
|
4672
|
+
const session = streamingSession;
|
|
4673
|
+
if (session?.isActive()) {
|
|
4674
|
+
try {
|
|
4675
|
+
await session.close(accumulatedText || void 0);
|
|
4676
|
+
} catch {
|
|
4677
|
+
}
|
|
4678
|
+
streamingSession = null;
|
|
4679
|
+
}
|
|
4556
4680
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4557
4681
|
log.warn(`Failed to dispatch message ${message.messageId}: ${errMsg}`);
|
|
4558
4682
|
reportError("error", `Failed to dispatch message: ${errMsg}`, {
|
|
@@ -4585,22 +4709,21 @@ var wrapper_default = import_websocket.default;
|
|
|
4585
4709
|
// src/gateway.ts
|
|
4586
4710
|
var activeTaskId = null;
|
|
4587
4711
|
var completedTaskId = null;
|
|
4588
|
-
|
|
4589
|
-
if (taskId === null && activeTaskId !== null) {
|
|
4590
|
-
completedTaskId = activeTaskId;
|
|
4591
|
-
}
|
|
4592
|
-
activeTaskId = taskId;
|
|
4593
|
-
}
|
|
4712
|
+
var inboundThreadId = null;
|
|
4594
4713
|
function getActiveTaskId() {
|
|
4595
|
-
return activeTaskId ?? completedTaskId;
|
|
4714
|
+
return activeTaskId ?? completedTaskId ?? inboundThreadId;
|
|
4596
4715
|
}
|
|
4597
4716
|
function clearCompletedTaskId() {
|
|
4598
4717
|
completedTaskId = null;
|
|
4599
4718
|
}
|
|
4719
|
+
function setInboundThreadId(taskId) {
|
|
4720
|
+
inboundThreadId = taskId;
|
|
4721
|
+
}
|
|
4600
4722
|
var PING_INTERVAL_MS = 5 * 60 * 1e3;
|
|
4601
4723
|
var POLL_FALLBACK_INTERVAL_MS = 30 * 1e3;
|
|
4602
4724
|
var WS_CONNECTION_TIMEOUT_MS = 30 * 1e3;
|
|
4603
4725
|
var WS_PONG_TIMEOUT_MS = 10 * 1e3;
|
|
4726
|
+
var NOTIFY_DEBOUNCE_MS = 200;
|
|
4604
4727
|
var BACKOFF_INITIAL_MS = 2e3;
|
|
4605
4728
|
var BACKOFF_MAX_MS = 3e4;
|
|
4606
4729
|
var BACKOFF_FACTOR = 1.8;
|
|
@@ -4824,6 +4947,7 @@ async function runWebSocketConnection(opts) {
|
|
|
4824
4947
|
let pingInterval;
|
|
4825
4948
|
let connectionTimer;
|
|
4826
4949
|
let pongTimer;
|
|
4950
|
+
let notifyDebounceTimer;
|
|
4827
4951
|
let isConnected = false;
|
|
4828
4952
|
const cleanup = () => {
|
|
4829
4953
|
if (pingInterval !== void 0) {
|
|
@@ -4838,6 +4962,10 @@ async function runWebSocketConnection(opts) {
|
|
|
4838
4962
|
clearTimeout(pongTimer);
|
|
4839
4963
|
pongTimer = void 0;
|
|
4840
4964
|
}
|
|
4965
|
+
if (notifyDebounceTimer !== void 0) {
|
|
4966
|
+
clearTimeout(notifyDebounceTimer);
|
|
4967
|
+
notifyDebounceTimer = void 0;
|
|
4968
|
+
}
|
|
4841
4969
|
};
|
|
4842
4970
|
connectionTimer = setTimeout(() => {
|
|
4843
4971
|
if (!isConnected) {
|
|
@@ -4918,15 +5046,21 @@ async function runWebSocketConnection(opts) {
|
|
|
4918
5046
|
return;
|
|
4919
5047
|
}
|
|
4920
5048
|
if (msg.action === "notify") {
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
5049
|
+
if (notifyDebounceTimer !== void 0) {
|
|
5050
|
+
clearTimeout(notifyDebounceTimer);
|
|
5051
|
+
}
|
|
5052
|
+
notifyDebounceTimer = setTimeout(() => {
|
|
5053
|
+
notifyDebounceTimer = void 0;
|
|
5054
|
+
enqueuePoll(
|
|
5055
|
+
client,
|
|
5056
|
+
ctx,
|
|
5057
|
+
() => cursor,
|
|
5058
|
+
(c) => {
|
|
5059
|
+
cursor = c ?? cursor;
|
|
5060
|
+
},
|
|
5061
|
+
log
|
|
5062
|
+
);
|
|
5063
|
+
}, NOTIFY_DEBOUNCE_MS);
|
|
4930
5064
|
} else if (msg.action === "pong") {
|
|
4931
5065
|
if (pongTimer !== void 0) {
|
|
4932
5066
|
clearTimeout(pongTimer);
|
|
@@ -5123,6 +5257,7 @@ async function pollAndDeliver(client, ctx, cursor, log, account) {
|
|
|
5123
5257
|
continue;
|
|
5124
5258
|
}
|
|
5125
5259
|
clearCompletedTaskId();
|
|
5260
|
+
setInboundThreadId(message.taskId ?? null);
|
|
5126
5261
|
await deliverMessageToAgent(ctx, message, client);
|
|
5127
5262
|
deliveredMessageIds.add(message.messageId);
|
|
5128
5263
|
deliveredCount++;
|
|
@@ -7765,76 +7900,67 @@ var ORCHESTRATOR_TOOLS = {
|
|
|
7765
7900
|
description: "Get full workspace state \u2014 all tasks, recently done tasks, active initiatives. Use to assess what needs attention.",
|
|
7766
7901
|
parameters: Type.Object({})
|
|
7767
7902
|
},
|
|
7768
|
-
|
|
7769
|
-
name: "
|
|
7770
|
-
description: "
|
|
7903
|
+
REOPEN_TASK: {
|
|
7904
|
+
name: "deskfree_reopen_task",
|
|
7905
|
+
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.",
|
|
7771
7906
|
parameters: Type.Object({
|
|
7772
|
-
taskId: Type.String({ description: "Task UUID to
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
name: "deskfree_update_deliverable",
|
|
7777
|
-
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.',
|
|
7778
|
-
parameters: Type.Object({
|
|
7779
|
-
taskId: Type.String({ description: "Task UUID" }),
|
|
7780
|
-
deliverable: Type.String({
|
|
7781
|
-
description: "Deliverable content (markdown or HTML depending on format)"
|
|
7782
|
-
}),
|
|
7783
|
-
format: Type.Optional(
|
|
7784
|
-
Type.Union([Type.Literal("markdown"), Type.Literal("html")], {
|
|
7785
|
-
description: '"markdown" (default) for text/documents, "html" for rich web content. HTML is rendered in a sandboxed iframe.'
|
|
7907
|
+
taskId: Type.String({ description: "Task UUID to reopen" }),
|
|
7908
|
+
reason: Type.Optional(
|
|
7909
|
+
Type.String({
|
|
7910
|
+
description: "Why this task is being reopened (shown in task thread as system message)"
|
|
7786
7911
|
})
|
|
7787
7912
|
)
|
|
7788
7913
|
})
|
|
7789
7914
|
},
|
|
7790
|
-
COMPLETE_TASK: {
|
|
7791
|
-
name: "deskfree_complete_task",
|
|
7792
|
-
description: 'Finish a task. Outcome "done" = work complete for review. Outcome "blocked" = need human input. Both move to human.',
|
|
7793
|
-
parameters: Type.Object({
|
|
7794
|
-
taskId: Type.String({ description: "Task UUID" }),
|
|
7795
|
-
outcome: Type.Union([Type.Literal("done"), Type.Literal("blocked")], {
|
|
7796
|
-
description: '"done" = work complete, "blocked" = need human input'
|
|
7797
|
-
})
|
|
7798
|
-
})
|
|
7799
|
-
},
|
|
7800
7915
|
SEND_MESSAGE: {
|
|
7801
7916
|
name: "deskfree_send_message",
|
|
7802
|
-
description: "Send a message
|
|
7917
|
+
description: "Send a message (progress update, question, status report). Used for communication outside of task threads.",
|
|
7803
7918
|
parameters: Type.Object({
|
|
7804
|
-
content: Type.
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
})
|
|
7808
|
-
),
|
|
7919
|
+
content: Type.String({
|
|
7920
|
+
description: "Message content."
|
|
7921
|
+
}),
|
|
7809
7922
|
taskId: Type.Optional(
|
|
7810
7923
|
Type.String({
|
|
7811
|
-
description: "Task UUID (optional
|
|
7924
|
+
description: "Task UUID (optional \u2014 threads into task if provided)"
|
|
7812
7925
|
})
|
|
7813
|
-
),
|
|
7814
|
-
suggestions: Type.Optional(
|
|
7815
|
-
Type.Array(
|
|
7816
|
-
Type.Object({
|
|
7817
|
-
title: Type.String({ description: "Task title (max 200 chars)" }),
|
|
7818
|
-
instructions: Type.Optional(
|
|
7819
|
-
Type.String({
|
|
7820
|
-
description: "Detailed instructions for the suggested task"
|
|
7821
|
-
})
|
|
7822
|
-
)
|
|
7823
|
-
}),
|
|
7824
|
-
{
|
|
7825
|
-
description: "Suggest tasks for human review (1-10). The human will see approve/reject buttons for each. Provide this instead of content.",
|
|
7826
|
-
minItems: 1,
|
|
7827
|
-
maxItems: 10
|
|
7828
|
-
}
|
|
7829
|
-
)
|
|
7830
7926
|
)
|
|
7831
7927
|
})
|
|
7832
7928
|
},
|
|
7833
|
-
|
|
7834
|
-
name: "
|
|
7835
|
-
description:
|
|
7929
|
+
PROPOSE: {
|
|
7930
|
+
name: "deskfree_propose",
|
|
7931
|
+
description: "Propose a plan for human approval. Nothing is created until the human reviews and approves in a modal. One initiative per call \u2014 make multiple calls for multiple initiatives. Include substeps as human-reviewable checklist items.",
|
|
7836
7932
|
parameters: Type.Object({
|
|
7837
|
-
|
|
7933
|
+
initiative: Type.Optional(
|
|
7934
|
+
Type.Union(
|
|
7935
|
+
[
|
|
7936
|
+
Type.String({
|
|
7937
|
+
description: 'Existing initiative ID (e.g. "INI_123") to group tasks under'
|
|
7938
|
+
}),
|
|
7939
|
+
Type.Object(
|
|
7940
|
+
{
|
|
7941
|
+
title: Type.String({
|
|
7942
|
+
description: "New initiative title (max 200 chars)"
|
|
7943
|
+
}),
|
|
7944
|
+
content: Type.String({
|
|
7945
|
+
description: "Initiative content markdown \u2014 current state, approach, and next priorities"
|
|
7946
|
+
})
|
|
7947
|
+
},
|
|
7948
|
+
{
|
|
7949
|
+
description: "Create a new initiative \u2014 human approves it along with tasks"
|
|
7950
|
+
}
|
|
7951
|
+
)
|
|
7952
|
+
],
|
|
7953
|
+
{
|
|
7954
|
+
description: "Initiative to group tasks under. Pass an existing ID string, a {title, content} object to create new, or omit for general/no initiative."
|
|
7955
|
+
}
|
|
7956
|
+
)
|
|
7957
|
+
),
|
|
7958
|
+
context: Type.Optional(
|
|
7959
|
+
Type.String({
|
|
7960
|
+
description: "Why this plan is being proposed \u2014 helps the human understand the reasoning behind the proposal"
|
|
7961
|
+
})
|
|
7962
|
+
),
|
|
7963
|
+
tasks: Type.Array(
|
|
7838
7964
|
Type.Object({
|
|
7839
7965
|
title: Type.String({
|
|
7840
7966
|
description: "Task title \u2014 short, action-oriented (max 200 chars)"
|
|
@@ -7844,110 +7970,278 @@ var ORCHESTRATOR_TOOLS = {
|
|
|
7844
7970
|
description: "Detailed instructions: what to do, why, what done looks like, known constraints"
|
|
7845
7971
|
})
|
|
7846
7972
|
),
|
|
7973
|
+
substeps: Type.Optional(
|
|
7974
|
+
Type.Array(Type.String(), {
|
|
7975
|
+
description: "Human-reviewable checklist items. The human can toggle each on/off before approving. Use for discrete steps within the task.",
|
|
7976
|
+
minItems: 1,
|
|
7977
|
+
maxItems: 20
|
|
7978
|
+
})
|
|
7979
|
+
),
|
|
7980
|
+
file: Type.Optional(
|
|
7981
|
+
Type.Union(
|
|
7982
|
+
[
|
|
7983
|
+
Type.Object(
|
|
7984
|
+
{
|
|
7985
|
+
existingId: Type.String({
|
|
7986
|
+
description: "Existing file ID to link to this task"
|
|
7987
|
+
})
|
|
7988
|
+
},
|
|
7989
|
+
{
|
|
7990
|
+
description: "Link an existing file \u2014 bot receives its content when claiming the task"
|
|
7991
|
+
}
|
|
7992
|
+
),
|
|
7993
|
+
Type.Object(
|
|
7994
|
+
{
|
|
7995
|
+
name: Type.String({
|
|
7996
|
+
description: "File name (max 200 chars)"
|
|
7997
|
+
}),
|
|
7998
|
+
description: Type.Optional(
|
|
7999
|
+
Type.String({
|
|
8000
|
+
description: "Brief description of the file's purpose"
|
|
8001
|
+
})
|
|
8002
|
+
)
|
|
8003
|
+
},
|
|
8004
|
+
{
|
|
8005
|
+
description: "Create a new file on approval \u2014 use when the task will produce a persistent document"
|
|
8006
|
+
}
|
|
8007
|
+
)
|
|
8008
|
+
],
|
|
8009
|
+
{
|
|
8010
|
+
description: "File to link: { existingId } for existing files, or { name, description? } to create a new one on approval. Omit if no file needed."
|
|
8011
|
+
}
|
|
8012
|
+
)
|
|
8013
|
+
),
|
|
7847
8014
|
estimatedTokens: Type.Optional(
|
|
7848
8015
|
Type.Number({
|
|
7849
8016
|
description: "Estimated token cost \u2014 consider files to read, reasoning, output"
|
|
7850
8017
|
})
|
|
7851
8018
|
),
|
|
7852
|
-
|
|
7853
|
-
Type.Array(Type.String(), {
|
|
7854
|
-
description: "Task IDs this suggestion depends on (blocks claiming until those are done)"
|
|
7855
|
-
})
|
|
7856
|
-
),
|
|
7857
|
-
initiativeId: Type.Optional(
|
|
8019
|
+
scheduledFor: Type.Optional(
|
|
7858
8020
|
Type.String({
|
|
7859
|
-
description: "
|
|
8021
|
+
description: "ISO-8601 date for when this task should become available. Use for future-dated or recurring work."
|
|
7860
8022
|
})
|
|
7861
8023
|
)
|
|
7862
8024
|
}),
|
|
7863
8025
|
{
|
|
7864
|
-
description: "Array of
|
|
8026
|
+
description: "Array of tasks to propose (1-20)",
|
|
7865
8027
|
minItems: 1,
|
|
7866
8028
|
maxItems: 20
|
|
7867
8029
|
}
|
|
7868
|
-
)
|
|
7869
|
-
|
|
8030
|
+
)
|
|
8031
|
+
})
|
|
8032
|
+
}
|
|
8033
|
+
};
|
|
8034
|
+
var SHARED_TOOLS = {
|
|
8035
|
+
UPDATE_FILE: {
|
|
8036
|
+
name: "deskfree_update_file",
|
|
8037
|
+
description: `Update a file's content. Use this to save work to a persistent file linked to your task. Call incrementally as you build content. Use format="html" for rich web content; use format="markdown" (default) for everything else.`,
|
|
8038
|
+
parameters: Type.Object({
|
|
8039
|
+
fileId: Type.String({ description: "File ID to update" }),
|
|
8040
|
+
content: Type.String({
|
|
8041
|
+
description: "Full file content (replaces previous)"
|
|
8042
|
+
}),
|
|
8043
|
+
contentFormat: Type.Optional(
|
|
8044
|
+
Type.Union([Type.Literal("markdown"), Type.Literal("html")], {
|
|
8045
|
+
description: '"markdown" (default) for text/documents, "html" for rich web content.'
|
|
8046
|
+
})
|
|
8047
|
+
)
|
|
8048
|
+
})
|
|
8049
|
+
},
|
|
8050
|
+
COMPLETE_TASK: {
|
|
8051
|
+
name: "deskfree_complete_task",
|
|
8052
|
+
description: 'Finish a task. Outcome "done" = work complete (summary required). Outcome "blocked" = need human input. Both move to human. For evaluation tasks (mode="evaluation"), include the evaluation object when outcome is "done".',
|
|
8053
|
+
parameters: Type.Object({
|
|
8054
|
+
taskId: Type.String({ description: "Task UUID" }),
|
|
8055
|
+
outcome: Type.Union([Type.Literal("done"), Type.Literal("blocked")], {
|
|
8056
|
+
description: '"done" = work complete, "blocked" = need human input'
|
|
8057
|
+
}),
|
|
8058
|
+
summary: Type.Optional(
|
|
7870
8059
|
Type.String({
|
|
7871
|
-
description:
|
|
8060
|
+
description: 'Brief summary of what was accomplished (required for outcome "done", max 2000 chars)'
|
|
7872
8061
|
})
|
|
7873
8062
|
),
|
|
7874
|
-
|
|
7875
|
-
Type.
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
description: "
|
|
7879
|
-
}),
|
|
7880
|
-
content: Type.String({
|
|
7881
|
-
description: "Initiative content markdown \u2014 current state, approach, and next priorities"
|
|
8063
|
+
evaluation: Type.Optional(
|
|
8064
|
+
Type.Object(
|
|
8065
|
+
{
|
|
8066
|
+
reasoning: Type.String({
|
|
8067
|
+
description: "Explanation of your evaluation \u2014 what you analyzed and why you did or did not update the ways of working and/or initiative content"
|
|
7882
8068
|
}),
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
8069
|
+
globalWoW: Type.Object(
|
|
8070
|
+
{
|
|
8071
|
+
hasChanges: Type.Boolean({
|
|
8072
|
+
description: "Whether the global ways of working should be updated"
|
|
8073
|
+
}),
|
|
8074
|
+
updatedContent: Type.Optional(
|
|
8075
|
+
Type.String({
|
|
8076
|
+
description: "Full updated global ways-of-working markdown (required if hasChanges=true)"
|
|
8077
|
+
})
|
|
8078
|
+
)
|
|
8079
|
+
},
|
|
8080
|
+
{
|
|
8081
|
+
description: "Global ways-of-working update \u2014 patterns that apply across all work"
|
|
8082
|
+
}
|
|
8083
|
+
),
|
|
8084
|
+
initiative: Type.Optional(
|
|
8085
|
+
Type.Object(
|
|
8086
|
+
{
|
|
8087
|
+
hasChanges: Type.Boolean({
|
|
8088
|
+
description: "Whether the initiative content should be updated (ignored if task has no initiative)"
|
|
8089
|
+
}),
|
|
8090
|
+
updatedContent: Type.Optional(
|
|
8091
|
+
Type.String({
|
|
8092
|
+
description: "Full updated initiative content markdown (required if hasChanges=true)"
|
|
8093
|
+
})
|
|
8094
|
+
)
|
|
8095
|
+
},
|
|
8096
|
+
{
|
|
8097
|
+
description: "Initiative content update \u2014 what was learned about this specific area of focus. Ignored if the task has no initiative_id."
|
|
8098
|
+
}
|
|
8099
|
+
)
|
|
7887
8100
|
)
|
|
7888
|
-
}
|
|
8101
|
+
},
|
|
7889
8102
|
{
|
|
7890
|
-
description: '
|
|
7891
|
-
minItems: 1,
|
|
7892
|
-
maxItems: 10
|
|
8103
|
+
description: 'Required when completing an evaluation task (mode="evaluation") with outcome "done". Review global WoW and initiative content and provide your analysis.'
|
|
7893
8104
|
}
|
|
7894
8105
|
)
|
|
7895
8106
|
)
|
|
7896
8107
|
})
|
|
7897
8108
|
},
|
|
7898
|
-
|
|
7899
|
-
name: "
|
|
7900
|
-
description: "
|
|
8109
|
+
SEND_MESSAGE: {
|
|
8110
|
+
name: "deskfree_send_message",
|
|
8111
|
+
description: "Send a message in the task thread (progress update, question, status report).",
|
|
7901
8112
|
parameters: Type.Object({
|
|
7902
|
-
|
|
8113
|
+
content: Type.String({
|
|
8114
|
+
description: "Message content."
|
|
8115
|
+
}),
|
|
8116
|
+
taskId: Type.Optional(
|
|
8117
|
+
Type.String({
|
|
8118
|
+
description: "Task UUID (optional if context provides it)"
|
|
8119
|
+
})
|
|
8120
|
+
)
|
|
7903
8121
|
})
|
|
7904
8122
|
},
|
|
7905
|
-
|
|
7906
|
-
name: "
|
|
7907
|
-
description: "
|
|
8123
|
+
PROPOSE: {
|
|
8124
|
+
name: "deskfree_propose",
|
|
8125
|
+
description: "Propose a plan for human approval. Nothing is created until the human reviews and approves in a modal. One initiative per call \u2014 make multiple calls for multiple initiatives. Include substeps as human-reviewable checklist items.",
|
|
7908
8126
|
parameters: Type.Object({
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
}),
|
|
7913
|
-
globalWoW: Type.Object(
|
|
7914
|
-
{
|
|
7915
|
-
hasChanges: Type.Boolean({
|
|
7916
|
-
description: "Whether the global ways of working should be updated"
|
|
7917
|
-
}),
|
|
7918
|
-
updatedContent: Type.Optional(
|
|
8127
|
+
initiative: Type.Optional(
|
|
8128
|
+
Type.Union(
|
|
8129
|
+
[
|
|
7919
8130
|
Type.String({
|
|
7920
|
-
description:
|
|
7921
|
-
})
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
8131
|
+
description: 'Existing initiative ID (e.g. "INI_123") to group tasks under'
|
|
8132
|
+
}),
|
|
8133
|
+
Type.Object(
|
|
8134
|
+
{
|
|
8135
|
+
title: Type.String({
|
|
8136
|
+
description: "New initiative title (max 200 chars)"
|
|
8137
|
+
}),
|
|
8138
|
+
content: Type.String({
|
|
8139
|
+
description: "Initiative content markdown \u2014 current state, approach, and next priorities"
|
|
8140
|
+
})
|
|
8141
|
+
},
|
|
8142
|
+
{
|
|
8143
|
+
description: "Create a new initiative \u2014 human approves it along with tasks"
|
|
8144
|
+
}
|
|
8145
|
+
)
|
|
8146
|
+
],
|
|
8147
|
+
{
|
|
8148
|
+
description: "Initiative to group tasks under. Pass an existing ID string, a {title, content} object to create new, or omit for general/no initiative."
|
|
8149
|
+
}
|
|
8150
|
+
)
|
|
7927
8151
|
),
|
|
7928
|
-
|
|
7929
|
-
{
|
|
7930
|
-
|
|
7931
|
-
|
|
8152
|
+
context: Type.Optional(
|
|
8153
|
+
Type.String({
|
|
8154
|
+
description: "Why this plan is being proposed \u2014 helps the human understand the reasoning behind the proposal"
|
|
8155
|
+
})
|
|
8156
|
+
),
|
|
8157
|
+
tasks: Type.Array(
|
|
8158
|
+
Type.Object({
|
|
8159
|
+
title: Type.String({
|
|
8160
|
+
description: "Task title \u2014 short, action-oriented (max 200 chars)"
|
|
7932
8161
|
}),
|
|
7933
|
-
|
|
8162
|
+
instructions: Type.Optional(
|
|
7934
8163
|
Type.String({
|
|
7935
|
-
description: "
|
|
8164
|
+
description: "Detailed instructions: what to do, why, what done looks like, known constraints"
|
|
8165
|
+
})
|
|
8166
|
+
),
|
|
8167
|
+
substeps: Type.Optional(
|
|
8168
|
+
Type.Array(Type.String(), {
|
|
8169
|
+
description: "Human-reviewable checklist items. The human can toggle each on/off before approving. Use for discrete steps within the task.",
|
|
8170
|
+
minItems: 1,
|
|
8171
|
+
maxItems: 20
|
|
8172
|
+
})
|
|
8173
|
+
),
|
|
8174
|
+
file: Type.Optional(
|
|
8175
|
+
Type.Union(
|
|
8176
|
+
[
|
|
8177
|
+
Type.Object(
|
|
8178
|
+
{
|
|
8179
|
+
existingId: Type.String({
|
|
8180
|
+
description: "Existing file ID to link to this task"
|
|
8181
|
+
})
|
|
8182
|
+
},
|
|
8183
|
+
{
|
|
8184
|
+
description: "Link an existing file \u2014 bot receives its content when claiming the task"
|
|
8185
|
+
}
|
|
8186
|
+
),
|
|
8187
|
+
Type.Object(
|
|
8188
|
+
{
|
|
8189
|
+
name: Type.String({
|
|
8190
|
+
description: "File name (max 200 chars)"
|
|
8191
|
+
}),
|
|
8192
|
+
description: Type.Optional(
|
|
8193
|
+
Type.String({
|
|
8194
|
+
description: "Brief description of the file's purpose"
|
|
8195
|
+
})
|
|
8196
|
+
)
|
|
8197
|
+
},
|
|
8198
|
+
{
|
|
8199
|
+
description: "Create a new file on approval \u2014 use when the task will produce a persistent document"
|
|
8200
|
+
}
|
|
8201
|
+
)
|
|
8202
|
+
],
|
|
8203
|
+
{
|
|
8204
|
+
description: "File to link: { existingId } for existing files, or { name, description? } to create a new one on approval. Omit if no file needed."
|
|
8205
|
+
}
|
|
8206
|
+
)
|
|
8207
|
+
),
|
|
8208
|
+
estimatedTokens: Type.Optional(
|
|
8209
|
+
Type.Number({
|
|
8210
|
+
description: "Estimated token cost \u2014 consider files to read, reasoning, output"
|
|
8211
|
+
})
|
|
8212
|
+
),
|
|
8213
|
+
scheduledFor: Type.Optional(
|
|
8214
|
+
Type.String({
|
|
8215
|
+
description: "ISO-8601 date for when this task should become available. Use for future-dated or recurring work."
|
|
7936
8216
|
})
|
|
7937
8217
|
)
|
|
7938
|
-
},
|
|
8218
|
+
}),
|
|
7939
8219
|
{
|
|
7940
|
-
description: "
|
|
8220
|
+
description: "Array of tasks to propose (1-20)",
|
|
8221
|
+
minItems: 1,
|
|
8222
|
+
maxItems: 20
|
|
7941
8223
|
}
|
|
7942
8224
|
)
|
|
7943
8225
|
})
|
|
7944
8226
|
}
|
|
7945
8227
|
};
|
|
7946
8228
|
var WORKER_TOOLS = {
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
8229
|
+
START_TASK: {
|
|
8230
|
+
name: "deskfree_start_task",
|
|
8231
|
+
description: 'Claim a bot task (isWorking=false) and start working. Returns full context (instructions, message history, and fileContext if the task has a linked file \u2014 use the file content as working context). For evaluation tasks, also returns WoW context and mode="evaluation".',
|
|
8232
|
+
parameters: Type.Object({
|
|
8233
|
+
taskId: Type.String({ description: "Task UUID to claim" }),
|
|
8234
|
+
runnerId: Type.Optional(
|
|
8235
|
+
Type.String({
|
|
8236
|
+
description: "Runner/session identifier for tracking (max 100 chars)"
|
|
8237
|
+
})
|
|
8238
|
+
)
|
|
8239
|
+
})
|
|
8240
|
+
},
|
|
8241
|
+
UPDATE_FILE: SHARED_TOOLS.UPDATE_FILE,
|
|
8242
|
+
COMPLETE_TASK: SHARED_TOOLS.COMPLETE_TASK,
|
|
8243
|
+
SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
|
|
8244
|
+
PROPOSE: SHARED_TOOLS.PROPOSE
|
|
7951
8245
|
};
|
|
7952
8246
|
var CHANNEL_META = {
|
|
7953
8247
|
name: "DeskFree",
|
|
@@ -8504,24 +8798,30 @@ var deskFreePlugin = {
|
|
|
8504
8798
|
};
|
|
8505
8799
|
|
|
8506
8800
|
// src/context.ts
|
|
8507
|
-
var DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014
|
|
8508
|
-
|
|
8509
|
-
1. **Check state** \u2192 \`deskfree_state\` \u2014 see
|
|
8510
|
-
2. **
|
|
8511
|
-
3. **
|
|
8512
|
-
4. **
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8801
|
+
var DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 Orchestrator
|
|
8802
|
+
You are the orchestrator. You assess state, propose plans, and dispatch work to sub-agents.
|
|
8803
|
+
1. **Check state** \u2192 \`deskfree_state\` \u2014 see tasks, initiatives, ways of working.
|
|
8804
|
+
2. **Propose plan** \u2192 \`deskfree_propose\` \u2014 ALL work requires human-approved tasks first.
|
|
8805
|
+
3. **Dispatch** \u2192 spawn a sub-agent for each approved task. Pass the taskId \u2014 the sub-agent claims it.
|
|
8806
|
+
4. **Communicate** \u2192 \`deskfree_send_message\` for updates outside task threads.
|
|
8807
|
+
|
|
8808
|
+
You do NOT claim tasks or do work directly. Sub-agents handle execution.
|
|
8809
|
+
- When a human writes in a task thread, you receive it with recent context. Use \`deskfree_reopen_task\` if it needs more work.
|
|
8810
|
+
- Write task instructions as if briefing a contractor who has never seen the codebase.
|
|
8811
|
+
- Estimate token cost per task \u2014 consider files to read, reasoning, output.
|
|
8812
|
+
- One initiative per proposal \u2014 make multiple calls for multiple initiatives.`;
|
|
8813
|
+
var DESKFREE_WORKER_DIRECTIVE = `## DeskFree Worker
|
|
8814
|
+
You are a worker sub-agent. Call \`deskfree_start_task\` with your taskId to claim and load context.
|
|
8815
|
+
Tools: deskfree_start_task, deskfree_update_file, deskfree_complete_task, deskfree_send_message, deskfree_propose.
|
|
8816
|
+
- Claim your task first with deskfree_start_task \u2014 this loads instructions, messages, and file context.
|
|
8817
|
+
- Save work to linked files with deskfree_update_file (incrementally).
|
|
8818
|
+
- Complete with deskfree_complete_task when done (summary required for outcome "done").
|
|
8819
|
+
- For evaluation tasks (mode="evaluation"), include the evaluation object in complete_task.
|
|
8820
|
+
- Propose follow-up tasks with deskfree_propose if you discover more work.`;
|
|
8821
|
+
function getDeskFreeContext(sessionKey) {
|
|
8822
|
+
const isWorker = sessionKey && (sessionKey.includes(":sub:") || sessionKey.includes(":spawn:") || sessionKey.includes(":run:"));
|
|
8823
|
+
const directive = isWorker ? DESKFREE_WORKER_DIRECTIVE : DESKFREE_AGENT_DIRECTIVE;
|
|
8824
|
+
return `${directive}
|
|
8525
8825
|
|
|
8526
8826
|
<!-- deskfree-plugin:${PLUGIN_VERSION} -->`;
|
|
8527
8827
|
}
|
|
@@ -8558,15 +8858,7 @@ function formatTaskResponse(task, summary, nextActions) {
|
|
|
8558
8858
|
content: [
|
|
8559
8859
|
{
|
|
8560
8860
|
type: "text",
|
|
8561
|
-
text: JSON.stringify(
|
|
8562
|
-
{
|
|
8563
|
-
summary,
|
|
8564
|
-
nextActions,
|
|
8565
|
-
task
|
|
8566
|
-
},
|
|
8567
|
-
null,
|
|
8568
|
-
2
|
|
8569
|
-
)
|
|
8861
|
+
text: JSON.stringify({ summary, nextActions, task }, null, 2)
|
|
8570
8862
|
}
|
|
8571
8863
|
]
|
|
8572
8864
|
};
|
|
@@ -8577,11 +8869,7 @@ function formatConfirmation(summary, nextActions, data) {
|
|
|
8577
8869
|
{
|
|
8578
8870
|
type: "text",
|
|
8579
8871
|
text: JSON.stringify(
|
|
8580
|
-
{
|
|
8581
|
-
summary,
|
|
8582
|
-
nextActions,
|
|
8583
|
-
...data ? { data } : {}
|
|
8584
|
-
},
|
|
8872
|
+
{ summary, nextActions, ...data ? { data } : {} },
|
|
8585
8873
|
null,
|
|
8586
8874
|
2
|
|
8587
8875
|
)
|
|
@@ -8642,14 +8930,7 @@ function errorResult(err) {
|
|
|
8642
8930
|
content: [
|
|
8643
8931
|
{
|
|
8644
8932
|
type: "text",
|
|
8645
|
-
text: JSON.stringify(
|
|
8646
|
-
{
|
|
8647
|
-
error: true,
|
|
8648
|
-
message: rawMessage
|
|
8649
|
-
},
|
|
8650
|
-
null,
|
|
8651
|
-
2
|
|
8652
|
-
)
|
|
8933
|
+
text: JSON.stringify({ error: true, message: rawMessage }, null, 2)
|
|
8653
8934
|
}
|
|
8654
8935
|
],
|
|
8655
8936
|
isError: true
|
|
@@ -8666,9 +8947,7 @@ function validateStringParam(params, key, required) {
|
|
|
8666
8947
|
}
|
|
8667
8948
|
const value = params[key];
|
|
8668
8949
|
if (value == null) {
|
|
8669
|
-
if (required) {
|
|
8670
|
-
throw new Error(`Required parameter '${key}' is missing`);
|
|
8671
|
-
}
|
|
8950
|
+
if (required) throw new Error(`Required parameter '${key}' is missing`);
|
|
8672
8951
|
return void 0;
|
|
8673
8952
|
}
|
|
8674
8953
|
if (typeof value !== "string") {
|
|
@@ -8679,34 +8958,124 @@ function validateStringParam(params, key, required) {
|
|
|
8679
8958
|
}
|
|
8680
8959
|
return value;
|
|
8681
8960
|
}
|
|
8682
|
-
function
|
|
8683
|
-
if (!
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8961
|
+
function parseProposeTasks(raw) {
|
|
8962
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
8963
|
+
throw new Error('Parameter "tasks" must be a non-empty array');
|
|
8964
|
+
}
|
|
8965
|
+
return raw.map((s, i) => {
|
|
8966
|
+
if (typeof s !== "object" || s === null) {
|
|
8967
|
+
throw new Error(`tasks[${i}] must be an object`);
|
|
8968
|
+
}
|
|
8969
|
+
const item = s;
|
|
8970
|
+
const title = item["title"];
|
|
8971
|
+
if (typeof title !== "string" || title.trim() === "") {
|
|
8972
|
+
throw new Error(`tasks[${i}].title must be a non-empty string`);
|
|
8973
|
+
}
|
|
8974
|
+
let substeps;
|
|
8975
|
+
if (Array.isArray(item["substeps"]) && item["substeps"].length > 0) {
|
|
8976
|
+
substeps = item["substeps"].filter((s2) => typeof s2 === "string" && s2.trim() !== "").map((s2) => s2.trim());
|
|
8977
|
+
if (substeps.length === 0) substeps = void 0;
|
|
8978
|
+
}
|
|
8979
|
+
let file;
|
|
8980
|
+
if (item["file"] && typeof item["file"] === "object") {
|
|
8981
|
+
const f = item["file"];
|
|
8982
|
+
if (typeof f["existingId"] === "string" && f["existingId"].trim()) {
|
|
8983
|
+
file = { type: "existing", existingId: f["existingId"].trim() };
|
|
8984
|
+
} else if (typeof f["name"] === "string" && f["name"].trim()) {
|
|
8985
|
+
file = {
|
|
8986
|
+
type: "new",
|
|
8987
|
+
name: f["name"].trim(),
|
|
8988
|
+
description: typeof f["description"] === "string" ? f["description"] : void 0
|
|
8989
|
+
};
|
|
8990
|
+
}
|
|
8688
8991
|
}
|
|
8689
|
-
return
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8992
|
+
return {
|
|
8993
|
+
title: title.trim(),
|
|
8994
|
+
instructions: typeof item["instructions"] === "string" ? item["instructions"] : void 0,
|
|
8995
|
+
substeps,
|
|
8996
|
+
file,
|
|
8997
|
+
estimatedTokens: typeof item["estimatedTokens"] === "number" ? item["estimatedTokens"] : void 0,
|
|
8998
|
+
scheduledFor: typeof item["scheduledFor"] === "string" ? item["scheduledFor"] : void 0
|
|
8999
|
+
};
|
|
9000
|
+
});
|
|
9001
|
+
}
|
|
9002
|
+
function parseInitiative(raw) {
|
|
9003
|
+
if (!raw) return void 0;
|
|
9004
|
+
if (typeof raw === "string") return raw;
|
|
9005
|
+
if (typeof raw === "object") {
|
|
9006
|
+
const obj = raw;
|
|
9007
|
+
if (typeof obj["title"] === "string" && obj["title"].trim()) {
|
|
9008
|
+
return {
|
|
9009
|
+
title: String(obj["title"]).trim(),
|
|
9010
|
+
content: String(obj["content"] ?? "")
|
|
9011
|
+
};
|
|
8695
9012
|
}
|
|
8696
|
-
return void 0;
|
|
8697
|
-
}
|
|
8698
|
-
if (typeof value !== "string") {
|
|
8699
|
-
throw new Error(`Parameter '${key}' must be a string, got ${typeof value}`);
|
|
8700
|
-
}
|
|
8701
|
-
if (required && value.trim() === "") {
|
|
8702
|
-
throw new Error(`Required parameter '${key}' cannot be empty`);
|
|
8703
|
-
}
|
|
8704
|
-
if (!allowedValues.includes(value)) {
|
|
8705
|
-
throw new Error(
|
|
8706
|
-
`Parameter '${key}' must be one of: ${allowedValues.join(", ")}, got: ${value}`
|
|
8707
|
-
);
|
|
8708
9013
|
}
|
|
8709
|
-
return
|
|
9014
|
+
return void 0;
|
|
9015
|
+
}
|
|
9016
|
+
function makeReopenTaskHandler(client) {
|
|
9017
|
+
return async (_id, params) => {
|
|
9018
|
+
try {
|
|
9019
|
+
const taskId = validateStringParam(params, "taskId", true);
|
|
9020
|
+
const reason = validateStringParam(params, "reason", false);
|
|
9021
|
+
const result = await client.reopenTask({ taskId, reason });
|
|
9022
|
+
return formatTaskResponse(result, `Task "${result.title}" reopened`, [
|
|
9023
|
+
"Task is now in bot queue \u2014 spawn a sub-agent to claim it"
|
|
9024
|
+
]);
|
|
9025
|
+
} catch (err) {
|
|
9026
|
+
return errorResult(err);
|
|
9027
|
+
}
|
|
9028
|
+
};
|
|
9029
|
+
}
|
|
9030
|
+
function makeSendMessageHandler(client) {
|
|
9031
|
+
return async (_id, params) => {
|
|
9032
|
+
try {
|
|
9033
|
+
const content = validateStringParam(params, "content", true);
|
|
9034
|
+
const taskId = validateStringParam(params, "taskId", false);
|
|
9035
|
+
await client.sendMessage({ content, taskId });
|
|
9036
|
+
return formatConfirmation(
|
|
9037
|
+
`Message sent${taskId ? ` to task ${taskId}` : ""}`,
|
|
9038
|
+
[
|
|
9039
|
+
"Message delivered to the human",
|
|
9040
|
+
taskId ? "Continue working on the task or wait for response" : "Check for response with task messages"
|
|
9041
|
+
]
|
|
9042
|
+
);
|
|
9043
|
+
} catch (err) {
|
|
9044
|
+
return errorResult(err);
|
|
9045
|
+
}
|
|
9046
|
+
};
|
|
9047
|
+
}
|
|
9048
|
+
function makeProposeHandler(client) {
|
|
9049
|
+
return async (_id, params) => {
|
|
9050
|
+
try {
|
|
9051
|
+
const tasks = parseProposeTasks(params?.tasks);
|
|
9052
|
+
const context = validateStringParam(params, "context", false);
|
|
9053
|
+
const initiative = parseInitiative(params?.initiative);
|
|
9054
|
+
const result = await client.proposePlan({
|
|
9055
|
+
tasks: tasks.map((t) => ({
|
|
9056
|
+
title: t.title,
|
|
9057
|
+
instructions: t.instructions,
|
|
9058
|
+
substeps: t.substeps,
|
|
9059
|
+
file: t.file,
|
|
9060
|
+
estimatedTokens: t.estimatedTokens,
|
|
9061
|
+
scheduledFor: t.scheduledFor
|
|
9062
|
+
})),
|
|
9063
|
+
initiative,
|
|
9064
|
+
context: context ?? void 0
|
|
9065
|
+
});
|
|
9066
|
+
return formatConfirmation(
|
|
9067
|
+
`Proposed ${tasks.length} task${tasks.length === 1 ? "" : "s"} for human approval`,
|
|
9068
|
+
[
|
|
9069
|
+
"Plan stored as proposal \u2014 NO tasks created until human approves",
|
|
9070
|
+
"Human will review in the proposal modal and can edit/approve/reject each item",
|
|
9071
|
+
"Use deskfree_state to check for approved tasks"
|
|
9072
|
+
],
|
|
9073
|
+
result
|
|
9074
|
+
);
|
|
9075
|
+
} catch (err) {
|
|
9076
|
+
return errorResult(err);
|
|
9077
|
+
}
|
|
9078
|
+
};
|
|
8710
9079
|
}
|
|
8711
9080
|
function createOrchestratorTools(api) {
|
|
8712
9081
|
const account = resolveAccountFromConfig(api);
|
|
@@ -8727,336 +9096,16 @@ function createOrchestratorTools(api) {
|
|
|
8727
9096
|
}
|
|
8728
9097
|
},
|
|
8729
9098
|
{
|
|
8730
|
-
...ORCHESTRATOR_TOOLS.
|
|
8731
|
-
|
|
8732
|
-
try {
|
|
8733
|
-
const taskId = validateStringParam(params, "taskId", true);
|
|
8734
|
-
const result = await client.claimTask({ taskId });
|
|
8735
|
-
setActiveTaskId(taskId);
|
|
8736
|
-
return {
|
|
8737
|
-
content: [
|
|
8738
|
-
{
|
|
8739
|
-
type: "text",
|
|
8740
|
-
text: JSON.stringify(
|
|
8741
|
-
{
|
|
8742
|
-
summary: `Claimed task "${result.title}" \u2014 full context loaded`,
|
|
8743
|
-
nextActions: [
|
|
8744
|
-
"Read the instructions and message history carefully",
|
|
8745
|
-
"Update deliverable incrementally with deskfree_update_deliverable",
|
|
8746
|
-
"Complete with deskfree_complete_task when done"
|
|
8747
|
-
],
|
|
8748
|
-
task: result
|
|
8749
|
-
},
|
|
8750
|
-
null,
|
|
8751
|
-
2
|
|
8752
|
-
)
|
|
8753
|
-
}
|
|
8754
|
-
]
|
|
8755
|
-
};
|
|
8756
|
-
} catch (err) {
|
|
8757
|
-
return errorResult(err);
|
|
8758
|
-
}
|
|
8759
|
-
}
|
|
8760
|
-
},
|
|
8761
|
-
{
|
|
8762
|
-
...ORCHESTRATOR_TOOLS.UPDATE_DELIVERABLE,
|
|
8763
|
-
async execute(_id, params) {
|
|
8764
|
-
try {
|
|
8765
|
-
const taskId = validateStringParam(params, "taskId", true);
|
|
8766
|
-
const deliverable = validateStringParam(params, "deliverable", true);
|
|
8767
|
-
const format = validateEnumParam(
|
|
8768
|
-
params,
|
|
8769
|
-
"format",
|
|
8770
|
-
["markdown", "html"],
|
|
8771
|
-
false
|
|
8772
|
-
);
|
|
8773
|
-
await client.updateDeliverable({ taskId, deliverable, format });
|
|
8774
|
-
return formatConfirmation(`Updated deliverable for task ${taskId}`, [
|
|
8775
|
-
"Deliverable has been saved",
|
|
8776
|
-
"Complete with deskfree_complete_task when ready"
|
|
8777
|
-
]);
|
|
8778
|
-
} catch (err) {
|
|
8779
|
-
return errorResult(err);
|
|
8780
|
-
}
|
|
8781
|
-
}
|
|
8782
|
-
},
|
|
8783
|
-
{
|
|
8784
|
-
...ORCHESTRATOR_TOOLS.COMPLETE_TASK,
|
|
8785
|
-
async execute(_id, params) {
|
|
8786
|
-
try {
|
|
8787
|
-
const taskId = validateStringParam(params, "taskId", true);
|
|
8788
|
-
const outcome = validateEnumParam(
|
|
8789
|
-
params,
|
|
8790
|
-
"outcome",
|
|
8791
|
-
["done", "blocked"],
|
|
8792
|
-
true
|
|
8793
|
-
);
|
|
8794
|
-
const result = await client.completeTask({ taskId, outcome });
|
|
8795
|
-
setActiveTaskId(null);
|
|
8796
|
-
const summaryVerb = outcome === "done" ? "completed" : "blocked";
|
|
8797
|
-
const icon = outcome === "done" ? "\u2705" : "\u{1F6AB}";
|
|
8798
|
-
await client.sendMessage({
|
|
8799
|
-
content: `${icon} Task ${summaryVerb}: "${result.title}"`
|
|
8800
|
-
}).catch(() => {
|
|
8801
|
-
});
|
|
8802
|
-
return formatTaskResponse(
|
|
8803
|
-
result,
|
|
8804
|
-
`Task "${result.title}" marked as ${summaryVerb} \u2014 waiting for human`,
|
|
8805
|
-
[
|
|
8806
|
-
outcome === "done" ? "Human will review the deliverable" : "Human will review the blocker and provide guidance",
|
|
8807
|
-
"Use deskfree_state to check for other tasks"
|
|
8808
|
-
]
|
|
8809
|
-
);
|
|
8810
|
-
} catch (err) {
|
|
8811
|
-
return errorResult(err);
|
|
8812
|
-
}
|
|
8813
|
-
}
|
|
9099
|
+
...ORCHESTRATOR_TOOLS.REOPEN_TASK,
|
|
9100
|
+
execute: makeReopenTaskHandler(client)
|
|
8814
9101
|
},
|
|
8815
9102
|
{
|
|
8816
9103
|
...ORCHESTRATOR_TOOLS.SEND_MESSAGE,
|
|
8817
|
-
|
|
8818
|
-
try {
|
|
8819
|
-
const content = validateStringParam(params, "content", false);
|
|
8820
|
-
const taskId = validateStringParam(params, "taskId", false);
|
|
8821
|
-
const rawSuggestions = params?.suggestions;
|
|
8822
|
-
let suggestions;
|
|
8823
|
-
if (Array.isArray(rawSuggestions) && rawSuggestions.length > 0) {
|
|
8824
|
-
suggestions = rawSuggestions.map((t, i) => {
|
|
8825
|
-
if (typeof t !== "object" || t === null) {
|
|
8826
|
-
throw new Error(`suggestions[${i}] must be an object`);
|
|
8827
|
-
}
|
|
8828
|
-
const item = t;
|
|
8829
|
-
const title = item["title"];
|
|
8830
|
-
if (typeof title !== "string" || title.trim() === "") {
|
|
8831
|
-
throw new Error(
|
|
8832
|
-
`suggestions[${i}].title must be a non-empty string`
|
|
8833
|
-
);
|
|
8834
|
-
}
|
|
8835
|
-
const instructions = item["instructions"];
|
|
8836
|
-
return {
|
|
8837
|
-
title: title.trim(),
|
|
8838
|
-
instructions: typeof instructions === "string" ? instructions : void 0
|
|
8839
|
-
};
|
|
8840
|
-
});
|
|
8841
|
-
}
|
|
8842
|
-
if (!content && !suggestions) {
|
|
8843
|
-
throw new Error(
|
|
8844
|
-
'Either "content" or "suggestions" parameter is required'
|
|
8845
|
-
);
|
|
8846
|
-
}
|
|
8847
|
-
if (suggestions) {
|
|
8848
|
-
await client.sendMessage({ suggestions, taskId });
|
|
8849
|
-
return formatConfirmation(
|
|
8850
|
-
`Suggested ${suggestions.length} task${suggestions.length === 1 ? "" : "s"} for human review`,
|
|
8851
|
-
[
|
|
8852
|
-
"The human will see approve/reject buttons for each suggestion",
|
|
8853
|
-
"Continue working on the current task"
|
|
8854
|
-
]
|
|
8855
|
-
);
|
|
8856
|
-
}
|
|
8857
|
-
await client.sendMessage({ content, taskId });
|
|
8858
|
-
return formatConfirmation(
|
|
8859
|
-
`Message sent${taskId ? ` to task ${taskId}` : ""}`,
|
|
8860
|
-
[
|
|
8861
|
-
"Message delivered to the human",
|
|
8862
|
-
taskId ? "Continue working on the task or wait for response" : "Check for response with task messages"
|
|
8863
|
-
]
|
|
8864
|
-
);
|
|
8865
|
-
} catch (err) {
|
|
8866
|
-
return errorResult(err);
|
|
8867
|
-
}
|
|
8868
|
-
}
|
|
8869
|
-
},
|
|
8870
|
-
{
|
|
8871
|
-
...ORCHESTRATOR_TOOLS.SUGGEST_TASKS,
|
|
8872
|
-
async execute(_id, params) {
|
|
8873
|
-
try {
|
|
8874
|
-
const rawSuggestions = params?.suggestions;
|
|
8875
|
-
if (!Array.isArray(rawSuggestions) || rawSuggestions.length === 0) {
|
|
8876
|
-
throw new Error(
|
|
8877
|
-
'Parameter "suggestions" must be a non-empty array'
|
|
8878
|
-
);
|
|
8879
|
-
}
|
|
8880
|
-
const suggestions = rawSuggestions.map((s, i) => {
|
|
8881
|
-
if (typeof s !== "object" || s === null) {
|
|
8882
|
-
throw new Error(`suggestions[${i}] must be an object`);
|
|
8883
|
-
}
|
|
8884
|
-
const item = s;
|
|
8885
|
-
const title = item["title"];
|
|
8886
|
-
if (typeof title !== "string" || title.trim() === "") {
|
|
8887
|
-
throw new Error(
|
|
8888
|
-
`suggestions[${i}].title must be a non-empty string`
|
|
8889
|
-
);
|
|
8890
|
-
}
|
|
8891
|
-
return {
|
|
8892
|
-
title: title.trim(),
|
|
8893
|
-
instructions: typeof item["instructions"] === "string" ? item["instructions"] : void 0,
|
|
8894
|
-
estimatedTokens: typeof item["estimatedTokens"] === "number" ? item["estimatedTokens"] : void 0,
|
|
8895
|
-
dependsOn: Array.isArray(item["dependsOn"]) ? item["dependsOn"] : void 0,
|
|
8896
|
-
initiativeId: typeof item["initiativeId"] === "string" ? item["initiativeId"] : void 0
|
|
8897
|
-
};
|
|
8898
|
-
});
|
|
8899
|
-
const parentTaskId = validateStringParam(
|
|
8900
|
-
params,
|
|
8901
|
-
"parentTaskId",
|
|
8902
|
-
false
|
|
8903
|
-
);
|
|
8904
|
-
const rawInitiativeSuggestions = params?.initiativeSuggestions;
|
|
8905
|
-
let initiativeSuggestions;
|
|
8906
|
-
if (Array.isArray(rawInitiativeSuggestions) && rawInitiativeSuggestions.length > 0) {
|
|
8907
|
-
initiativeSuggestions = rawInitiativeSuggestions.map(
|
|
8908
|
-
(s, i) => {
|
|
8909
|
-
if (typeof s !== "object" || s === null) {
|
|
8910
|
-
throw new Error(`initiativeSuggestions[${i}] must be an object`);
|
|
8911
|
-
}
|
|
8912
|
-
const item = s;
|
|
8913
|
-
const title = item["title"];
|
|
8914
|
-
if (typeof title !== "string" || title.trim() === "") {
|
|
8915
|
-
throw new Error(
|
|
8916
|
-
`initiativeSuggestions[${i}].title must be a non-empty string`
|
|
8917
|
-
);
|
|
8918
|
-
}
|
|
8919
|
-
const content = item["content"];
|
|
8920
|
-
if (typeof content !== "string") {
|
|
8921
|
-
throw new Error(
|
|
8922
|
-
`initiativeSuggestions[${i}].content must be a string`
|
|
8923
|
-
);
|
|
8924
|
-
}
|
|
8925
|
-
return {
|
|
8926
|
-
title: title.trim(),
|
|
8927
|
-
content,
|
|
8928
|
-
taskRefs: Array.isArray(item["taskRefs"]) ? item["taskRefs"] : void 0
|
|
8929
|
-
};
|
|
8930
|
-
}
|
|
8931
|
-
);
|
|
8932
|
-
}
|
|
8933
|
-
const result = await client.suggestTasksDedicated({
|
|
8934
|
-
suggestions,
|
|
8935
|
-
parentTaskId,
|
|
8936
|
-
initiativeSuggestions
|
|
8937
|
-
});
|
|
8938
|
-
const initiativeCount = initiativeSuggestions?.length ?? 0;
|
|
8939
|
-
const summaryParts = [
|
|
8940
|
-
`Suggested ${suggestions.length} task${suggestions.length === 1 ? "" : "s"} for human approval`,
|
|
8941
|
-
...initiativeCount > 0 ? [
|
|
8942
|
-
`and ${initiativeCount} initiative${initiativeCount === 1 ? "" : "s"}`
|
|
8943
|
-
] : []
|
|
8944
|
-
];
|
|
8945
|
-
return formatConfirmation(
|
|
8946
|
-
summaryParts.join(" "),
|
|
8947
|
-
[
|
|
8948
|
-
'Tasks created with "suggested" status \u2014 human will approve or reject each',
|
|
8949
|
-
...initiativeCount > 0 ? ["Initiative suggestions created \u2014 human will approve or reject independently"] : [],
|
|
8950
|
-
"Use deskfree_state to check for approved tasks"
|
|
8951
|
-
],
|
|
8952
|
-
result
|
|
8953
|
-
);
|
|
8954
|
-
} catch (err) {
|
|
8955
|
-
return errorResult(err);
|
|
8956
|
-
}
|
|
8957
|
-
}
|
|
9104
|
+
execute: makeSendMessageHandler(client)
|
|
8958
9105
|
},
|
|
8959
9106
|
{
|
|
8960
|
-
...ORCHESTRATOR_TOOLS.
|
|
8961
|
-
|
|
8962
|
-
try {
|
|
8963
|
-
const taskId = validateStringParam(params, "taskId", true);
|
|
8964
|
-
const result = await client.claimEvaluation({ taskId });
|
|
8965
|
-
if (!result) {
|
|
8966
|
-
return formatConfirmation(
|
|
8967
|
-
"Evaluation already claimed by another process",
|
|
8968
|
-
["Use deskfree_state to check for other pending evaluations"]
|
|
8969
|
-
);
|
|
8970
|
-
}
|
|
8971
|
-
return {
|
|
8972
|
-
content: [
|
|
8973
|
-
{
|
|
8974
|
-
type: "text",
|
|
8975
|
-
text: JSON.stringify(
|
|
8976
|
-
{
|
|
8977
|
-
summary: `Claimed evaluation for task "${result.task.title}"`,
|
|
8978
|
-
nextActions: [
|
|
8979
|
-
"Review the task messages, current global ways of working, and initiative content (if present)",
|
|
8980
|
-
"Decide what to update: globalWoW (universal patterns), initiative (area-specific learnings), both, or neither",
|
|
8981
|
-
"Call deskfree_submit_evaluation with your analysis"
|
|
8982
|
-
],
|
|
8983
|
-
task: result.task,
|
|
8984
|
-
waysOfWorking: result.waysOfWorking,
|
|
8985
|
-
currentVersion: result.currentVersion,
|
|
8986
|
-
messages: result.messages,
|
|
8987
|
-
...result.initiative ? { initiative: result.initiative } : {}
|
|
8988
|
-
},
|
|
8989
|
-
null,
|
|
8990
|
-
2
|
|
8991
|
-
)
|
|
8992
|
-
}
|
|
8993
|
-
]
|
|
8994
|
-
};
|
|
8995
|
-
} catch (err) {
|
|
8996
|
-
return errorResult(err);
|
|
8997
|
-
}
|
|
8998
|
-
}
|
|
8999
|
-
},
|
|
9000
|
-
{
|
|
9001
|
-
...ORCHESTRATOR_TOOLS.SUBMIT_EVALUATION,
|
|
9002
|
-
async execute(_id, params) {
|
|
9003
|
-
try {
|
|
9004
|
-
const taskId = validateStringParam(params, "taskId", true);
|
|
9005
|
-
const reasoning = validateStringParam(params, "reasoning", true);
|
|
9006
|
-
const rawGlobalWoW = params?.globalWoW;
|
|
9007
|
-
if (typeof rawGlobalWoW !== "object" || rawGlobalWoW === null) {
|
|
9008
|
-
throw new Error('Parameter "globalWoW" must be an object');
|
|
9009
|
-
}
|
|
9010
|
-
const globalWoWObj = rawGlobalWoW;
|
|
9011
|
-
if (typeof globalWoWObj["hasChanges"] !== "boolean") {
|
|
9012
|
-
throw new Error('Parameter "globalWoW.hasChanges" must be a boolean');
|
|
9013
|
-
}
|
|
9014
|
-
const globalWoW = {
|
|
9015
|
-
hasChanges: globalWoWObj["hasChanges"],
|
|
9016
|
-
updatedContent: typeof globalWoWObj["updatedContent"] === "string" ? globalWoWObj["updatedContent"] : void 0
|
|
9017
|
-
};
|
|
9018
|
-
const rawInitiative = params?.initiative;
|
|
9019
|
-
if (typeof rawInitiative !== "object" || rawInitiative === null) {
|
|
9020
|
-
throw new Error('Parameter "initiative" must be an object');
|
|
9021
|
-
}
|
|
9022
|
-
const initiativeObj = rawInitiative;
|
|
9023
|
-
if (typeof initiativeObj["hasChanges"] !== "boolean") {
|
|
9024
|
-
throw new Error('Parameter "initiative.hasChanges" must be a boolean');
|
|
9025
|
-
}
|
|
9026
|
-
const initiative = {
|
|
9027
|
-
hasChanges: initiativeObj["hasChanges"],
|
|
9028
|
-
updatedContent: typeof initiativeObj["updatedContent"] === "string" ? initiativeObj["updatedContent"] : void 0
|
|
9029
|
-
};
|
|
9030
|
-
const result = await client.submitEvaluation({
|
|
9031
|
-
taskId,
|
|
9032
|
-
reasoning,
|
|
9033
|
-
globalWoW,
|
|
9034
|
-
initiative
|
|
9035
|
-
});
|
|
9036
|
-
const parts = [];
|
|
9037
|
-
if (globalWoW.hasChanges) {
|
|
9038
|
-
parts.push(`global WoW \u2192 v${result.globalVersion}`);
|
|
9039
|
-
}
|
|
9040
|
-
if (initiative.hasChanges && result.initiativeVersion !== void 0) {
|
|
9041
|
-
parts.push(`initiative \u2192 v${result.initiativeVersion}`);
|
|
9042
|
-
}
|
|
9043
|
-
const messageContent = parts.length > 0 ? `\u{1F4DD} Updated ${parts.join(", ")}: ${reasoning}` : `\u{1F4DD} No updates to ways of working: ${reasoning}`;
|
|
9044
|
-
await client.sendMessage({ content: messageContent, taskId }).catch(() => {
|
|
9045
|
-
});
|
|
9046
|
-
const summaryParts = [];
|
|
9047
|
-
if (globalWoW.hasChanges) summaryParts.push(`global WoW v${result.globalVersion}`);
|
|
9048
|
-
if (initiative.hasChanges && result.initiativeVersion !== void 0) {
|
|
9049
|
-
summaryParts.push(`initiative v${result.initiativeVersion}`);
|
|
9050
|
-
}
|
|
9051
|
-
return formatConfirmation(
|
|
9052
|
-
summaryParts.length > 0 ? `Evaluation complete \u2014 updated ${summaryParts.join(", ")}` : "Evaluation complete \u2014 no changes needed",
|
|
9053
|
-
["Use deskfree_state to check for other pending evaluations"],
|
|
9054
|
-
result
|
|
9055
|
-
);
|
|
9056
|
-
} catch (err) {
|
|
9057
|
-
return errorResult(err);
|
|
9058
|
-
}
|
|
9059
|
-
}
|
|
9107
|
+
...ORCHESTRATOR_TOOLS.PROPOSE,
|
|
9108
|
+
execute: makeProposeHandler(client)
|
|
9060
9109
|
}
|
|
9061
9110
|
];
|
|
9062
9111
|
}
|
|
@@ -9177,8 +9226,8 @@ var plugin = {
|
|
|
9177
9226
|
api.registerTool(() => {
|
|
9178
9227
|
return createOrchestratorTools(api);
|
|
9179
9228
|
});
|
|
9180
|
-
api.on("before_agent_start", (_event,
|
|
9181
|
-
return { prependContext: getDeskFreeContext() };
|
|
9229
|
+
api.on("before_agent_start", (_event, ctx) => {
|
|
9230
|
+
return { prependContext: getDeskFreeContext(ctx.sessionKey) };
|
|
9182
9231
|
});
|
|
9183
9232
|
}
|
|
9184
9233
|
};
|