feat: 階段 6 工具四待辦事項(全域待辦、類型自訂、到點提醒、確認後執行並回報)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ import { layout, meter, parseFields, remainColor, SEPARATOR, usedColor } from '.
|
||||
import type { Segment } from './statusline'
|
||||
import { CACHE_MS, addEntry, commandName, commitMessage, pageName, parseCatalog, parseWriteArgs, renderHome, renderPage, renderSidebar, teaToken, wikiUrlFromRemote } from './wiki'
|
||||
import type { Entry } from './wiki'
|
||||
import { DEFAULT_TYPES, RUN_SYSTEM, TODO_COMMAND, TODO_KEY, TYPES_KEY, USAGE, dueNow, nextId, parseArgs as parseTodo, reminderText, renderList } from './todo'
|
||||
import type { Todo } from './todo'
|
||||
import { NEED_ACTIVE, TOOL_COMMAND, registerTools, runSkillTool } from './skilltool'
|
||||
|
||||
const ACTIVE_KEY = 'active'
|
||||
@@ -169,6 +171,64 @@ async function readWiki($: Engine, cfg: WikiCfg, name: string): Promise<string |
|
||||
return await $.fs.read(`${w.dir}/${pageName(e)}.md`).catch(() => undefined)
|
||||
}
|
||||
|
||||
async function todoRun($: Engine, name: string, persona: string, args: string): Promise<string> {
|
||||
const todos = (((await $.store.get(TODO_KEY)) as Todo[] | undefined) ?? []).slice()
|
||||
const types = ((await $.store.get(TYPES_KEY)) as string[] | undefined) ?? DEFAULT_TYPES
|
||||
const p = parseTodo(args)
|
||||
if (!p) return USAGE
|
||||
const save = () => $.store.set(TODO_KEY, todos)
|
||||
const find = (id: number) => todos.find(t => t.id === id)
|
||||
if (p.op === 'list') return renderList(todos)
|
||||
if (p.op === 'type') {
|
||||
if (types.includes(p.name)) return `類型「${p.name}」已存在。`
|
||||
await $.store.set(TYPES_KEY, [...types, p.name])
|
||||
return `已新增類型「${p.name}」。`
|
||||
}
|
||||
if (p.op === 'add' || p.op === 'update') {
|
||||
if (!types.includes(p.type)) return `沒有「${p.type}」這個類型,現有:${types.join('、')}。可用 type 新增。`
|
||||
const project = (await $.session.cwd().catch(() => '')) || ''
|
||||
if (p.op === 'add') {
|
||||
const t: Todo = { id: nextId(todos), type: p.type, at: p.at, task: p.task, project, status: 'pending' }
|
||||
todos.push(t)
|
||||
await save()
|
||||
return `已建立待辦 #${t.id}。`
|
||||
}
|
||||
const t = find(p.id)
|
||||
if (!t) return `找不到待辦 #${p.id}。`
|
||||
Object.assign(t, { type: p.type, at: p.at, task: p.task, status: 'pending', result: undefined })
|
||||
await save()
|
||||
return `已更新待辦 #${t.id}。`
|
||||
}
|
||||
const t = find(p.id)
|
||||
if (!t) return `找不到待辦 #${p.id}。`
|
||||
if (t.status === 'done') return `待辦 #${t.id} 已經完成。`
|
||||
if (p.op === 'done') {
|
||||
t.status = 'done'
|
||||
await save()
|
||||
return `待辦 #${t.id} 已標記完成。`
|
||||
}
|
||||
// 使用者輸入 run 即為確認;執行結果由角色告知。
|
||||
const res = await $.model
|
||||
.complete({ model: 'sonnet', prompt: `[${t.type}] ${t.task}`, system: `${RUN_SYSTEM}\n角色:${name}。${persona}`, maxTokens: 2000, timeoutMs: 120000 })
|
||||
.catch(() => undefined)
|
||||
if (!res || !res.isAnswered || !res.text.trim()) return `待辦 #${t.id} 執行失敗,仍保留為待辦。`
|
||||
t.status = 'done'
|
||||
t.result = res.text.trim()
|
||||
await save()
|
||||
return withPrefix(name, t.result)
|
||||
}
|
||||
|
||||
async function remindDue($: Engine, name: string): Promise<void> {
|
||||
const todos = ((await $.store.get(TODO_KEY)) as Todo[] | undefined) ?? []
|
||||
const due = dueNow(todos, await $.clock.now())
|
||||
if (!due.length) return
|
||||
for (const t of due) {
|
||||
t.status = 'reminded'
|
||||
$.ui.log(withPrefix(name, reminderText(t)))
|
||||
}
|
||||
await $.store.set(TODO_KEY, todos)
|
||||
}
|
||||
|
||||
const affinityKey = (slug: string): string => `affinity:${slug}`
|
||||
async function loadAffinity($: Engine, slug: string): Promise<Affinity> {
|
||||
const v = (await $.store.get(affinityKey(slug))) as Affinity | undefined
|
||||
@@ -229,6 +289,8 @@ export const register: Register = (on, options) => {
|
||||
await registerTools($)
|
||||
}
|
||||
if (active) await registerWikiTools($, wikiCfg(), wikiCommands)
|
||||
const ch = findCharacter(active)
|
||||
if (ch && flag('todoRemind', true)) await remindDue($, ch.name)
|
||||
}
|
||||
return next(e)
|
||||
})
|
||||
@@ -244,6 +306,10 @@ export const register: Register = (on, options) => {
|
||||
if (e.command === TOOL_COMMAND) {
|
||||
return { text: findCharacter(active) ? await runSkillTool($, e.args) : NEED_ACTIVE }
|
||||
}
|
||||
if (e.command === TODO_COMMAND) {
|
||||
const c = findCharacter(active)
|
||||
return { text: c ? await todoRun($, c.name, c.persona, String(e.args ?? '')) : NEED_ACTIVE }
|
||||
}
|
||||
if (e.command === AFFINITY_COMMAND) {
|
||||
const c = findCharacter(active)
|
||||
if (!c) return { text: '請先啟動角色。' }
|
||||
@@ -297,6 +363,7 @@ export const register: Register = (on, options) => {
|
||||
|
||||
on('prompt.submit', async ($, e, next) => {
|
||||
const c = isEnabled ? findCharacter(active) : undefined
|
||||
if (c && flag('todoRemind', true)) await remindDue($, c.name)
|
||||
if (!c || e.command || e.isMeta) return next(e)
|
||||
prefixed = false
|
||||
const axes = applyOverrides(c.axes, overrides())
|
||||
|
||||
Reference in New Issue
Block a user