74 lines
3.8 KiB
TypeScript
74 lines
3.8 KiB
TypeScript
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) }) };
|
||
}));
|
||
}
|