Files
2026-11-12 15:29:12 +08:00

74 lines
3.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { join } from 'node:path';
import type { Runtime } from '../runtime.ts';
import { baseTools } from '../tools.ts';
import type { Evidence } from '../tools.ts';
import type { Answer, Question, ScenarioInfo } from '../types.ts';
import { collect, evidenceRules, invoke, questionSpec, summarize } from './shared.ts';
export const asynchronous: ScenarioInfo = {
id: 'async',
task: '整理本周研发周报。先问我下周优先推进什么,等待期间继续收集本周工作。归纳候选后让我选择保留项,最后写成周报。',
system: `${evidenceRules}
question 先返回 accepted 和 question_id。真实用户答复在随后的 user 消息中,带 question_id。
先询问下周优先事项,在等待时继续收集本周证据。核对提交和相关产物后,调用第二个 question 让讲者多选。
候选 2 至 5 项,每项用约 30 至 50 字写业务项目和可核对的具体工作,不写路径和提交哈希,勿把只有会话或文件名的项目列为成果。
只写讲者选中的工作项,按讲者答复写下周计划。`,
tools: [...baseTools(), questionSpec(true)],
};
async function ask(loop: Runtime, kind: Question['kind']) {
const instruction = kind === 'priority'
? '调用 question,question 为「下周优先推进什么?」,options 传空数组。'
: '调用 question,question 为「本周保留哪些工作项?」。options 数组填入 2 至 5 条有证据的简洁工作项。';
let opened: Question | undefined;
await invoke(loop, 'question', instruction, (call) => loop.runTool(call, (args) => {
const { question } = loop.openQuestion(call.id, 'async', kind, args);
opened = question;
return { ref: question.id, result: JSON.stringify({ accepted: true, question_id: question.id }) };
}));
return opened!;
}
// stage:start
export async function runAsync(loop: Runtime) {
const priority = await ask(loop, 'priority');
const evidence = await collect(loop); // 提问后立即取证
await verifyArtifacts(loop, evidence);
const items = await ask(loop, 'selection');
const plan = await loop.waitForAnswer(priority.id);
const picked = await loop.waitForAnswer(items.id);
await writeReport(loop, evidence, items, plan, picked);
return summarize(loop, '一句话汇报周报写入结果。');
}
// stage:end
async function verifyArtifacts(loop: Runtime, evidence: Evidence) {
const artifact = evidence.repositories.flatMap((repo) => repo.artifacts)
.find((item) => item.path === 'research/async-tool-calling-survey.md');
if (artifact) {
const path = join(artifact.cwd, artifact.path);
await invoke(loop, 'grep', `用 grep 搜索该调研中的「交付」,path 为 ${JSON.stringify(path)}。`);
await invoke(loop, 'read', `读取该调研前 25 行,path 为 ${JSON.stringify(path)}。`);
}
}
async function writeReport(loop: Runtime, evidence: Evidence, items: Question, plan: Answer, picked: Answer) {
if (!('text' in plan) || !('selected' in picked)) throw new Error('两项周报决策尚未齐备。');
const bullets = picked.selected.map((index) => `- ${items.options[index]}`).join('\n');
const { from, through } = evidence.window;
const markdown = [
'# 研发周报', '',
`统计范围 ${from.slice(0, 10)} 至 ${through.slice(0, 16).replace('T', ' ')}(上海时间)`, '',
'## 本周工作内容', '', bullets || '讲者未选择本周工作项。', '',
'## 下周工作计划', '', `- ${plan.text}`, '',
].join('\n');
await invoke(loop, 'write', `调用 write,path 为 weekly-report.md,content 逐字使用以下正文:\n${markdown}`,
(call) => loop.runTool(call, async (args) => {
const result = await loop.tools.write(args, markdown);
loop.emit({ type: 'report.written', ...result });
return { result: JSON.stringify({ path: 'weekly-report.md', bytes: Buffer.byteLength(result.markdown) }) };
}));
}