dsh-vibe-math 0.3.2 → 0.3.4
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/README.md +14 -4
- package/package.json +1 -1
- package/vibe-math-v1/preset.yml +1 -1
- package/vibe-math-v1/vibe-math.js +38 -12
- package/vibe-math-v2/vibe-math-v2.js +131 -49
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ dsh plugin --profile <你的 profile> add github:ChongCyrus/Vibe-Mathematics
|
|
|
96
96
|
|
|
97
97
|
> 静态 PNG 预览 + 可编辑 Mermaid 图源(GitHub 原生渲染);完整流程说明见 [docs/架构图.md](docs/架构图.md)。
|
|
98
98
|
|
|
99
|
-

|
|
100
100
|
|
|
101
101
|
```mermaid
|
|
102
102
|
flowchart TB
|
|
@@ -153,6 +153,10 @@ flowchart TB
|
|
|
153
153
|
|
|
154
154
|
## 🧩 架构图 · v2(新架构 · 概率驱动)
|
|
155
155
|
|
|
156
|
+
> 静态 PNG 预览 + 可编辑 Mermaid 图源(GitHub 原生渲染);生成脚本见 [docs/generate_framework_diagram_v2.py](docs/generate_framework_diagram_v2.py)。
|
|
157
|
+
|
|
158
|
+

|
|
159
|
+
|
|
156
160
|
```mermaid
|
|
157
161
|
flowchart TB
|
|
158
162
|
subgraph L1["👤 交互层"]
|
|
@@ -399,7 +403,9 @@ flowchart TB
|
|
|
399
403
|
| `solverToolAllow` / `solverToolDeny` | `[]` | 求解器允许/禁止的工具(硬性 toolFilter) |
|
|
400
404
|
| `verifierToolAllow` / `verifierToolDeny` | `[]` | 验证器允许/禁止的工具 |
|
|
401
405
|
| `solverMaxToolCalls` / `verifierMaxToolCalls` | 0 | 每轮外部工具调用上限(0=不限,软性) |
|
|
402
|
-
| `reportIntervalMs` |
|
|
406
|
+
| `reportIntervalMs` | 0 | 0 = 仅事件驱动(有代理状态更新等事件才写报告);>0 = 定时自动写(毫秒) |
|
|
407
|
+
| `tickIntervalMs` | 2000 | 调度器心跳间隔(毫秒) |
|
|
408
|
+
| `activityLogCap` | 100 | 活动日志保留条数(report 最多显示 30 条) |
|
|
403
409
|
|
|
404
410
|
### v2(新架构)默认值
|
|
405
411
|
|
|
@@ -414,12 +420,16 @@ flowchart TB
|
|
|
414
420
|
| `reportMode` | `file` | `file` = 写报告文件 / `push` = 推送主代理汇报 / `both` |
|
|
415
421
|
| `promoteValueThreshold` | 0.7 | Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json |
|
|
416
422
|
| `priorityAdjust` | `none` | `none` / `deadend-deprioritize`(全死路降优先级)/ `survival-map`(按存活率重算) |
|
|
423
|
+
| `proposPriorityAdjust` | `none` | 命题优先级动态调整:`none` / `progress-graded`(按定论接近度+证明/证伪材料量重算,越接近定论越优先验证) |
|
|
417
424
|
| `provider` / `model` | 空 | 子代理模型(空 = 继承根代理) |
|
|
418
425
|
| `solverPersona` / `verifierPersona` | 空 | 注入求解器/验证器的额外要求 |
|
|
419
426
|
| `solverToolAllow` / `solverToolDeny` | `[]` | 求解器允许/禁止的工具 |
|
|
420
427
|
| `verifierToolAllow` / `verifierToolDeny` | `[]` | 验证器允许/禁止的工具 |
|
|
421
428
|
| `solverMaxToolCalls` / `verifierMaxToolCalls` | 0 | 每轮外部工具调用上限(0=不限) |
|
|
422
|
-
| `reportIntervalMs` |
|
|
429
|
+
| `reportIntervalMs` | 0 | 0 = 仅事件驱动(有状态更新才写/推);>0 = 定时自动汇报(毫秒) |
|
|
430
|
+
| `tickIntervalMs` | 2000 | 调度器心跳间隔(毫秒) |
|
|
431
|
+
| `activityLogCap` | 100 | 活动日志保留条数(report 最多显示 30 条) |
|
|
432
|
+
| `maxExplorerRetries` | 3 | explorer 拆方向失败的重派生上限 |
|
|
423
433
|
|
|
424
434
|
---
|
|
425
435
|
|
|
@@ -427,7 +437,7 @@ flowchart TB
|
|
|
427
437
|
|
|
428
438
|
- **断点续跑**:所有状态落盘到 `VibeMath_State/*.json`,每个子代理都是 DSH 的 **continuable 持久会话**(对话由 DSH 自动保存)。重启后新开会话 → `vibe_math_resume` 即可续跑。v2 额外用**进程纪元**区分"同进程暂停→恢复"(保留存活子代理继续)与"跨进程重启"(清理陈旧任务)。
|
|
429
439
|
- **中途人工干预**:`manual` 模式在关键节点(v1:brainstorm/solver 派发、验证裁决、晋升 Verified;v2:explorer/solver 派发、验证裁决)挂起决策;可随时 `set_mode auto` 切回自动;可对任意子代理 `message_agent` / `interrupt_agent`。
|
|
430
|
-
-
|
|
440
|
+
- **进度汇报**:默认**事件驱动** —— 只有代理状态更新等事件发生时才会写 `Progress_Logs/report.json`(v2 的 `reportMode` 可 `file`/`push`/`both`,`push` 通过 `rootAgent.followup()` 唤醒主代理主动汇报);只有把 `reportIntervalMs` 设为 >0 才启动定时自动汇报(间隔毫秒)。
|
|
431
441
|
|
|
432
442
|
---
|
|
433
443
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vibe-math",
|
|
3
3
|
"description": "Multi-agent mathematical problem-solving & verification frameworks for DeepSeek Harness — TWO agent presets in one install: vibe-math-v1 (classic pipeline: brainstorm → solver iteration → multi-verifier debate → Verified) and vibe-math-v2 (new probability-driven architecture: qs.json + Propos knowledge base + explorer→solver→review/debate verdict). Installing this bundle auto-installs both presets into the DSH preset root.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "installer.js",
|
|
7
7
|
"exports": {
|
package/vibe-math-v1/preset.yml
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
name: Vibe Math
|
|
1
|
+
name: Vibe Math V1
|
|
2
2
|
description: 多代理数学问题求解与验证框架(广度探索 → 深度迭代 → 交叉验证 → 知识沉淀)。具备标准模式的全部能力,并提供 vibe_math_* 工具集(20 个)与 /vibe 斜杠命令,用于数学问题的自动求解、多代理交叉验证、按项目隔离、断点续跑与人工/自动干预。
|
|
@@ -69,7 +69,9 @@ export function apply(ctx) {
|
|
|
69
69
|
verifierToolDeny: [],
|
|
70
70
|
solverMaxToolCalls: 0,
|
|
71
71
|
verifierMaxToolCalls: 0,
|
|
72
|
-
reportIntervalMs:
|
|
72
|
+
reportIntervalMs: 0, // 0 = 仅事件驱动(有代理状态更新等事件才写报告);>0 = 定时自动写(毫秒)
|
|
73
|
+
tickIntervalMs: 2000, // 调度器心跳间隔(毫秒):多久扫描一次状态并推进
|
|
74
|
+
activityLogCap: 100, // 活动日志保留条数(report.recentActivity 最多显示 30 条)
|
|
73
75
|
}
|
|
74
76
|
let params = Object.assign({}, DEFAULT_PARAMS)
|
|
75
77
|
let scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }
|
|
@@ -152,7 +154,9 @@ export function apply(ctx) {
|
|
|
152
154
|
{ name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
|
|
153
155
|
{ name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
|
|
154
156
|
{ name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
|
|
155
|
-
{ name: 'reportIntervalMs', type: 'integer', description: '
|
|
157
|
+
{ name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写 report.json);>0 = 定时自动汇报', suggestion: 0 },
|
|
158
|
+
{ name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
|
|
159
|
+
{ name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 report.recentActivity 的细节量,报告最多显示 30 条)', suggestion: 100 },
|
|
156
160
|
]
|
|
157
161
|
|
|
158
162
|
// ================= fs =================
|
|
@@ -178,7 +182,7 @@ export function apply(ctx) {
|
|
|
178
182
|
// ================= settings file (JSONC) =================
|
|
179
183
|
function sanitizeParams(obj) {
|
|
180
184
|
const out = {}
|
|
181
|
-
const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs']
|
|
185
|
+
const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap']
|
|
182
186
|
const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
|
|
183
187
|
for (const k of Object.keys(DEFAULT_PARAMS)) {
|
|
184
188
|
if (!(k in obj)) continue
|
|
@@ -231,7 +235,7 @@ export function apply(ctx) {
|
|
|
231
235
|
async function saveAll() { await writeJson('VibeMath_State/params.json', params); await writeJson('VibeMath_State/scheduler_state.json', scheduler); await writeJson('VibeMath_State/agent_registry.json', agentRegistry); await writeJson('VibeMath_State/dependencies.json', dependencies); await writeJson('VibeMath_State/decision_queue.json', decisionQueue); await writeJson('VibeMath_State/tasks.json', tasks); await writeJson('VibeMath_State/solved_by_verified.json', solvedByVerified); await writeJson('VibeMath_State/promotion_queue.json', promotionQueue); await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy); scheduler.lastCheckpoint = now() }
|
|
232
236
|
|
|
233
237
|
// ================= activity log / report =================
|
|
234
|
-
function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); if (activityLog.length >
|
|
238
|
+
function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); const cap = Number(params.activityLogCap) || 100; if (activityLog.length > cap) activityLog.shift(); reportDirty = true }
|
|
235
239
|
function buildReport() {
|
|
236
240
|
const openTasks = Object.keys(tasks).filter(function (k) { const t = tasks[k]; return t.status === 'spawning' || t.status === 'debating' || t.status === 'awaiting-verdict' })
|
|
237
241
|
return {
|
|
@@ -246,13 +250,14 @@ export function apply(ctx) {
|
|
|
246
250
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
|
|
247
251
|
openTasks: openTasks.length,
|
|
248
252
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
249
|
-
recentActivity: activityLog.slice(-30),
|
|
253
|
+
recentActivity: activityLog.slice(-Math.min(30, Number(params.activityLogCap) || 100)),
|
|
250
254
|
params: params,
|
|
251
255
|
}
|
|
252
256
|
}
|
|
253
257
|
async function maybeWriteReport(force) {
|
|
254
|
-
|
|
255
|
-
if (!force && (now() - lastReportWrite) <
|
|
258
|
+
const interval = Number(params.reportIntervalMs) || 0
|
|
259
|
+
if (!force && interval > 0 && (now() - lastReportWrite) < interval) return
|
|
260
|
+
if (!force && interval <= 0 && !reportDirty) return
|
|
256
261
|
await writeJson('Progress_Logs/report.json', buildReport())
|
|
257
262
|
lastReportWrite = now()
|
|
258
263
|
reportDirty = false
|
|
@@ -307,6 +312,19 @@ export function apply(ctx) {
|
|
|
307
312
|
async function resumeScheduler(agent) { const r = await init(agent); if (!r.ok) return r; scheduler.running = true; scheduler.gate = null; logActivity('resume', 'scheduler resumed'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler resumed', project: currentProject, frameworkRoot: frameworkRoot() } }
|
|
308
313
|
async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
|
|
309
314
|
async function abortScheduler() { scheduler.running = false; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]); scheduler.activeCount = 0; logActivity('abort', 'scheduler aborted, ' + ids.length + ' child(ren) interrupted'); await saveAll(); return { ok: true, message: 'scheduler aborted', interrupted: ids.length } }
|
|
315
|
+
// auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
|
|
316
|
+
async function autoResolvePending() {
|
|
317
|
+
const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
|
|
318
|
+
for (let i = 0; i < pending.length; i++) {
|
|
319
|
+
const d = pending[i]
|
|
320
|
+
try {
|
|
321
|
+
if (d.node === 'spawn') { await spawnChild(d.data.label, d.data.promptText, d.data.meta); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
|
|
322
|
+
else if (d.node === 'verdict') { await settleVerdict(d.data.task, d.data.verdict); delete tasks[d.data.task.id]; d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
|
|
323
|
+
else if (d.node === 'promote') { await promoteUnit(d.data.objId); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
|
|
324
|
+
} catch (e) { console.error('vibe-math: auto-resolve decision failed: ' + String((e && e.message) || e)) }
|
|
325
|
+
}
|
|
326
|
+
if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
|
|
327
|
+
}
|
|
310
328
|
async function getStatus() { return { ok: true, initialized: rootAgent !== undefined, running: scheduler.running, project: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects'), mode: params.mode, activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold, frameworkRoot: frameworkRoot(), pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length, openTasks: Object.keys(tasks).filter(function (k) { return tasks[k].status === 'spawning' || tasks[k].status === 'debating' || tasks[k].status === 'awaiting-verdict' }).length, registeredAgents: Object.keys(agentRegistry).length, recentActivity: activityLog.slice(-10), params: params } }
|
|
311
329
|
|
|
312
330
|
// ================= projects =================
|
|
@@ -339,7 +357,7 @@ export function apply(ctx) {
|
|
|
339
357
|
async function onChildEnd(info) { const meta = agentRegistry[info.id]; if (meta === undefined) return; scheduler.activeCount = Math.max(0, scheduler.activeCount - 1); const output = blocksToText(info.lastAssistantMessage); try { if (meta.role === 'brainstorm') await handleBrainstorm(info.id, meta, output); else if (meta.role === 'solver') await handleSolver(info.id, meta, output, info.stopReason); else if (meta.role === 'verifier') await handleVerifier(info.id, meta, output, info.stopReason); else if (meta.role === 'decider') await handleDecider(info.id, meta, output); else if (meta.role === 'derive') await handleDerive(info.id, meta, output) } catch (e) { console.error('vibe-math onChildEnd error: ' + String((e && e.stack) || e)) } await saveAll(); scheduleTick() }
|
|
340
358
|
async function handleBrainstorm(childId, meta, output) { delete agentRegistry[childId]; const parsed = parseJson(output); const dirs = (parsed && parsed.directions) || []; if (dirs.length === 0) { logActivity('brainstorm', 'problem ' + meta.qid + ' brainstorm returned no directions'); await saveAll(); return } brainstormRetries[meta.qid] = 0; const prog = []; for (let i = 0; i < dirs.length; i++) { const d = dirs[i]; prog.push({ direction_id: 'd_' + shortId(), title: d.title || '', method: d.method || '', core_assumption: d.core_assumption || '', round: 0, status: 'active', survival_probability: (typeof d.feasibility === 'number') ? d.feasibility : 0.5, dead_end_reason: '', lemmas: [], sub_routes: [], aux_hypotheses: [], updated_at: String(now()) }) } logActivity('brainstorm', 'problem ' + meta.qid + ' → ' + prog.length + ' directions'); await writeProgress(meta.qid, prog) }
|
|
341
359
|
async function handleDerive(childId, meta, output) { delete agentRegistry[childId]; const parsed = parseJson(output); const dirs = (parsed && parsed.directions) || []; if (dirs.length === 0) { logActivity('derive', 'problem ' + meta.qid + ' derived no new directions'); await saveAll(); return } const prog = await readProgress(meta.qid); for (let i = 0; i < dirs.length; i++) { const d = dirs[i]; prog.push({ direction_id: 'd_' + shortId(), title: d.title || '', method: d.method || '', core_assumption: d.core_assumption || (d.motivation || ''), round: 0, status: 'active', survival_probability: (typeof d.feasibility === 'number') ? d.feasibility : 0.5, dead_end_reason: '', lemmas: [], sub_routes: [], aux_hypotheses: [], updated_at: String(now()) }) } logActivity('derive', 'problem ' + meta.qid + ' derived ' + dirs.length + ' new directions'); await writeProgress(meta.qid, prog) }
|
|
342
|
-
async function handleSolver(childId, meta, output, stopReason) { const qid = meta.qid; const direction = meta.direction; const parsed = parseJson(output); const prog = await readProgress(qid); const dir = prog.find(function (d) { return d.direction_id === direction }); if (!dir) { delete agentRegistry[childId]; return } const status = (parsed && parsed.status) || statusFromStop(stopReason); dir.round = meta.round; dir.status = status; if (parsed) { if (parsed.lemmas) dir.lemmas = parsed.lemmas; if (parsed.sub_routes) dir.sub_routes = parsed.sub_routes; if (parsed.aux_hypotheses) dir.aux_hypotheses = parsed.aux_hypotheses; if (typeof parsed.survival_probability === 'number') dir.survival_probability = parsed.survival_probability; if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason } if (status === 'success') { dir.status = 'success'; delete agentRegistry[childId]; logActivity('solver', qid + '/' + direction + ' success at round ' + meta.round); await writePending(qid, dir, parsed) } else if (status === 'dead-end' || meta.round >= params.solverMaxRounds) { dir.status = 'dead-end'; if (!dir.dead_end_reason) dir.dead_end_reason = (status === 'dead-end' && !parsed) ? 'solver ended abnormally (' + stopReason + ')' : 'iteration cap reached'; delete agentRegistry[childId]; logActivity('solver', qid + '/' + direction + ' dead-end: ' + dir.dead_end_reason) } else { const q = { id: qid, description: meta.description || '' }; await followupChild(childId, solverPrompt(q, dir, meta.round + 1)); agentRegistry[childId].round = meta.round + 1; dir.round = meta.round + 1 } if (parsed && parsed.aux_hypotheses && parsed.aux_hypotheses.length > 0) await handleAuxHypotheses(qid, parsed.aux_hypotheses); dir.updated_at = String(now()); await writeProgress(qid, prog) }
|
|
360
|
+
async function handleSolver(childId, meta, output, stopReason) { const qid = meta.qid; const direction = meta.direction; const parsed = parseJson(output); const prog = await readProgress(qid); const dir = prog.find(function (d) { return d.direction_id === direction }); if (!dir) { delete agentRegistry[childId]; return } if (!parsed && !scheduler.running) { delete agentRegistry[childId]; return } const status = (parsed && parsed.status) || statusFromStop(stopReason); dir.round = meta.round; dir.status = status; if (parsed) { if (parsed.lemmas) dir.lemmas = parsed.lemmas; if (parsed.sub_routes) dir.sub_routes = parsed.sub_routes; if (parsed.aux_hypotheses) dir.aux_hypotheses = parsed.aux_hypotheses; if (typeof parsed.survival_probability === 'number') dir.survival_probability = parsed.survival_probability; if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason } if (status === 'success') { dir.status = 'success'; delete agentRegistry[childId]; logActivity('solver', qid + '/' + direction + ' success at round ' + meta.round); await writePending(qid, dir, parsed) } else if (status === 'dead-end' || meta.round >= params.solverMaxRounds) { dir.status = 'dead-end'; if (!dir.dead_end_reason) dir.dead_end_reason = (status === 'dead-end' && !parsed) ? 'solver ended abnormally (' + stopReason + ')' : 'iteration cap reached'; delete agentRegistry[childId]; logActivity('solver', qid + '/' + direction + ' dead-end: ' + dir.dead_end_reason) } else { const q = { id: qid, description: meta.description || '' }; await followupChild(childId, solverPrompt(q, dir, meta.round + 1)); agentRegistry[childId].round = meta.round + 1; dir.round = meta.round + 1 } if (parsed && parsed.aux_hypotheses && parsed.aux_hypotheses.length > 0) await handleAuxHypotheses(qid, parsed.aux_hypotheses); dir.updated_at = String(now()); await writeProgress(qid, prog) }
|
|
343
361
|
async function handleAuxHypotheses(qid, hyps) { const qs = await getQs(); for (let i = 0; i < hyps.length; i++) { const h = hyps[i]; if (!h || !h.title) continue; const subId = qid + '_sub_' + shortId(); qs.push({ id: subId, description: h.title + (h.statement ? ' - ' + h.statement : ''), priority: 1, status: 'unsolved', deps: [] }); dependencies[qid] = dependencies[qid] || []; dependencies[qid].push(subId); await writeQs(qs); logActivity('subquestion', qid + ' spawned sub-question ' + subId + ' (Aux_Hypothesis)') } }
|
|
344
362
|
async function writePending(qid, dir, parsed) { const doc = { qid: qid, direction: dir.direction_id, solution: solutionText(parsed, dir), lemmas: (parsed && parsed.lemmas) || [], findings: (parsed && parsed.findings) || [], sub_routes: (parsed && parsed.sub_routes) || [], aux_hypotheses: (parsed && parsed.aux_hypotheses) || [], survival_probability: dir.survival_probability, created_at: String(now()) }; const id = uuid(); await writeText('Pending_Verification/' + id + '.csv', 'qid,direction,content_json,created_at\n' + csvRow([qid, dir.direction_id, JSON.stringify(doc), String(now())]) + '\n') }
|
|
345
363
|
|
|
@@ -400,6 +418,14 @@ export function apply(ctx) {
|
|
|
400
418
|
const verdict = (parsed && parsed.verdict) || 'uncertain'
|
|
401
419
|
let t = tasks['verify:' + unitId]
|
|
402
420
|
if (!t) { t = { id: 'verify:' + unitId, type: 'verify', unitId: unitId, unit: meta.unit || { obj_id: unitId, title: unitId, content: '' }, status: 'debating', children: [], childResults: {}, round: 1, expectedCount: Math.max(3, params.verifierCount), createdAt: now() }; tasks[t.id] = t }
|
|
421
|
+
if (!parsed && !scheduler.running) {
|
|
422
|
+
// abort:被中断的验证器没有产出,丢弃该子代理并清理任务簿记(任务在 resume 时由 processVerification 重建)
|
|
423
|
+
delete agentRegistry[childId]
|
|
424
|
+
const ix = t.children.indexOf(childId); if (ix !== -1) t.children.splice(ix, 1)
|
|
425
|
+
delete t.childResults[childId]
|
|
426
|
+
if (t.children.length === 0 && t.id && tasks[t.id]) delete tasks[t.id]
|
|
427
|
+
return
|
|
428
|
+
}
|
|
403
429
|
if (t.children.indexOf(childId) === -1) t.children.push(childId)
|
|
404
430
|
t.childResults[childId] = { verdict: verdict, reason: (parsed && parsed.reason) || '', strictness: (parsed && parsed.strictness) || 'lenient', round: meta.round }
|
|
405
431
|
delete agentRegistry[childId]
|
|
@@ -458,7 +484,7 @@ export function apply(ctx) {
|
|
|
458
484
|
|
|
459
485
|
// ================= events / timer =================
|
|
460
486
|
ctx.on('subagent/end', function (info) { onChildEnd(info).catch(function (e) { console.error('vibe-math onChildEnd reject: ' + String((e && e.stack) || e)) }) })
|
|
461
|
-
ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, 2000); return () => clearInterval(t) })
|
|
487
|
+
ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, Math.max(200, Number(params.tickIntervalMs) || 2000)); return () => clearInterval(t) })
|
|
462
488
|
|
|
463
489
|
// ================= tools =================
|
|
464
490
|
function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
|
|
@@ -476,8 +502,8 @@ export function apply(ctx) {
|
|
|
476
502
|
registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
|
|
477
503
|
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { return await getStatus() })
|
|
478
504
|
registerTool('vibe_math_report', 'Return the full progress report (status + recent activity + params) and write it to Progress_Logs/report.json.', objParams({}), async function () { await maybeWriteReport(true); return buildReport() })
|
|
479
|
-
registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); return { ok: true, mode: params.mode } })
|
|
480
|
-
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['direct-veto', 'weighted-vote'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, args); await saveAll(); return { ok: true, params: params } })
|
|
505
|
+
registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } })
|
|
506
|
+
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['direct-veto', 'weighted-vote'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, args); await saveAll(); return { ok: true, params: params } })
|
|
481
507
|
registerTool('vibe_math_setup', 'Return the interactive parameter schema (each param: name, type, current, default, description, options, suggestion) for guided configuration.', objParams({}), async function () { const list = PARAM_SCHEMA.map(function (p) { const out = Object.assign({}, p); out.current = params[p.name]; out.default = DEFAULT_PARAMS[p.name]; return out }); return { ok: true, parameters: list, saveTo: frameworkRoot() + '/vibe_math_setting.json' } })
|
|
482
508
|
registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), async function () { return await saveSettings() })
|
|
483
509
|
registerTool('vibe_math_template', 'Create a fresh vibe_math_setting.json template (with defaults + comments) in the workspace (global) or current project folder.', objParams({ where: { type: 'string', enum: ['global', 'project'] } }), async function (args) { return await createTemplate((args && args.where) || 'global') })
|
|
@@ -499,7 +525,7 @@ export function apply(ctx) {
|
|
|
499
525
|
if (cmd === 'abort') return await abortScheduler()
|
|
500
526
|
if (cmd === 'status') return await getStatus()
|
|
501
527
|
if (cmd === 'report') { await maybeWriteReport(true); return buildReport() }
|
|
502
|
-
if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); return { ok: true, mode: params.mode } }
|
|
528
|
+
if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } }
|
|
503
529
|
if (cmd === 'setup') { const list = PARAM_SCHEMA.map(function (p) { const out = Object.assign({}, p); out.current = params[p.name]; out.default = DEFAULT_PARAMS[p.name]; return out }); return { ok: true, parameters: list, saveTo: frameworkRoot() + '/vibe_math_setting.json' } }
|
|
504
530
|
if (cmd === 'save') return await saveSettings()
|
|
505
531
|
if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')
|
|
@@ -47,10 +47,14 @@ export function apply(ctx) {
|
|
|
47
47
|
verifierToolDeny: [],
|
|
48
48
|
solverMaxToolCalls: 0,
|
|
49
49
|
verifierMaxToolCalls: 0,
|
|
50
|
-
reportIntervalMs:
|
|
50
|
+
reportIntervalMs: 0, // 0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 定时自动汇报(毫秒)
|
|
51
51
|
reportMode: 'file', // file | push | both
|
|
52
52
|
promoteValueThreshold: 0.7, // Propos → qs auto-promotion threshold (价值/关键性)
|
|
53
53
|
priorityAdjust: 'none', // none | deadend-deprioritize | survival-map
|
|
54
|
+
proposPriorityAdjust: 'none', // none | progress-graded(按定论接近度+证明/证伪材料量动态调命题优先级)
|
|
55
|
+
tickIntervalMs: 2000, // 调度器心跳间隔(毫秒)
|
|
56
|
+
activityLogCap: 100, // 活动日志保留条数(report.recentActivity 最多显示 30 条)
|
|
57
|
+
maxExplorerRetries: 3, // explorer 重派生上限(拆方向失败重试次数)
|
|
54
58
|
}
|
|
55
59
|
let params = Object.assign({}, DEFAULT_PARAMS)
|
|
56
60
|
let scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }
|
|
@@ -128,10 +132,14 @@ export function apply(ctx) {
|
|
|
128
132
|
{ name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
|
|
129
133
|
{ name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
|
|
130
134
|
{ name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
|
|
131
|
-
{ name: 'reportIntervalMs', type: 'integer', description: '
|
|
135
|
+
{ name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 同时按该间隔定时自动汇报', suggestion: 0 },
|
|
132
136
|
{ name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息让主代理主动汇报;both = 两者都做', suggestion: 'file' },
|
|
133
137
|
{ name: 'promoteValueThreshold', type: 'number', description: 'Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json', suggestion: 0.7 },
|
|
134
138
|
{ name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '优先级动态调整策略:none=不自动调;deadend-deprioritize=方向全死路时降优先级;survival-map=按最高方向存活率重算(存活率高越优先)', suggestion: 'none' },
|
|
139
|
+
{ name: 'proposPriorityAdjust', type: 'enum', options: ['none', 'progress-graded'], description: '命题优先级动态调整:none=不自动调;progress-graded=按「定论接近度(|布尔估计-0.5|)+ 证明/证伪材料量」重算,越接近定论越优先验证', suggestion: 'none' },
|
|
140
|
+
{ name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
|
|
141
|
+
{ name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 report.recentActivity 的细节量,报告最多显示 30 条)', suggestion: 100 },
|
|
142
|
+
{ name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限(达到后该问题标记为方向耗尽)', suggestion: 3 },
|
|
135
143
|
]
|
|
136
144
|
|
|
137
145
|
// ================= fs =================
|
|
@@ -156,7 +164,7 @@ export function apply(ctx) {
|
|
|
156
164
|
// ================= settings =================
|
|
157
165
|
function sanitizeParams(obj) {
|
|
158
166
|
const out = {}
|
|
159
|
-
const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs']
|
|
167
|
+
const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries']
|
|
160
168
|
const numFields = ['promoteValueThreshold']
|
|
161
169
|
const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
|
|
162
170
|
for (const k of Object.keys(DEFAULT_PARAMS)) {
|
|
@@ -169,6 +177,7 @@ export function apply(ctx) {
|
|
|
169
177
|
else if (k === 'verdictMode') { out[k] = (v === 'flat' || v === 'forced') ? v : DEFAULT_PARAMS[k] }
|
|
170
178
|
else if (k === 'reportMode') { out[k] = (v === 'file' || v === 'push' || v === 'both') ? v : DEFAULT_PARAMS[k] }
|
|
171
179
|
else if (k === 'priorityAdjust') { out[k] = (v === 'none' || v === 'deadend-deprioritize' || v === 'survival-map') ? v : DEFAULT_PARAMS[k] }
|
|
180
|
+
else if (k === 'proposPriorityAdjust') { out[k] = (v === 'none' || v === 'progress-graded') ? v : DEFAULT_PARAMS[k] }
|
|
172
181
|
else { out[k] = v }
|
|
173
182
|
}
|
|
174
183
|
return out
|
|
@@ -275,7 +284,7 @@ export function apply(ctx) {
|
|
|
275
284
|
async function reliableFiles() { return await listFiles('Reliable') }
|
|
276
285
|
|
|
277
286
|
// ================= reporting =================
|
|
278
|
-
function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); if (activityLog.length >
|
|
287
|
+
function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); const cap = Number(params.activityLogCap) || 100; if (activityLog.length > cap) activityLog.shift(); reportDirty = true }
|
|
279
288
|
async function buildReport() {
|
|
280
289
|
const qs = await getQs()
|
|
281
290
|
const propos = await getPropos()
|
|
@@ -287,21 +296,28 @@ export function apply(ctx) {
|
|
|
287
296
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
288
297
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
|
|
289
298
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
290
|
-
recentActivity: activityLog.slice(-30),
|
|
299
|
+
recentActivity: activityLog.slice(-Math.min(30, Number(params.activityLogCap) || 100)),
|
|
291
300
|
params: params,
|
|
292
301
|
}
|
|
293
302
|
}
|
|
294
303
|
async function maybeWriteReport(force) {
|
|
295
|
-
|
|
296
|
-
if (!force && (now() - lastReportWrite) <
|
|
304
|
+
const interval = Number(params.reportIntervalMs) || 0
|
|
305
|
+
if (!force && interval > 0 && (now() - lastReportWrite) < interval) return
|
|
306
|
+
if (!force && interval <= 0 && !reportDirty) return
|
|
297
307
|
await writeJson('Progress_Logs/report.json', await buildReport())
|
|
298
308
|
lastReportWrite = now(); reportDirty = false
|
|
299
309
|
}
|
|
300
310
|
async function maybePushReport(force) {
|
|
301
311
|
const mode = params.reportMode || 'file'
|
|
302
312
|
if (mode !== 'push' && mode !== 'both') return
|
|
303
|
-
|
|
304
|
-
if (
|
|
313
|
+
const interval = Number(params.reportIntervalMs) || 0
|
|
314
|
+
if (interval > 0) {
|
|
315
|
+
// heartbeat: push on interval (or force), independent of reportDirty so 'both' mode works
|
|
316
|
+
if (!force && (now() - lastPushReport) < interval) return
|
|
317
|
+
} else if (!force && !reportDirty) {
|
|
318
|
+
// event-driven: only push when an event happened since the last report
|
|
319
|
+
return
|
|
320
|
+
}
|
|
305
321
|
if (!rootAgent || typeof rootAgent.followup !== 'function') return
|
|
306
322
|
try {
|
|
307
323
|
const report = await buildReport()
|
|
@@ -310,7 +326,7 @@ export function apply(ctx) {
|
|
|
310
326
|
'活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions.length + '。' +
|
|
311
327
|
'请调用 vibe_math_report 汇总当前进展及各代理状态,并用人话简要汇报(不打断用户,简短即可)。'
|
|
312
328
|
rootAgent.followup({ id: uuid(), role: 'user', content: [textBlock(text)], source: { kind: 'plugin', plugin: 'vibe-math-v2' } })
|
|
313
|
-
lastPushReport = now()
|
|
329
|
+
lastPushReport = now()
|
|
314
330
|
} catch (e) {
|
|
315
331
|
console.error('vibe-math-v2: push report failed: ' + String((e && e.message) || e))
|
|
316
332
|
}
|
|
@@ -455,8 +471,8 @@ export function apply(ctx) {
|
|
|
455
471
|
await processVerify()
|
|
456
472
|
await reconcileVerify()
|
|
457
473
|
await processSolve()
|
|
458
|
-
await maybeWriteReport(false)
|
|
459
474
|
await maybePushReport(false)
|
|
475
|
+
await maybeWriteReport(false)
|
|
460
476
|
const qs = await getQs()
|
|
461
477
|
const unsolved = qs.filter(function (q) { return !q.已解决 && q.优先级 !== 'never' })
|
|
462
478
|
if (unsolved.length === 0 && Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0) {
|
|
@@ -469,7 +485,7 @@ export function apply(ctx) {
|
|
|
469
485
|
const prog = parseProgress(unsolved[i])
|
|
470
486
|
const exhaustedAll = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
|
|
471
487
|
const hasActive = prog.directions.some(function (d) { return d.status === 'active' })
|
|
472
|
-
const blocked = (prog.directions.length === 0 || exhaustedAll) && (explorerRetries[unsolved[i].id] || 0) >= 3
|
|
488
|
+
const blocked = (prog.directions.length === 0 || exhaustedAll) && (explorerRetries[unsolved[i].id] || 0) >= (Number(params.maxExplorerRetries) || 3)
|
|
473
489
|
if (hasActive || !blocked) { allBlocked = false; break }
|
|
474
490
|
}
|
|
475
491
|
if (allBlocked) {
|
|
@@ -482,14 +498,15 @@ export function apply(ctx) {
|
|
|
482
498
|
}
|
|
483
499
|
// note 4: probability-1 rules
|
|
484
500
|
async function processStatusUpdates() {
|
|
485
|
-
let changed = false
|
|
486
501
|
const qs = await getQs()
|
|
502
|
+
let qsChanged = false
|
|
487
503
|
for (let i = 0; i < qs.length; i++) {
|
|
488
504
|
const q = qs[i]
|
|
489
|
-
if (q.解法列表 && q.解法列表.some(function (s) { return s.正确概率 === 1 })) { if (!q.已解决)
|
|
505
|
+
if (q.解法列表 && q.解法列表.some(function (s) { return s.正确概率 === 1 })) { if (!q.已解决) qsChanged = true; q.已解决 = true; q.优先级 = 'never' }
|
|
490
506
|
}
|
|
491
|
-
if (
|
|
507
|
+
if (qsChanged) { await writeQs(qs); logActivity('update', 'problems marked solved by probability-1 solutions') }
|
|
492
508
|
const propos = await getPropos()
|
|
509
|
+
let closedPromoted = false
|
|
493
510
|
for (let i = 0; i < propos.length; i++) {
|
|
494
511
|
const p = propos[i]
|
|
495
512
|
let pChanged = false
|
|
@@ -498,32 +515,61 @@ export function apply(ctx) {
|
|
|
498
515
|
if (proofOne && p.布尔估计 !== 1) { p.布尔估计 = 1; pChanged = true }
|
|
499
516
|
else if (refuteOne && p.布尔估计 !== 0) { p.布尔估计 = 0; pChanged = true }
|
|
500
517
|
if ((p.布尔估计 === 1 || p.布尔估计 === 0) && p.优先级 !== 'never') { p.优先级 = 'never'; pChanged = true }
|
|
501
|
-
if (p.布尔估计 === 1 || p.布尔估计 === 0) {
|
|
518
|
+
if (p.布尔估计 === 1 || p.布尔估计 === 0) {
|
|
519
|
+
if (await writeVerifiedCardIfNeeded(p)) pChanged = true
|
|
520
|
+
// 源命题已定论 → 关闭其晋升出的"僵尸"问题(避免永远未解决)
|
|
521
|
+
const srcMarker = '由命题 ' + p.id + '('
|
|
522
|
+
for (let j = 0; j < qs.length; j++) {
|
|
523
|
+
const qj = qs[j]
|
|
524
|
+
if (!qj.已解决 && String(qj.progress || '').indexOf(srcMarker) !== -1) { qj.已解决 = true; qj.优先级 = 'never'; closedPromoted = true }
|
|
525
|
+
}
|
|
526
|
+
}
|
|
502
527
|
if (pChanged) await upsertProposition(p)
|
|
503
528
|
}
|
|
529
|
+
if (closedPromoted) { await writeQs(qs); logActivity('update', 'promoted problems closed because their source proposition resolved') }
|
|
504
530
|
}
|
|
505
531
|
async function processPriorityAdjust() {
|
|
506
532
|
const mode = params.priorityAdjust || 'none'
|
|
507
|
-
if (mode
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
533
|
+
if (mode !== 'none') {
|
|
534
|
+
const qs = await getQs()
|
|
535
|
+
let changed = false
|
|
536
|
+
for (let i = 0; i < qs.length; i++) {
|
|
537
|
+
const q = qs[i]
|
|
538
|
+
if (q.已解决 || q.优先级 === 'never') continue
|
|
539
|
+
const prog = parseProgress(q)
|
|
540
|
+
if (mode === 'deadend-deprioritize') {
|
|
541
|
+
if (prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' })) {
|
|
542
|
+
const cur = Number(q.优先级); if (Number.isFinite(cur) && cur < 10) { q.优先级 = 10; changed = true }
|
|
543
|
+
}
|
|
544
|
+
} else if (mode === 'survival-map') {
|
|
545
|
+
if (prog.directions.length > 0) {
|
|
546
|
+
const maxSurv = Math.max.apply(null, prog.directions.map(function (d) { return Number(d.survival) || 0 }))
|
|
547
|
+
const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * maxSurv)))
|
|
548
|
+
if (q.优先级 !== target) { q.优先级 = target; changed = true }
|
|
549
|
+
}
|
|
523
550
|
}
|
|
524
551
|
}
|
|
552
|
+
if (changed) { await writeQs(qs); logActivity('priority', 'priorities auto-adjusted (' + mode + ')') }
|
|
553
|
+
}
|
|
554
|
+
const pMode = params.proposPriorityAdjust || 'none'
|
|
555
|
+
if (pMode === 'progress-graded') {
|
|
556
|
+
const propos = await getPropos()
|
|
557
|
+
const changedProps = []
|
|
558
|
+
for (let i = 0; i < propos.length; i++) {
|
|
559
|
+
const p = propos[i]
|
|
560
|
+
if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
|
|
561
|
+
const closeness = Math.abs(Number(p.布尔估计) - 0.5)
|
|
562
|
+
const material = Math.min(5, (p.证明列表 || []).length + (p.证伪列表 || []).length)
|
|
563
|
+
const score = closeness * 1.2 + material * 0.08
|
|
564
|
+
const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * score)))
|
|
565
|
+
const cur = Number(p.优先级)
|
|
566
|
+
if (Number.isFinite(cur) && cur !== target) { p.优先级 = target; changedProps.push(p) }
|
|
567
|
+
}
|
|
568
|
+
if (changedProps.length > 0) {
|
|
569
|
+
for (let i = 0; i < changedProps.length; i++) await upsertProposition(changedProps[i])
|
|
570
|
+
logActivity('priority', 'proposition priorities auto-adjusted (progress-graded)')
|
|
571
|
+
}
|
|
525
572
|
}
|
|
526
|
-
if (changed) { await writeQs(qs); logActivity('priority', 'priorities auto-adjusted (' + mode + ')') }
|
|
527
573
|
}
|
|
528
574
|
// note 3 + user 价值 field: promote high-value unresolved propositions into qs.json
|
|
529
575
|
async function processPromote() {
|
|
@@ -570,7 +616,7 @@ export function apply(ctx) {
|
|
|
570
616
|
for (let j = 0; j < sols.length; j++) {
|
|
571
617
|
const s = sols[j]
|
|
572
618
|
if (s.正确概率 === 1 || s.正确概率 === 0 || s.已验) continue
|
|
573
|
-
out.push({ rId: 'r-' + q.id + '-s' + j, kind: 'problem-solution', qid: q.id, 概述: q.概述, process: s.完整解法 || '', idx: j, priority: q.优先级 === 'never' ? 999 : Number(q.优先级) })
|
|
619
|
+
out.push({ rId: 'r-' + q.id + '-s' + j, kind: 'problem-solution', qid: q.id, 概述: q.概述, process: s.完整解法 || '', idx: j, prob: Number(s.正确概率) || 0, priority: q.优先级 === 'never' ? 999 : Number(q.优先级) })
|
|
574
620
|
}
|
|
575
621
|
}
|
|
576
622
|
const propos = await getPropos()
|
|
@@ -579,13 +625,13 @@ export function apply(ctx) {
|
|
|
579
625
|
if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
|
|
580
626
|
const proofs = p.证明列表 || []; const refutes = p.证伪列表 || []
|
|
581
627
|
if (proofs.length === 0 && refutes.length === 0) {
|
|
582
|
-
out.push({ rId: 'r-' + p.id, kind: 'proposition', pId: p.id, 概述: p.概述, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) })
|
|
628
|
+
out.push({ rId: 'r-' + p.id, kind: 'proposition', pId: p.id, 概述: p.概述, prob: Number(p.布尔估计) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) })
|
|
583
629
|
} else {
|
|
584
|
-
for (let j = 0; j < proofs.length; j++) { if (proofs[j].正确概率 === 1 || proofs[j].正确概率 === 0 || proofs[j].已验) continue; out.push({ rId: 'r-' + p.id + '-pf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证明', process: proofs[j].完整过程 || '', idx: j, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
|
|
585
|
-
for (let j = 0; j < refutes.length; j++) { if (refutes[j].正确概率 === 1 || refutes[j].正确概率 === 0 || refutes[j].已验) continue; out.push({ rId: 'r-' + p.id + '-rf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证伪', process: refutes[j].完整过程 || '', idx: j, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
|
|
630
|
+
for (let j = 0; j < proofs.length; j++) { if (proofs[j].正确概率 === 1 || proofs[j].正确概率 === 0 || proofs[j].已验) continue; out.push({ rId: 'r-' + p.id + '-pf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证明', process: proofs[j].完整过程 || '', idx: j, prob: Number(proofs[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
|
|
631
|
+
for (let j = 0; j < refutes.length; j++) { if (refutes[j].正确概率 === 1 || refutes[j].正确概率 === 0 || refutes[j].已验) continue; out.push({ rId: 'r-' + p.id + '-rf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证伪', process: refutes[j].完整过程 || '', idx: j, prob: Number(refutes[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
|
|
586
632
|
}
|
|
587
633
|
}
|
|
588
|
-
out.sort(function (a, b) { return a.priority - b.priority })
|
|
634
|
+
out.sort(function (a, b) { if (a.priority !== b.priority) return a.priority - b.priority; return (b.prob || 0) - (a.prob || 0) })
|
|
589
635
|
return out
|
|
590
636
|
}
|
|
591
637
|
async function backfillVerifiers(t) {
|
|
@@ -623,10 +669,11 @@ export function apply(ctx) {
|
|
|
623
669
|
const prog = parseProgress(q)
|
|
624
670
|
const allExhausted = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
|
|
625
671
|
if (prog.directions.length === 0 || allExhausted) {
|
|
626
|
-
|
|
627
|
-
|
|
672
|
+
const explorerCap = Number(params.maxExplorerRetries) || 3
|
|
673
|
+
if ((explorerRetries[q.id] || 0) >= explorerCap) {
|
|
674
|
+
if (prog.directions.length === 0) prog.directions.push({ id: 'd_' + shortId(), title: 'explorer 失败', method: '', core_assumption: '', feasibility: 0, status: 'dead-end', round: 0, survival: 0, routes: [], blockers: [], dead_end_reason: 'explorer 连续 ' + explorerCap + ' 次未产出方向' })
|
|
628
675
|
await saveProgress(q.id, prog)
|
|
629
|
-
logActivity('explorer', 'problem ' + q.id + ' explorer exhausted (
|
|
676
|
+
logActivity('explorer', 'problem ' + q.id + ' explorer exhausted (' + explorerCap + ' failed attempts)')
|
|
630
677
|
continue
|
|
631
678
|
}
|
|
632
679
|
explorerRetries[q.id] = (explorerRetries[q.id] || 0) + 1
|
|
@@ -672,6 +719,7 @@ export function apply(ctx) {
|
|
|
672
719
|
const prog = parseProgress(q)
|
|
673
720
|
const dir = prog.directions.find(function (d) { return d.id === dirId })
|
|
674
721
|
if (!dir) { delete agentRegistry[childId]; return }
|
|
722
|
+
if (!parsed && !scheduler.running) { delete agentRegistry[childId]; return } // abort:不把方向标记为死路,保留待 resume
|
|
675
723
|
const status = (parsed && parsed.status) || statusFromStop(stopReason)
|
|
676
724
|
dir.round = meta.round
|
|
677
725
|
if (parsed) {
|
|
@@ -728,9 +776,11 @@ export function apply(ctx) {
|
|
|
728
776
|
function statusFromStop(stopReason) { return (stopReason === 'completed' || stopReason === 'max-tokens') ? 'continue' : 'dead-end' }
|
|
729
777
|
async function addLemmaAsProposition(qid, lemma) {
|
|
730
778
|
if (!lemma || !lemma.title) return
|
|
779
|
+
let be = clamp01(lemma.布尔估计 != null ? lemma.布尔估计 : 0.6)
|
|
780
|
+
if (be >= 1) be = 0.99; else if (be <= 0) be = 0.01 // 写入时概率必须 <1 且 >0(待验证器验证)
|
|
731
781
|
const p = {
|
|
732
782
|
id: 'p-' + shortId(), 概述: lemma.statement || lemma.title,
|
|
733
|
-
布尔估计:
|
|
783
|
+
布尔估计: be,
|
|
734
784
|
细类型: (lemma.细类型 && typeof lemma.细类型 === 'object') ? lemma.细类型 : { 未分类: {} },
|
|
735
785
|
证明列表: [{ 完整过程: lemma.proof || '', 正确概率: clamp01(0.7), '支持信息/依据': '' }],
|
|
736
786
|
证伪列表: [], 优先级: (lemma.优先级 != null) ? lemma.优先级 : 1,
|
|
@@ -771,6 +821,14 @@ export function apply(ctx) {
|
|
|
771
821
|
const Reason = (parsed && parsed.Reason) || ''
|
|
772
822
|
let t = tasks['verify:' + rId]
|
|
773
823
|
if (!t) { t = { id: 'verify:' + rId, type: 'verify', r: { kind: 'proposition', pId: rId, 概述: rId }, rId: rId, status: 'debating', children: [], childResults: {}, round: 1, expectedCount: Math.max(2, params.verifierCount), createdAt: now() }; tasks[t.id] = t }
|
|
824
|
+
if (!parsed && !scheduler.running) {
|
|
825
|
+
// abort:被中断的验证器没有产出,丢弃该子代理并清理任务簿记(任务在 resume 时由 processVerify 重建)
|
|
826
|
+
delete agentRegistry[childId]
|
|
827
|
+
const ix = t.children.indexOf(childId); if (ix !== -1) t.children.splice(ix, 1)
|
|
828
|
+
delete t.childResults[childId]
|
|
829
|
+
if (t.children.length === 0 && t.id && tasks[t.id]) delete tasks[t.id]
|
|
830
|
+
return
|
|
831
|
+
}
|
|
774
832
|
if (t.children.indexOf(childId) === -1) t.children.push(childId)
|
|
775
833
|
t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round }
|
|
776
834
|
delete agentRegistry[childId]
|
|
@@ -782,6 +840,7 @@ export function apply(ctx) {
|
|
|
782
840
|
async function advanceVerification(t, round) {
|
|
783
841
|
if (round < params.debateMaxRounds && !consensus(t) && t.children.length > 0) {
|
|
784
842
|
if (!scheduler.running) { t.status = 'paused'; return } // resume will re-advance this task
|
|
843
|
+
if (scheduler.activeCount >= params.maxParallelThreshold) { t.status = 'paused'; return } // 并发门:等有空闲槽位再辩论(reconcileVerify 会重推进)
|
|
785
844
|
t.round = round + 1
|
|
786
845
|
const transcript = buildTranscript(t)
|
|
787
846
|
const nextChildren = []
|
|
@@ -891,7 +950,7 @@ export function apply(ctx) {
|
|
|
891
950
|
sol.已验 = true
|
|
892
951
|
sol.验证记录 = sol.验证记录 || []
|
|
893
952
|
sol.验证记录.push({ 结果: v, 时间: now(), 依据: strongestReason(t, v >= 0.5 ? 1 : 0) })
|
|
894
|
-
if (v === 1) { q.已解决 = true; q.优先级 = 'never' }
|
|
953
|
+
if (v === 1) { q.已解决 = true; q.优先级 = 'never'; await writeVerifiedProblemCardIfNeeded(q, sol) }
|
|
895
954
|
}
|
|
896
955
|
await writeQs(qs)
|
|
897
956
|
}
|
|
@@ -922,6 +981,16 @@ export function apply(ctx) {
|
|
|
922
981
|
await writeJson('Verified/' + cat + '_Verified.json', list)
|
|
923
982
|
return true
|
|
924
983
|
}
|
|
984
|
+
async function writeVerifiedProblemCardIfNeeded(q, sol) {
|
|
985
|
+
if (!q || !q.已解决) return false
|
|
986
|
+
const cat = '问题'
|
|
987
|
+
const list = await readVerifiedCategory(cat)
|
|
988
|
+
if (list.some(function (c) { return c.id === q.id })) return false // idempotent
|
|
989
|
+
const card = { id: q.id, 概述: q.概述, 类型: '问题', 结论: true, 概率: 1, 内容: (sol && sol.完整解法) || '', 来源: q.id, 时间: now(), 分类: cat }
|
|
990
|
+
list.push(card)
|
|
991
|
+
await writeJson('Verified/' + cat + '_Verified.json', list)
|
|
992
|
+
return true
|
|
993
|
+
}
|
|
925
994
|
|
|
926
995
|
// ================= child result dispatch =================
|
|
927
996
|
async function onChildEnd(info) {
|
|
@@ -956,15 +1025,28 @@ export function apply(ctx) {
|
|
|
956
1025
|
logActivity(fresh ? 'start' : 'resume', 'cleared ' + Object.keys(agentRegistry).length + ' agent(s) and ' + Object.keys(tasks).length + ' task(s) (' + (fresh ? 'restart' : 'stale from previous process') + ')')
|
|
957
1026
|
agentRegistry = {}; tasks = {}
|
|
958
1027
|
}
|
|
1028
|
+
scheduler.activeCount = 0 // 仅清空 registry/tasks 时归零;同进程 resume 保留存活计数(并发门才准确)
|
|
959
1029
|
}
|
|
960
1030
|
await writeJson('VibeMath_State/process_epoch.json', processEpoch)
|
|
961
|
-
|
|
1031
|
+
await saveAll()
|
|
962
1032
|
return { ok: true }
|
|
963
1033
|
}
|
|
964
1034
|
async function startScheduler(agent) { const r = await init(agent, true); if (!r.ok) return r; scheduler.running = true; scheduler.startedAt = now(); scheduler.gate = null; logActivity('start', 'scheduler started for project ' + currentProject); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler started', project: currentProject, frameworkRoot: frameworkRoot() } }
|
|
965
1035
|
async function resumeScheduler(agent) { const r = await init(agent, false); if (!r.ok) return r; scheduler.running = true; scheduler.gate = null; logActivity('resume', 'scheduler resumed'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler resumed', project: currentProject, frameworkRoot: frameworkRoot() } }
|
|
966
1036
|
async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
|
|
967
1037
|
async function abortScheduler() { scheduler.running = false; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]); scheduler.activeCount = 0; logActivity('abort', 'scheduler aborted, ' + ids.length + ' child(ren) interrupted'); await saveAll(); return { ok: true, message: 'scheduler aborted', interrupted: ids.length } }
|
|
1038
|
+
// auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
|
|
1039
|
+
async function autoResolvePending() {
|
|
1040
|
+
const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
|
|
1041
|
+
for (let i = 0; i < pending.length; i++) {
|
|
1042
|
+
const d = pending[i]
|
|
1043
|
+
try {
|
|
1044
|
+
if (d.node === 'spawn') { await spawnChild(d.data.label, d.data.promptText, d.data.meta); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
|
|
1045
|
+
else if (d.node === 'verdict') { await settleVerdict(d.data.task, d.data.verdict); delete tasks[d.data.task.id]; d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
|
|
1046
|
+
} catch (e) { console.error('vibe-math-v2: auto-resolve decision failed: ' + String((e && e.message) || e)) }
|
|
1047
|
+
}
|
|
1048
|
+
if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
|
|
1049
|
+
}
|
|
968
1050
|
async function getStatus() {
|
|
969
1051
|
const qs = await getQs(); const propos = await getPropos()
|
|
970
1052
|
return {
|
|
@@ -976,7 +1058,7 @@ export function apply(ctx) {
|
|
|
976
1058
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
977
1059
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
|
|
978
1060
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
979
|
-
recentActivity: activityLog.slice(-10), params: params,
|
|
1061
|
+
recentActivity: activityLog.slice(-Math.min(10, Number(params.activityLogCap) || 100)), params: params,
|
|
980
1062
|
}
|
|
981
1063
|
}
|
|
982
1064
|
|
|
@@ -995,7 +1077,7 @@ export function apply(ctx) {
|
|
|
995
1077
|
|
|
996
1078
|
// ================= events / timer =================
|
|
997
1079
|
ctx.on('subagent/end', function (info) { onChildEnd(info).catch(function (e) { console.error('vibe-math-v2 onChildEnd reject: ' + String((e && e.stack) || e)) }) })
|
|
998
|
-
ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, 2000); return () => clearInterval(t) })
|
|
1080
|
+
ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, Math.max(200, Number(params.tickIntervalMs) || 2000)); return () => clearInterval(t) })
|
|
999
1081
|
|
|
1000
1082
|
// ================= tools =================
|
|
1001
1083
|
function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
|
|
@@ -1012,8 +1094,8 @@ export function apply(ctx) {
|
|
|
1012
1094
|
registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
|
|
1013
1095
|
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { return await getStatus() })
|
|
1014
1096
|
registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), async function () { await maybeWriteReport(true); return await buildReport() })
|
|
1015
|
-
registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); return { ok: true, mode: params.mode } })
|
|
1016
|
-
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); return { ok: true, params: params } })
|
|
1097
|
+
registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } })
|
|
1098
|
+
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); return { ok: true, params: params } })
|
|
1017
1099
|
registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), async function () { const list = PARAM_SCHEMA.map(function (p) { const out = Object.assign({}, p); out.current = params[p.name]; out.default = DEFAULT_PARAMS[p.name]; return out }); return { ok: true, parameters: list, saveTo: frameworkRoot() + '/vibe_math_setting.json' } })
|
|
1018
1100
|
registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), async function () { return await saveSettings() })
|
|
1019
1101
|
registerTool('vibe_math_template', 'Create a fresh vibe_math_setting.json template (with defaults + comments) in the workspace (global) or current project folder.', objParams({ where: { type: 'string', enum: ['global', 'project'] } }), async function (args) { return await createTemplate((args && args.where) || 'global') })
|
|
@@ -1040,7 +1122,7 @@ export function apply(ctx) {
|
|
|
1040
1122
|
if (cmd === 'abort') return await abortScheduler()
|
|
1041
1123
|
if (cmd === 'status') return await getStatus()
|
|
1042
1124
|
if (cmd === 'report') { await maybeWriteReport(true); return await buildReport() }
|
|
1043
|
-
if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); return { ok: true, mode: params.mode } }
|
|
1125
|
+
if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } }
|
|
1044
1126
|
if (cmd === 'setup') { const list = PARAM_SCHEMA.map(function (p) { const out = Object.assign({}, p); out.current = params[p.name]; out.default = DEFAULT_PARAMS[p.name]; return out }); return { ok: true, parameters: list, saveTo: frameworkRoot() + '/vibe_math_setting.json' } }
|
|
1045
1127
|
if (cmd === 'save') return await saveSettings()
|
|
1046
1128
|
if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')
|