Refine discussion closure messaging
This commit is contained in:
@@ -255,10 +255,11 @@ After callback:
|
|||||||
[Discussion Idle]
|
[Discussion Idle]
|
||||||
|
|
||||||
No agent responded in the latest discussion round.
|
No agent responded in the latest discussion round.
|
||||||
If the discussion goal has been achieved, the initiator should now:
|
If the discussion goal has already been achieved, the initiator should now:
|
||||||
1. write the discussion summary to a file in the workspace
|
1. write the discussion summary to a file in the workspace
|
||||||
2. call discuss-callback with the summary file path
|
2. call discuss-callback with the summary file path
|
||||||
|
|
||||||
|
This reminder does not mean the discussion was automatically summarized or closed.
|
||||||
If more discussion is still needed, continue the discussion in this channel.
|
If more discussion is still needed, continue the discussion in this channel.
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -284,6 +285,7 @@ If more discussion is still needed, continue the discussion in this channel.
|
|||||||
This discussion channel has been closed.
|
This discussion channel has been closed.
|
||||||
It is now kept for archive/reference only.
|
It is now kept for archive/reference only.
|
||||||
Further discussion in this channel is ignored.
|
Further discussion in this channel is ignored.
|
||||||
|
If follow-up work is needed, continue it from the origin work channel instead.
|
||||||
```
|
```
|
||||||
|
|
||||||
这部分实现明确采用已有“在指定 session 上临时覆盖为 no-reply 模型”的方式,而不是修改 Agent 的全局默认模型。
|
这部分实现明确采用已有“在指定 session 上临时覆盖为 no-reply 模型”的方式,而不是修改 Agent 的全局默认模型。
|
||||||
|
|||||||
@@ -152,9 +152,9 @@
|
|||||||
- [x] 模板中明确 `discuss-callback(summaryPath)` 调用要求
|
- [x] 模板中明确 `discuss-callback(summaryPath)` 调用要求
|
||||||
|
|
||||||
#### A10.2 idle reminder
|
#### A10.2 idle reminder
|
||||||
- [. ] 定稿 discussion idle 模板
|
- [x] 定稿 discussion idle 模板
|
||||||
- [. ] 模板中提醒 initiator:写总结文件并 callback
|
- [x] 模板中提醒 initiator:写总结文件并 callback
|
||||||
- [. ] 避免提醒文案歧义或像自动总结器
|
- [x] 避免提醒文案歧义或像自动总结器
|
||||||
|
|
||||||
#### A10.3 origin callback message
|
#### A10.3 origin callback message
|
||||||
- [x] 定稿发回原工作 channel 的结果通知模板
|
- [x] 定稿发回原工作 channel 的结果通知模板
|
||||||
@@ -163,8 +163,8 @@
|
|||||||
- [x] 模板中明确“继续基于该总结文件推进原任务”
|
- [x] 模板中明确“继续基于该总结文件推进原任务”
|
||||||
|
|
||||||
#### A10.4 closed reply
|
#### A10.4 closed reply
|
||||||
- [. ] 定稿 closed channel 固定回复模板
|
- [x] 定稿 closed channel 固定回复模板
|
||||||
- [. ] 明确 channel 已关闭,仅做留档使用
|
- [x] 明确 channel 已关闭,仅做留档使用
|
||||||
|
|
||||||
### A11. `discuss-callback` 详细校验任务
|
### A11. `discuss-callback` 详细校验任务
|
||||||
- [x] 校验当前 channel 必须是 discussion channel
|
- [x] 校验当前 channel 必须是 discussion channel
|
||||||
@@ -212,9 +212,9 @@
|
|||||||
- [x] 测试绝对路径越界失败
|
- [x] 测试绝对路径越界失败
|
||||||
|
|
||||||
#### A13.5 回调链路测试
|
#### A13.5 回调链路测试
|
||||||
- [. ] 测试 callback 成功后 moderator 在 origin channel 发出通知
|
- [x] 测试 callback 成功后 moderator 在 origin channel 发出通知
|
||||||
- [. ] 测试 origin channel 收到路径后能继续原工作流
|
- [ ] 测试 origin channel 收到路径后能继续原工作流
|
||||||
- [. ] 测试 discussion channel 后续只保留留档行为
|
- [x] 测试 discussion channel 后续只保留留档行为
|
||||||
|
|
||||||
#### A13.6 文档交付
|
#### A13.6 文档交付
|
||||||
- [. ] 根据最终代码实现更新 `plans/CSM.md`
|
- [. ] 根据最终代码实现更新 `plans/CSM.md`
|
||||||
|
|||||||
@@ -31,10 +31,11 @@ export function buildDiscussionIdleReminderMessage(): string {
|
|||||||
"[Discussion Idle]",
|
"[Discussion Idle]",
|
||||||
"",
|
"",
|
||||||
"No agent responded in the latest discussion round.",
|
"No agent responded in the latest discussion round.",
|
||||||
"If the discussion goal has been achieved, the initiator should now:",
|
"If the discussion goal has already been achieved, the initiator should now:",
|
||||||
"1. write the discussion summary to a file in the workspace",
|
"1. write the discussion summary to a file in the workspace",
|
||||||
"2. call discuss-callback with the summary file path",
|
"2. call discuss-callback with the summary file path",
|
||||||
"",
|
"",
|
||||||
|
"This reminder does not mean the discussion was automatically summarized or closed.",
|
||||||
"If more discussion is still needed, continue the discussion in this channel.",
|
"If more discussion is still needed, continue the discussion in this channel.",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
@@ -46,6 +47,7 @@ export function buildDiscussionClosedMessage(): string {
|
|||||||
"This discussion channel has been closed.",
|
"This discussion channel has been closed.",
|
||||||
"It is now kept for archive/reference only.",
|
"It is now kept for archive/reference only.",
|
||||||
"Further discussion in this channel is ignored.",
|
"Further discussion in this channel is ignored.",
|
||||||
|
"If follow-up work is needed, continue it from the origin work channel instead.",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import os from 'node:os';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { createDiscussionService } from '../plugin/core/discussion-service.ts';
|
import { createDiscussionService } from '../plugin/core/discussion-service.ts';
|
||||||
|
import { buildDiscussionClosedMessage, buildDiscussionOriginCallbackMessage } from '../plugin/core/discussion-messages.ts';
|
||||||
|
|
||||||
function makeLogger() {
|
function makeLogger() {
|
||||||
return {
|
return {
|
||||||
@@ -244,3 +245,100 @@ test('handleCallback rejects an absolute path outside the initiator workspace',
|
|||||||
/summaryPath must stay inside the initiator workspace/,
|
/summaryPath must stay inside the initiator workspace/,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('handleCallback notifies the origin channel with the resolved summary path', async () => {
|
||||||
|
const workspace = makeWorkspace();
|
||||||
|
const summaryRelPath = path.join('plans', 'discussion-summary.md');
|
||||||
|
const summaryAbsPath = path.join(workspace, summaryRelPath);
|
||||||
|
fs.mkdirSync(path.dirname(summaryAbsPath), { recursive: true });
|
||||||
|
fs.writeFileSync(summaryAbsPath, '# done\n');
|
||||||
|
|
||||||
|
const fetchCalls: Array<{ url: string; body: any }> = [];
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
|
||||||
|
const body = init?.body ? JSON.parse(String(init.body)) : undefined;
|
||||||
|
fetchCalls.push({ url: String(url), body });
|
||||||
|
return new Response(JSON.stringify({ id: `msg-${fetchCalls.length}` }), { status: 200 });
|
||||||
|
}) as typeof fetch;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const service = createDiscussionService({
|
||||||
|
api: makeApi() as any,
|
||||||
|
workspaceRoot: workspace,
|
||||||
|
moderatorBotToken: 'bot-token',
|
||||||
|
forceNoReplyForSession: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await service.initDiscussion({
|
||||||
|
discussionChannelId: 'discussion-origin-1',
|
||||||
|
originChannelId: 'origin-8',
|
||||||
|
initiatorAgentId: 'agent-theta',
|
||||||
|
initiatorSessionId: 'session-theta',
|
||||||
|
discussGuide: 'Notify the origin channel.',
|
||||||
|
});
|
||||||
|
|
||||||
|
await service.handleCallback({
|
||||||
|
channelId: 'discussion-origin-1',
|
||||||
|
summaryPath: summaryRelPath,
|
||||||
|
callerAgentId: 'agent-theta',
|
||||||
|
callerSessionKey: 'session-theta',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(fetchCalls.length, 2);
|
||||||
|
assert.equal(fetchCalls[1]?.url, 'https://discord.com/api/v10/channels/origin-8/messages');
|
||||||
|
assert.equal(
|
||||||
|
fetchCalls[1]?.body?.content,
|
||||||
|
buildDiscussionOriginCallbackMessage(fs.realpathSync.native(summaryAbsPath), 'discussion-origin-1'),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('maybeReplyClosedChannel sends the archive-only closed message for later channel activity', async () => {
|
||||||
|
const workspace = makeWorkspace();
|
||||||
|
const summaryRelPath = 'summary.md';
|
||||||
|
const summaryAbsPath = path.join(workspace, summaryRelPath);
|
||||||
|
fs.writeFileSync(summaryAbsPath, 'closed\n');
|
||||||
|
|
||||||
|
const fetchCalls: Array<{ url: string; body: any }> = [];
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
|
||||||
|
const body = init?.body ? JSON.parse(String(init.body)) : undefined;
|
||||||
|
fetchCalls.push({ url: String(url), body });
|
||||||
|
return new Response(JSON.stringify({ id: `msg-${fetchCalls.length}` }), { status: 200 });
|
||||||
|
}) as typeof fetch;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const service = createDiscussionService({
|
||||||
|
api: makeApi() as any,
|
||||||
|
workspaceRoot: workspace,
|
||||||
|
moderatorBotToken: 'bot-token',
|
||||||
|
moderatorUserId: 'moderator-user',
|
||||||
|
forceNoReplyForSession: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await service.initDiscussion({
|
||||||
|
discussionChannelId: 'discussion-closed-1',
|
||||||
|
originChannelId: 'origin-9',
|
||||||
|
initiatorAgentId: 'agent-iota',
|
||||||
|
initiatorSessionId: 'session-iota',
|
||||||
|
discussGuide: 'Close and archive.',
|
||||||
|
});
|
||||||
|
|
||||||
|
await service.handleCallback({
|
||||||
|
channelId: 'discussion-closed-1',
|
||||||
|
summaryPath: summaryRelPath,
|
||||||
|
callerAgentId: 'agent-iota',
|
||||||
|
callerSessionKey: 'session-iota',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handled = await service.maybeReplyClosedChannel('discussion-closed-1', 'human-user');
|
||||||
|
assert.equal(handled, true);
|
||||||
|
assert.equal(fetchCalls.length, 3);
|
||||||
|
assert.equal(fetchCalls[2]?.url, 'https://discord.com/api/v10/channels/discussion-closed-1/messages');
|
||||||
|
assert.equal(fetchCalls[2]?.body?.content, buildDiscussionClosedMessage());
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user