From f426ba417e68a70b5bb0402fbaae40f1103cf2f1 Mon Sep 17 00:00:00 2001 From: elfcv Date: Sun, 1 Nov 2026 03:00:10 +0800 Subject: [PATCH 1/2] fix(scheduler): reuse one conversation for agent-created check schedules schedule_create with silentWhenIdle=true and no binding used to start a fresh task on every fire, filling the sidebar with empty no-change conversations. When the caller leaves persistentSession unset and binds no session, the tool layer now defaults it to true. Explicit false, bound targets, reminders and script schedules are unchanged; engine and UI defaults are unchanged. Signed-off-by: elfcv --- docs/product-rules/bot-routines.md | 4 ++++ .../cindy_schedulerMcpServer.test.ts | 20 +++++++++++++++++++ packages/lizi-mcps/src/scheduler/create.ts | 15 ++++++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/product-rules/bot-routines.md b/docs/product-rules/bot-routines.md index c0601a8bd62..f4f33e4ac02 100644 --- a/docs/product-rules/bot-routines.md +++ b/docs/product-rules/bot-routines.md @@ -249,6 +249,10 @@ Pi 将 routine_list、routine_save、routine_sources、routine_history、routine 明确判定为检查型的新建 agent 自动化由创建入口显式启用“检查没事不说话”; 未分类的通用创建入口和仅执行脚本的任务保持原通知行为,避免吞掉提醒或定时发送。 +Agent 经 `schedule_create` 新建的检查(`silentWhenIdle=true`)若既未绑定会话、也未表态 +`persistentSession`,工具层补成持续会话,后续检查复用同一任务,不再每次在侧栏新建一条 +没有变化的对话(2026-11-01);显式 `persistentSession=false`、绑定已有任务、提醒类与仅脚本任务不变, +引擎与 UI 的默认值不变。 伙伴例行任务沿用原本的静默通知默认值,并把选择和可选 `preRunHook` 暴露到 同一份 routine 定义与桌面高级设置。显式提醒/定时发送应设置 `silentWhenIdle=false`。没有批量迁移旧规则:普通旧调度保留已存选择,旧伙伴 diff --git a/packages/lizi-mcps/src/__tests__/cindy_schedulerMcpServer.test.ts b/packages/lizi-mcps/src/__tests__/cindy_schedulerMcpServer.test.ts index 6e21d892275..85d5975e60b 100644 --- a/packages/lizi-mcps/src/__tests__/cindy_schedulerMcpServer.test.ts +++ b/packages/lizi-mcps/src/__tests__/cindy_schedulerMcpServer.test.ts @@ -370,6 +370,26 @@ describe('cindy_scheduler MCP server (in-process smoke)', () => { await h.cleanup(); }); + it('call_tool(schedule_create) reuses one conversation for unbound checks but keeps reminders and explicit choices', async () => { + const create = async (extra: Record) => { + const result = await h.client.callTool({ + name: 'call_tool', + arguments: { name: 'schedule_create', args: { ...baseCreate, ...extra } as Record }, + }); + const { envelope } = parseToolResult(result as { content: unknown[]; isError?: boolean }); + if (!envelope.ok) throw new Error(`create failed: ${JSON.stringify(envelope)}`); + return envelope.data as Schedule; + }; + + expect((await create({ silentWhenIdle: true })).persistentSession).toBe(true); + expect((await create({ silentWhenIdle: true, persistentSession: false })).persistentSession).toBe(false); + expect((await create({ silentWhenIdle: true, targetSessionId: 'sess-bound' })).persistentSession).toBe(false); + expect((await create({})).persistentSession).toBe(false); + expect((await create({ silentWhenIdle: false })).persistentSession).toBe(false); + + await h.cleanup(); + }); + it('call_tool(schedule_create) accepts agentKind: pi as a first-class scheduled agent', async () => { const created = await h.client.callTool({ name: 'call_tool', diff --git a/packages/lizi-mcps/src/scheduler/create.ts b/packages/lizi-mcps/src/scheduler/create.ts index 716861264b1..7e7a9bc9201 100644 --- a/packages/lizi-mcps/src/scheduler/create.ts +++ b/packages/lizi-mcps/src/scheduler/create.ts @@ -40,7 +40,7 @@ export function registerScheduleCreateTool( '(4) heartbeat 模式(注入到已存 session):**要跟进当前这条对话就传 bindToCurrentSession=true**(代码自动绑定本会话,无需也不要自己去查 / 传 session id);只有要绑到另一条已知 session 时才显式传 targetSessionId。heartbeat 模式不传 workingDir;model / providerId / effort 留空沿用绑定会话,传入则改用;' + '(5) 默认 useWorktree=false / notify={desktop:true, feishu:false};' + '(6) 删除已有调度前先用 schedule_get / schedule_list 核对目标和授权;本次自动跟进达到既定停止条件时,可自行停止和清理自身调度,不影响其他调度;' + - '(7) 确定是检查时显式设 silentWhenIdle=true(无变化不播报);提醒/定时发送设 false,保留用户选择。省略此字段会按 false 处理,避免旧调用方的提醒被吞。如果用户定义了提醒条件,把条件保留在 prompt 里(如 CI 失败/新评论时提醒),有结果时用 schedule_notify_current_run 主动上报;' + + '(7) 确定是检查时显式设 silentWhenIdle=true(无变化不播报),未绑定会话且未设 persistentSession 时自动复用同一会话,不为每次检查新建任务;提醒/定时发送设 false,每次仍新建,保留用户选择。省略此字段会按 false 处理,避免旧调用方的提醒被吞。如果用户定义了提醒条件,把条件保留在 prompt 里(如 CI 失败/新评论时提醒),有结果时用 schedule_notify_current_run 主动上报;' + '(8) 轮询型任务("有新 PR 才 review / CI 挂了才处理")优先配 preRunHook 前置检查(exit 0=放行、exit 2=跳过本轮不烧 token;报错/超时 fail-closed 阻止本轮并记录失败)。**脚本一律经 schedule_set_pre_run_hook 创建/修改**(落盘路径、协议、自测由宿主代码统一保证),不要自己写脚本文件再手填 preRunHook.command——先调它拿 command 再来创建,或创建后传 scheduleId 让它直接挂载;' + '(9) 既有工作流程中的自动跟进,沿用该流程明确的频率、执行引擎、静默与通知默认值;用户在线也不需要为这些默认值另行确认。用户主动新建独立任务时,若关键实体或会实质改变目标的参数缺失,先询问,不盲猜;用户已明确说明或已有流程定义的参数不重复问。' + '(10) **仅运行脚本模式(零 token)**:executionMode="script" 时不启动 agent,宿主直接执行 scriptConfig.command(cwd=workingDir,必填本地项目目录);脚本经 stdout/stdin 的 cindy-script/1 JSONL 协议回调宿主受限能力,能力按 scriptConfig.capabilities 白名单授予(默认全拒)。script 模式不需要 prompt(可省),不支持 useWorktree / persistentSession。单任务跟进应传 bindToCurrentSession=true 或 targetSessionId:它是脚本的生命周期绑定及唯一投递目标,普通目标归档后会像用户发消息一样恢复原任务,完成后保持激活;目标删除或不存在时自动暂停,恢复任务不会自动恢复已暂停的调度。未绑定脚本保留独立任务语义。与 preRunHook 的分工:preRunHook 只是"要不要跑"的闸门,script 模式是"任务本体就是脚本"。', @@ -141,7 +141,7 @@ export function registerScheduleCreateTool( persistentSession: z .boolean() .optional() - .describe('true → 第一次 fire 后自动把新建 session 的 id 回写到 targetSessionId,后续 fire 持续在同一 session 中运行(拥有完整上下文)。默认 false。'), + .describe('true → 第一次 fire 后自动把新建 session 的 id 回写到 targetSessionId,后续 fire 持续在同一 session 中运行(拥有完整上下文)。默认 false;silentWhenIdle=true 的检查任务未显式设置且未绑定会话时按 true 处理,显式传 false 则保持每次新建。'), silentWhenIdle: z .boolean() .optional() @@ -222,6 +222,17 @@ export function registerScheduleCreateTool( } input = { ...input, targetSessionId: sessionId }; } + // 检查类(silentWhenIdle=true)没有显式绑定时默认复用同一个会话:每次触发都新建任务会让 + // 没变化的检查在侧栏刷出一串空对话。只在调用方没有表态时补默认,显式 false/绑定一律保留; + // 提醒类(silentWhenIdle 缺省/false)不受影响。script 模式不支持 persistentSession,同样跳过。 + if ( + input.silentWhenIdle === true && + input.persistentSession === undefined && + !input.targetSessionId?.trim() && + (input.executionMode ?? 'agent') === 'agent' + ) { + input = { ...input, persistentSession: true }; + } if (input.preRunHook?.command?.trim()) { if (!deps.hookScript?.stabilizeCommand) { throw new Error( From 3564888c98cff8ae4d07e8cb9e09464a16c7b924 Mon Sep 17 00:00:00 2001 From: elfcv Date: Sun, 1 Nov 2026 03:34:13 +0800 Subject: [PATCH 2/2] fix(scheduler): keep local-location-only checks creatable A persistent session with modelAgentDeviceId='' must also carry an explicit Harness and model (engine validation). Do not default persistentSession in that case, so such check requests keep working. Signed-off-by: elfcv --- .../lizi-mcps/src/__tests__/cindy_schedulerMcpServer.test.ts | 1 + packages/lizi-mcps/src/scheduler/create.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/lizi-mcps/src/__tests__/cindy_schedulerMcpServer.test.ts b/packages/lizi-mcps/src/__tests__/cindy_schedulerMcpServer.test.ts index 85d5975e60b..4eb70ab1056 100644 --- a/packages/lizi-mcps/src/__tests__/cindy_schedulerMcpServer.test.ts +++ b/packages/lizi-mcps/src/__tests__/cindy_schedulerMcpServer.test.ts @@ -384,6 +384,7 @@ describe('cindy_scheduler MCP server (in-process smoke)', () => { expect((await create({ silentWhenIdle: true })).persistentSession).toBe(true); expect((await create({ silentWhenIdle: true, persistentSession: false })).persistentSession).toBe(false); expect((await create({ silentWhenIdle: true, targetSessionId: 'sess-bound' })).persistentSession).toBe(false); + expect((await create({ silentWhenIdle: true, modelAgentDeviceId: '', model: 'gpt-5.5' })).persistentSession).toBe(false); expect((await create({})).persistentSession).toBe(false); expect((await create({ silentWhenIdle: false })).persistentSession).toBe(false); diff --git a/packages/lizi-mcps/src/scheduler/create.ts b/packages/lizi-mcps/src/scheduler/create.ts index d52605b3f4d..cec77682b0c 100644 --- a/packages/lizi-mcps/src/scheduler/create.ts +++ b/packages/lizi-mcps/src/scheduler/create.ts @@ -233,11 +233,13 @@ export function registerScheduleCreateTool( // 检查类(silentWhenIdle=true)没有显式绑定时默认复用同一个会话:每次触发都新建任务会让 // 没变化的检查在侧栏刷出一串空对话。只在调用方没有表态时补默认,显式 false/绑定一律保留; // 提醒类(silentWhenIdle 缺省/false)不受影响。script 模式不支持 persistentSession,同样跳过。 + // 持续会话下「本机位置」须带完整 Harness+model(引擎校验);只给了本机位置时不补,保持原来可创建。 if ( input.silentWhenIdle === true && input.persistentSession === undefined && !input.targetSessionId?.trim() && - (input.executionMode ?? 'agent') === 'agent' + (input.executionMode ?? 'agent') === 'agent' && + !(input.modelAgentDeviceId === '' && (!input.modelAgentKind || !input.model?.trim())) ) { input = { ...input, persistentSession: true }; }