Q10: wake tasks no longer carry a truncated copy of the message
Manual re-runs of a chat wake answer the same thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
abe9534350
commit
31ab919334
4 files changed
+53
-6
No files matched your search
@@ -130,7 +130,7 @@ export class ChatAgentWakeService {
|
||||
const taskId = this.organization.createTask({
|
||||
orgId: input.orgId,
|
||||
title: direct ? `Reply to ${from}` : `Reply to ${from} in #${space.name}`,
|
||||
description: this.mentionTaskDescription(input, from, space.name, direct),
|
||||
description: this.mentionTaskDescription(from, space.name, direct),
|
||||
status: TASK_STATUS.BACKLOG,
|
||||
assigneeAgentId: agent.id,
|
||||
risk: RISK_LEVEL.LOW,
|
||||
@@ -296,15 +296,15 @@ export class ChatAgentWakeService {
|
||||
return `Chat mention: ${title.length > 64 ? `${title.slice(0, 61)}...` : title}`;
|
||||
}
|
||||
|
||||
private mentionTaskDescription(input: SendMessageInput, from: string, channel: string, direct: boolean): string {
|
||||
const said = input.bodyMd.trim();
|
||||
const where = direct ? `${from} messaged you directly:` : `${from} addressed you in #${channel}:`;
|
||||
private mentionTaskDescription(from: string, channel: string, direct: boolean): string {
|
||||
const where = direct
|
||||
? `${from} messaged you directly; their full message is in the thread below.`
|
||||
: `${from} addressed you in #${channel}; their full message is in the thread below.`;
|
||||
const how = direct
|
||||
? `Reply to ${from} in your direct conversation: call send_message with to="${from}" and this thread_id.`
|
||||
: `Reply to ${from} in this thread.`;
|
||||
return [
|
||||
where,
|
||||
`"${said.length > 400 ? `${said.slice(0, 397)}...` : said}"`,
|
||||
"",
|
||||
`${how} Answer what they asked, give your honest take, push back if you disagree, or hand work over — speak as yourself, the way a colleague would, not as a status report.`,
|
||||
].join("\n");
|
||||
|
||||
@@ -665,10 +665,26 @@ export class TaskExecutionService {
|
||||
providerIds: [provider.id],
|
||||
scope: "task_run",
|
||||
credentialAccess: "provider_runtime_only",
|
||||
...this.chatWakeSource(orgId, task),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/** A re-run of a chat wake answers the same thread. */
|
||||
private chatWakeSource(orgId: string, task: TaskExecutionTaskRow): { sourceThreadId: string; sourceMessageId: string } | Record<string, never> {
|
||||
if (task.origin !== "chat_wake") return {};
|
||||
const row = this.db.prepare(`
|
||||
select json_extract(secret_scope_json, '$.sourceThreadId') as thread_id,
|
||||
json_extract(secret_scope_json, '$.sourceMessageId') as message_id
|
||||
from task_runs
|
||||
where task_id = ? and org_id = ? and json_extract(secret_scope_json, '$.sourceThreadId') is not null
|
||||
order by created_at desc
|
||||
limit 1
|
||||
`).get(task.id, orgId) as { thread_id: string; message_id: string } | undefined;
|
||||
if (!row) throw new Error(`Chat wake task ${task.id} has no recorded source thread.`);
|
||||
return { sourceThreadId: row.thread_id, sourceMessageId: row.message_id };
|
||||
}
|
||||
|
||||
private requireServiceIdentity(orgId: string, agentId: string): string {
|
||||
const row = this.db.prepare("select id from service_identities where org_id = ? and agent_id = ?")
|
||||
.get(orgId, agentId) as { id: string } | undefined;
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface TaskExecutionTaskRow {
|
||||
priority: string;
|
||||
requires_filesystem: number;
|
||||
handoff_json: string | null;
|
||||
origin: string | null;
|
||||
}
|
||||
|
||||
export interface TaskExecutionAgentRow {
|
||||
|
||||
@@ -66,7 +66,14 @@ async function founderLine() {
|
||||
chat.editMessage({ orgId, messageId: row.id, bodyMd: newBody, actorType: "employee", actorId: founderId });
|
||||
};
|
||||
const onWakeTurn = (hook: (turnIndex: number) => void) => { onWake = hook; };
|
||||
return { connection, wakePrompts, wakeInputs, say, runWake, edit, onWakeTurn };
|
||||
const runManually = async () => {
|
||||
const wake = connection.db.prepare(
|
||||
"select id, task_id from task_runs where org_id = ? and agent_id = ? and trigger = 'chat_mention' and state = 'queued'",
|
||||
).get(orgId, ceoId) as { id: string; task_id: string };
|
||||
connection.db.prepare("update task_runs set state = 'failed' where id = ?").run(wake.id);
|
||||
await execution.runTask({ orgId, taskId: wake.task_id });
|
||||
};
|
||||
return { connection, wakePrompts, wakeInputs, say, runWake, runManually, edit, onWakeTurn };
|
||||
}
|
||||
|
||||
describe("chat wake context", () => {
|
||||
@@ -194,4 +201,27 @@ describe("chat wake context", () => {
|
||||
expect(earlier).toContain("13 earlier messages in this thread are not shown.");
|
||||
h.connection.close();
|
||||
});
|
||||
|
||||
test("a long message reaches the run whole, and the task description does not truncate it", async () => {
|
||||
const h = await founderLine();
|
||||
const long = `Plan: ${"step ".repeat(200)}END-OF-PLAN`;
|
||||
h.say(long, null);
|
||||
await h.runWake();
|
||||
const prompt = h.wakePrompts[0];
|
||||
expect(prompt).toContain(long);
|
||||
expect(prompt).not.toContain("step ...");
|
||||
const description = (h.connection.db.prepare("select description from tasks where origin = 'chat_wake'").get() as { description: string }).description;
|
||||
expect(description).not.toContain("step ");
|
||||
expect(description).toContain("messaged you directly");
|
||||
h.connection.close();
|
||||
});
|
||||
|
||||
test("re-running a chat wake by hand answers the same thread", async () => {
|
||||
const h = await founderLine();
|
||||
h.say("Please draft the Q3 hiring plan.", null);
|
||||
await h.runManually();
|
||||
expect(h.wakePrompts).toHaveLength(1);
|
||||
expect(h.wakePrompts[0]).toContain("Please draft the Q3 hiring plan.");
|
||||
h.connection.close();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user