ai_co_discuss 0.2.34 → 0.2.36
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 +3 -3
- package/.agents/skills/discuss-participant/SKILL.md +6 -4
- package/README.md +2 -2
- package/backend/free_room.py +23 -3
- package/backend/runtime.py +10 -0
- package/backend/schema.sql +9 -3
- package/backend/server.py +7 -2
- package/docs/architecture.md +9 -7
- package/docs/cli.md +5 -3
- package/docs/runner.md +10 -8
- package/docs/workflow.md +9 -5
- package/package.json +1 -1
- package/runner/api_participant.mjs +24 -2
- package/runner/discuss.py +980 -970
- package/runner/discussion_adapters.mjs +3 -1
- package/runner/discussion_api.mjs +4 -2
- package/runner/discussion_launch.mjs +38 -17
- package/runner/discussion_models.mjs +19 -4
- package/runner/discussion_runner.mjs +8 -4
- package/runner/discussion_service.mjs +3 -3
- package/runner/free_runner.mjs +5 -2
- package/web/app.js +66 -18
- package/web/modern.css +1 -1
- package/web/modern.js +1959 -1858
|
@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
4
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
+
import { setTimeout } from 'node:timers/promises';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
import { api_generate, api_history, api_tool_results, read_api_key } from './discussion_api.mjs';
|
|
8
9
|
|
|
@@ -10,6 +11,9 @@ const MAX_MODEL_TURNS = 24;
|
|
|
10
11
|
// API participants follow the same skills that local software loads, read fresh for every execution.
|
|
11
12
|
const SKILLS = fileURLToPath(new URL('../.agents/skills/', import.meta.url));
|
|
12
13
|
const SITE_TIMEOUT_MS = 10_000;
|
|
14
|
+
const MODEL_RETRIES = 2;
|
|
15
|
+
const MODEL_RETRY_DELAY_MS = 3000;
|
|
16
|
+
const NO_TOOL_REMINDER = '普通文本不会被保存。请调用写入工具提交真实结果;确实无法继续才不调用工具直接结束。';
|
|
13
17
|
const TEXT = { type: 'string' };
|
|
14
18
|
const NUMBER = { type: 'integer', minimum: 1 };
|
|
15
19
|
const ROOM_FIELDS = ['id', 'title', 'goal', 'host', 'user_request', 'background', 'boundaries', 'experts', 'phase', 'focus',
|
|
@@ -150,6 +154,17 @@ export function system_prompt(context) {
|
|
|
150
154
|
return [environment, skill_text('discuss-participant'), ...(host && context.mode !== 'free' ? [skill_text('discuss-host')] : [])].join('\n\n---\n\n');
|
|
151
155
|
}
|
|
152
156
|
|
|
157
|
+
/** Retry a model request that failed before any room write; history is only extended on success. */
|
|
158
|
+
async function generate_with_retry(profile, key, state, signal) {
|
|
159
|
+
for (let attempt = 0; ; attempt++) {
|
|
160
|
+
try { return await api_generate(profile, key, state, signal); }
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (!error.retryable || attempt >= MODEL_RETRIES) throw error;
|
|
163
|
+
await setTimeout(MODEL_RETRY_DELAY_MS * (attempt + 1), undefined, { signal });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
153
168
|
/** Run bounded tool turns while the parent dispatcher continues renewing leases and observing pause. */
|
|
154
169
|
async function run_participant(profile, key, context) {
|
|
155
170
|
const room = await read_room(context);
|
|
@@ -158,9 +173,16 @@ async function run_participant(profile, key, context) {
|
|
|
158
173
|
execution_id: context.execution_id, requests: context.requests, read_after: context.read_after,
|
|
159
174
|
...(context.scheduler_suggestions?.length ? { scheduler_suggestions: context.scheduler_suggestions } : {}) } });
|
|
160
175
|
const state = { system: system_prompt(context), history: api_history(profile, prompt), tools: definitions };
|
|
176
|
+
let reminded = false;
|
|
161
177
|
for (let index = 0; index < MAX_MODEL_TURNS; index++) {
|
|
162
|
-
const calls = await
|
|
163
|
-
if (!calls.length)
|
|
178
|
+
const calls = await generate_with_retry(profile, key, state, context.signal);
|
|
179
|
+
if (!calls.length) {
|
|
180
|
+
// Some models answer in plain text mid-run; one reminder avoids pausing on a recoverable slip.
|
|
181
|
+
if (reminded) throw new Error('模型未调用房间工具保存结果,请核对模型是否支持工具调用');
|
|
182
|
+
reminded = true;
|
|
183
|
+
state.history.push(profile.api_protocol === 'gemini' ? { role: 'user', parts: [{ text: NO_TOOL_REMINDER }] } : { role: 'user', content: NO_TOOL_REMINDER });
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
164
186
|
const results = [];
|
|
165
187
|
for (const call of calls) {
|
|
166
188
|
context.signal.throwIfAborted();
|