ai_co_discuss 0.2.70 → 0.2.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/discuss-host/SKILL.md +5 -1
- package/backend/free_room.py +70 -24
- package/backend/schema.sql +15 -0
- package/backend/server.py +44 -3
- package/docs/architecture.md +280 -279
- package/docs/cli.md +1 -1
- package/docs/runner.md +2 -1
- package/docs/workflow.md +155 -155
- package/package.json +1 -1
- package/runner/discussion_runner.mjs +1 -1
- package/runner/free_runner.mjs +39 -3
- package/runner/scheduler.mjs +48 -24
- package/web/app.js +3384 -3382
- package/web/modern.css +1 -1
- package/web/modern.js +4286 -4146
package/runner/scheduler.mjs
CHANGED
|
@@ -12,7 +12,7 @@ export const ROUTE_CONFIDENCE = 0.7;
|
|
|
12
12
|
/** Waking everyone or no one costs more when wrong (N runs, or an ignored message), so it needs more certainty. */
|
|
13
13
|
export const ROUTE_WIDE_CONFIDENCE = 0.85;
|
|
14
14
|
/** Bump when the routing question or its option descriptions change, so ratings can be compared across wordings. */
|
|
15
|
-
export const ROUTE_PROMPT_VERSION =
|
|
15
|
+
export const ROUTE_PROMPT_VERSION = 4;
|
|
16
16
|
const HOST_CHOICE = '__host__';
|
|
17
17
|
const NOTE_CHOICE = '__note__';
|
|
18
18
|
const ALL_CHOICE = '__all__';
|
|
@@ -95,6 +95,11 @@ function detail_of(questions, answers) {
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/** The shown detail plus the exact input (state and full questions), kept so a rated-bad decision can be reviewed later. */
|
|
99
|
+
function judged_of(state, questions, answers) {
|
|
100
|
+
return { detail: detail_of(questions, answers), input: { state, questions } };
|
|
101
|
+
}
|
|
102
|
+
|
|
98
103
|
/** Say which option the provider leaned to when it was not sure enough, so the transcript shows how close it was. */
|
|
99
104
|
function unsure_label(lean) {
|
|
100
105
|
return lean ? `倾向${lean},把握不足,交给主持人` : '拿不准,交给主持人';
|
|
@@ -108,8 +113,8 @@ function excerpt(message) {
|
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
/** The room's purpose in the user's words, so a decision can tell drifting from progress. */
|
|
111
|
-
function topic_lines(title, goal, user_request, { brief, focus, arrangement, progress } = {}) {
|
|
112
|
-
return [`房间:${title || ''}`, goal && `目标:${goal}`, user_request && `用户原文:${String(user_request).slice(0, MAX_MESSAGE_CHARS)}`,
|
|
116
|
+
function topic_lines(title, goal, user_request, { roster, brief, focus, arrangement, progress } = {}) {
|
|
117
|
+
return [`房间:${title || ''}`, roster && `房间成员(名字不区分大小写):${roster}`, goal && `目标:${goal}`, user_request && `用户原文:${String(user_request).slice(0, MAX_MESSAGE_CHARS)}`,
|
|
113
118
|
focus && `当前焦点:${focus}`, brief && `主持简报(含分工与规则):${String(brief).slice(0, MAX_BRIEF_CHARS)}`,
|
|
114
119
|
arrangement && `主持人最近的安排:${arrangement}`, progress].filter(Boolean).join('\n');
|
|
115
120
|
}
|
|
@@ -136,21 +141,39 @@ export function certainty(answer) {
|
|
|
136
141
|
*/
|
|
137
142
|
export async function route_free_message(home, scheduler, { title, goal, context, message, host, members }, fetch_impl) {
|
|
138
143
|
const choices = [...members.map(member => ({ value: member.name, description: member.description })),
|
|
139
|
-
{ value: HOST_CHOICE, description: `主持人 ${host.description}
|
|
140
|
-
{ value: ALL_CHOICE, description: '
|
|
144
|
+
{ value: HOST_CHOICE, description: `主持人 ${host.description}:没有指向任何人(如单独一句“hi”“你好”这类问候、闲聊,由主持人代表房间回应),或需要分工、排先后、互相回应` },
|
|
145
|
+
{ value: ALL_CHOICE, description: '全体成员(含主持人)各自回应一次,彼此不需要配合;须是用户明确要求大家各自回应,如“大家/都出来打个招呼”“各自介绍一下”“各说一句看法”' },
|
|
141
146
|
{ value: NONE_CHOICE, description: '无需回应:只是确认、致谢或旁注,不需要任何人回答' }];
|
|
142
147
|
const questions = [{ type: 'choice', name: 'handler', choices,
|
|
143
|
-
instructions: '这条用户消息没有用 @
|
|
144
|
-
const
|
|
145
|
-
const
|
|
148
|
+
instructions: '这条用户消息没有用 @ 点名成员。判断它的回应对象,按下面的顺序:1)正文叫了某位成员的名字,或在向其提问(包括“X在吗”“X还在吗”“X你怎么看”),选该成员,即使上一条是别人的回答;2)正文没有指向任何人时,看它是否在延续某位成员的回答(追问、要求修改、引用,或“回复”指向其消息),或在补充、纠正一条正由某位成员处理的消息,是则选该成员,是主持人就选主持人;3)明确要求所有成员各自回应一次(用了“大家”“都”“各自”等)选全体;4)单独的问候或闲聊(如“hi”)既没有指向某位成员,也没有要求大家一起回应,选主持人;5)只是确认、致谢或旁注选无需回应;6)对象不明确,或需要分工、排先后、互相回应时选主持人。' }];
|
|
149
|
+
const roster = [`${host.name}(主持人)`, ...members.map(member => member.name)].join('、');
|
|
150
|
+
const state = scheduler_state(title, context, message, goal, '', { roster }, ROUTE_CONTEXT_MESSAGES);
|
|
151
|
+
const answers = await decide(home, scheduler, state, questions, fetch_impl);
|
|
152
|
+
const judged = judged_of(state, questions, answers);
|
|
146
153
|
const choice = answers.handler?.choice ?? null;
|
|
147
154
|
const confidence = certainty(answers.handler);
|
|
148
155
|
const wide = choice === ALL_CHOICE || choice === NONE_CHOICE;
|
|
149
156
|
const sure = confidence !== null && confidence >= (wide ? ROUTE_WIDE_CONFIDENCE : ROUTE_CONFIDENCE);
|
|
150
|
-
if (sure && choice === ALL_CHOICE) return { action: 'all', choice, confidence,
|
|
151
|
-
if (sure && choice === NONE_CHOICE) return { action: 'none', choice, confidence,
|
|
152
|
-
if (sure && members.some(item => item.name === choice)) return { action: 'member', member: choice, choice, confidence,
|
|
153
|
-
return { action: 'host', choice, confidence,
|
|
157
|
+
if (sure && choice === ALL_CHOICE) return { action: 'all', choice, confidence, ...judged };
|
|
158
|
+
if (sure && choice === NONE_CHOICE) return { action: 'none', choice, confidence, ...judged };
|
|
159
|
+
if (sure && members.some(item => item.name === choice)) return { action: 'member', member: choice, choice, confidence, ...judged };
|
|
160
|
+
return { action: 'host', choice, confidence, ...judged };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The user named everyone (@全体成员): decide whether they answer at once (parallel) or one after another (sequential).
|
|
165
|
+
* Anything but a confident sequential stays parallel, the cheaper and earlier behaviour of naming everyone.
|
|
166
|
+
*/
|
|
167
|
+
export async function decide_group_order(home, scheduler, { title, goal, context, message, members }, fetch_impl) {
|
|
168
|
+
const questions = [{ type: 'choice', name: 'order', choices: [
|
|
169
|
+
{ value: 'parallel', description: '同时回答:各自独立回应即可,不需要看到别人的回答,如打招呼、各自介绍、各说一句看法、各自投票' },
|
|
170
|
+
{ value: 'sequential', description: `依次回答:后发言的需要看到前面的回答,如接龙、轮流补充或反驳、逐个评审上一位的结果、按顺序分工接力;按房间发言顺序(${members.join('、')})逐个回答` }],
|
|
171
|
+
instructions: '用户用 @全体成员 请房间里所有成员回应这条消息。判断大家应同时各自回答,还是需要按顺序依次回答(后一位看到前一位的回答再接着说)。' }];
|
|
172
|
+
const state = scheduler_state(title, context, message, goal, '', {}, ROUTE_CONTEXT_MESSAGES);
|
|
173
|
+
const answers = await decide(home, scheduler, state, questions, fetch_impl);
|
|
174
|
+
const confidence = certainty(answers.order);
|
|
175
|
+
const sequential = answers.order?.choice === 'sequential' && confidence !== null && confidence >= ROUTE_CONFIDENCE;
|
|
176
|
+
return { action: sequential ? 'sequential' : 'all', choice: answers.order?.choice ?? null, confidence, ...judged_of(state, questions, answers) };
|
|
154
177
|
}
|
|
155
178
|
|
|
156
179
|
/**
|
|
@@ -162,15 +185,16 @@ export async function decide_interjection(home, scheduler, { title, goal, user_r
|
|
|
162
185
|
{ value: HOST_CHOICE, description: `由主持人 ${host} 处理:留言要改变讨论目标、规则、分工、安排或结束讨论,或同时涉及多位专家` },
|
|
163
186
|
{ value: NOTE_CHOICE, description: '仅补充信息:留言只是旁注、信息或鼓励,不要求任何人回应,按当前安排继续' }];
|
|
164
187
|
const questions = [{ type: 'choice', name: 'handling', instructions: '讨论进行中用户发来这条留言。对照当前焦点、主持简报和安排,判断它最适合怎样处理。', choices }];
|
|
165
|
-
const
|
|
166
|
-
const
|
|
188
|
+
const state = scheduler_state(title, context, message, goal, user_request, extra);
|
|
189
|
+
const answers = await decide(home, scheduler, state, questions, fetch_impl);
|
|
190
|
+
const judged = judged_of(state, questions, answers);
|
|
167
191
|
const choice = answers.handling?.choice;
|
|
168
192
|
const confidence = certainty(answers.handling);
|
|
169
193
|
const sure = confidence !== null && confidence >= ROUTE_CONFIDENCE;
|
|
170
|
-
if (sure && choice === NOTE_CHOICE) return { action: 'note', confidence,
|
|
171
|
-
if (sure && experts.includes(choice)) return { action: 'ask', expert: choice, confidence,
|
|
194
|
+
if (sure && choice === NOTE_CHOICE) return { action: 'note', confidence, ...judged, label: '仅补充信息,按原计划继续' };
|
|
195
|
+
if (sure && experts.includes(choice)) return { action: 'ask', expert: choice, confidence, ...judged, label: `先请 ${choice} 回应这条留言` };
|
|
172
196
|
const lean = choice === NOTE_CHOICE ? '按原计划继续' : experts.includes(choice) ? `请 ${choice} 回应` : choice === HOST_CHOICE ? '由主持人处理' : '';
|
|
173
|
-
return { action: 'host', confidence,
|
|
197
|
+
return { action: 'host', confidence, ...judged, label: choice === HOST_CHOICE && sure ? '这条留言需要主持人处理' : unsure_label(lean) };
|
|
174
198
|
}
|
|
175
199
|
|
|
176
200
|
/**
|
|
@@ -189,17 +213,17 @@ export async function decide_next_speaker(home, scheduler, { title, goal, user_r
|
|
|
189
213
|
{ type: 'predicate', name: 'obligation',
|
|
190
214
|
instructions: `是否存在明确依据要求 ${candidates.join('、')} 之一接着发言:简报分工或流程规定轮到其人,或最近发言点名追问了其人?仅仅是“本轮还没发言”不算依据。` }];
|
|
191
215
|
const answers = await decide(home, scheduler, state, questions, fetch_impl);
|
|
192
|
-
const
|
|
216
|
+
const judged = judged_of(state, questions, answers);
|
|
193
217
|
const choice = answers.speaker?.choice;
|
|
194
218
|
const confidence = certainty(answers.speaker);
|
|
195
219
|
const obligation = answers.obligation?.probability;
|
|
196
220
|
if (candidates.includes(choice) && confidence !== null && confidence >= ROUTE_CONFIDENCE && obligation >= ROUTE_CONFIDENCE) {
|
|
197
|
-
return { action: 'next', expert: choice, confidence,
|
|
221
|
+
return { action: 'next', expert: choice, confidence, ...judged, label: `本轮继续,请 ${choice} 接着发言` };
|
|
198
222
|
}
|
|
199
223
|
if (typeof obligation === 'number' && obligation < ROUTE_CONFIDENCE) {
|
|
200
|
-
return { action: 'host', confidence,
|
|
224
|
+
return { action: 'host', confidence, ...judged, label: `有明确依据的概率 ${obligation.toFixed(2)},未达 ${ROUTE_CONFIDENCE.toFixed(2)},交给主持人安排` };
|
|
201
225
|
}
|
|
202
|
-
return { action: 'host', confidence,
|
|
226
|
+
return { action: 'host', confidence, ...judged, label: choice === HOST_CHOICE && confidence >= ROUTE_CONFIDENCE ? '建议由主持人调整安排,交给主持人' : unsure_label(candidates.includes(choice) ? `请 ${choice} 发言` : choice === HOST_CHOICE ? '交给主持人' : '') };
|
|
203
227
|
}
|
|
204
228
|
|
|
205
229
|
/**
|
|
@@ -216,13 +240,13 @@ export async function decide_round(home, scheduler, { title, goal, user_request,
|
|
|
216
240
|
if (candidates.length > 1) questions.push({ type: 'choice', name: 'first', instructions: '下一轮应先请谁发言?按简报分工或接龙顺序判断;没有规定时选被其他专家点名追问、最需要回应的人。',
|
|
217
241
|
choices: candidates.map(name => ({ value: name, description: `先请 ${name}` })) });
|
|
218
242
|
const answers = await decide(home, scheduler, state, questions, fetch_impl);
|
|
219
|
-
const
|
|
243
|
+
const judged = judged_of(state, questions, answers);
|
|
220
244
|
const choice = answers.next?.choice;
|
|
221
245
|
const confidence = certainty(answers.next);
|
|
222
246
|
if (choice !== 'continue' || confidence === null || confidence < ROUTE_CONFIDENCE || !candidates.length) {
|
|
223
247
|
const sure = confidence !== null && confidence >= ROUTE_CONFIDENCE;
|
|
224
|
-
return { action: 'host', confidence,
|
|
248
|
+
return { action: 'host', confidence, ...judged, label: choice === HOST_CHOICE && sure ? '建议收束或调整方向,交给主持人' : unsure_label(choice === 'continue' ? '继续下一轮' : choice === HOST_CHOICE ? '收束或调整方向' : '') };
|
|
225
249
|
}
|
|
226
250
|
const first = candidates.includes(answers.first?.choice) ? answers.first.choice : candidates[0];
|
|
227
|
-
return { action: 'continue', first, confidence,
|
|
251
|
+
return { action: 'continue', first, confidence, ...judged, label: `继续下一轮,先请 ${first} 发言` };
|
|
228
252
|
}
|