dsh-vibe-math 0.3.16 → 0.3.18
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 +1 -0
- package/package.json +1 -1
- package/vibe-math-v1/vibe-math.js +137 -24
- package/vibe-math-v1//345/256/236/347/216/260/346/226/271/346/241/210-/345/244/232/344/273/243/347/220/206/346/225/260/345/255/246/351/227/256/351/242/230/346/261/202/350/247/243/344/270/216/351/252/214/350/257/201/346/241/206/346/236/266.md +2 -0
- package/vibe-math-v2/vibe-math-v2.js +142 -24
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +1 -0
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
- **断点续跑**:调度状态、任务栈、代理注册表、决策队列、验证器历史准确率等全部落盘;重启后 `resume` 即可恢复(v2 用进程纪元区分"同进程暂停→恢复"与"跨进程重启")。
|
|
42
42
|
- **中途人工干预(并继续)**:`auto / manual` 模式随时切换;manual 在关键节点挂起决策等你 approve/reject/override;可对任意子代理发消息 / 中断。
|
|
43
43
|
- **按项目隔离**:每个数学问题一个独立项目文件夹,互不干扰,可随时切换。
|
|
44
|
+
- **多会话并行隔离**:DSH 的 agent preset 是 standing mount(同一 preset 的所有会话共享一个插件实例),插件内部按**根会话 id** 隔离全部运行状态——两个会话可以同时各跑一个项目(如 A 会话跑项目 A、B 会话跑项目 B),各自的子代理会正确挂在自己会话名下,调度器 / 参数 / 决策队列 / 当前项目互不干扰。当前项目按会话分别持久化(`VibeMath/current.<会话id>.json`)。
|
|
44
45
|
- **子代理权限可调控**:可限制子代理允许/禁止的工具、每轮外部工具调用上限,并明确告知其可读 `Verified/`、`Propos/`、`Reliable/` 与进度日志。
|
|
45
46
|
- **可配置**:`vibe_math_setting.json`(含注释)自定义默认参数;`/vibe setup` 交互式问答配置。
|
|
46
47
|
- **自然语言控制**:主代理充当「助手 + 汇报者」,你把需求说成人话,它自己调用工具、汇报进度、配置参数。
|
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.18",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "installer.js",
|
|
7
7
|
"exports": {
|
|
@@ -40,6 +40,13 @@ function ensurePresetInstalled(logger) {
|
|
|
40
40
|
export const name = 'vibe-math'
|
|
41
41
|
export const inject = ['subagents', 'agents', 'fs', 'tools', 'commands']
|
|
42
42
|
|
|
43
|
+
// Standing mount: DSH mounts each agent preset ONCE per preset and joins every
|
|
44
|
+
// session that names it to that SAME plugin instance (see @deepseek-ai/dsh-agent-presets).
|
|
45
|
+
// This plugin must therefore isolate ALL per-session state itself, keyed by the
|
|
46
|
+
// root agent (session) id — otherwise two sessions running the preset at the same
|
|
47
|
+
// time (e.g. project A and project B) would share one rootAgent/scheduler/registry
|
|
48
|
+
// and spawn children under the wrong parent session. Each session gets its own
|
|
49
|
+
// Session instance below via makeSession(rootAgent, sessionId).
|
|
43
50
|
export function apply(ctx) {
|
|
44
51
|
ensurePresetInstalled(ctx.logger)
|
|
45
52
|
const subagents = ctx.subagents
|
|
@@ -50,7 +57,41 @@ export function apply(ctx) {
|
|
|
50
57
|
const subprocess = ctx.get('subprocess')
|
|
51
58
|
const sandboxPolicy = ctx.get('sandboxPolicy')
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
// ================= per-session registry =================
|
|
61
|
+
const sessions = new Map() // rootAgentId -> Session
|
|
62
|
+
const childOwner = new Map() // childId -> rootAgentId (route subagent/end back to its session)
|
|
63
|
+
|
|
64
|
+
function sessionIdOf(agent) { try { return (agent && agent.id) ? String(agent.id) : undefined } catch (e) { return undefined } }
|
|
65
|
+
// Walk up the durable session lineage to the top-level (root) agent of this session,
|
|
66
|
+
// so calls from a child agent (which inherits this preset) still route to its session.
|
|
67
|
+
function rootOf(agent) {
|
|
68
|
+
try {
|
|
69
|
+
let cur = agent
|
|
70
|
+
const seen = new Set()
|
|
71
|
+
while (cur) {
|
|
72
|
+
const id = cur.id
|
|
73
|
+
if (seen.has(id)) return cur
|
|
74
|
+
seen.add(id)
|
|
75
|
+
const parentId = (cur.session && cur.session.header) ? cur.session.header.parentSession : undefined
|
|
76
|
+
if (parentId === undefined) return cur
|
|
77
|
+
const parent = agents.get(parentId)
|
|
78
|
+
if (!parent) return cur
|
|
79
|
+
cur = parent
|
|
80
|
+
}
|
|
81
|
+
} catch (e) { /* fall through */ }
|
|
82
|
+
return agent
|
|
83
|
+
}
|
|
84
|
+
function getSession(agent) {
|
|
85
|
+
const root = rootOf(agent)
|
|
86
|
+
const sid = sessionIdOf(root)
|
|
87
|
+
if (sid === undefined) return undefined
|
|
88
|
+
let s = sessions.get(sid)
|
|
89
|
+
if (!s) { s = makeSession(root, sid); sessions.set(sid, s) }
|
|
90
|
+
return s
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ================= per-session plugin body =================
|
|
94
|
+
function makeSession(rootAgent, sessionId) {
|
|
54
95
|
let currentProject = 'default'
|
|
55
96
|
const DEFAULT_PARAMS = {
|
|
56
97
|
mode: 'auto',
|
|
@@ -80,6 +121,7 @@ export function apply(ctx) {
|
|
|
80
121
|
let decisionQueue = []
|
|
81
122
|
let tasks = {}
|
|
82
123
|
let tickInFlight = false
|
|
124
|
+
let lastTickAt = 0
|
|
83
125
|
let brainstormRetries = {}
|
|
84
126
|
let deriveRetries = {}
|
|
85
127
|
let solvedByVerified = {}
|
|
@@ -99,6 +141,7 @@ export function apply(ctx) {
|
|
|
99
141
|
function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
|
|
100
142
|
function frameworkRoot() { return projectRoot(currentProject) }
|
|
101
143
|
function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
|
|
144
|
+
function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
|
|
102
145
|
function getPolicy() { try { if (sandboxPolicy && rootAgent && rootAgent.session) return sandboxPolicy.resolve({ session: rootAgent.session }) } catch (e) {} try { if (sandboxPolicy) return sandboxPolicy.resolve({}) } catch (e) {} return undefined }
|
|
103
146
|
function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
|
|
104
147
|
function blocksToText(blocks) { if (!blocks) return ''; let out = ''; for (let i = 0; i < blocks.length; i++) { const b = blocks[i]; if (b && b.type === 'text' && typeof b.text === 'string') out += b.text + '\n' } return out.trim() }
|
|
@@ -169,8 +212,14 @@ export function apply(ctx) {
|
|
|
169
212
|
async function writeJson(rel, obj) { return await writeText(rel, JSON.stringify(obj, null, 2)) }
|
|
170
213
|
async function listFiles(rel) { try { const t = await fsTarget(rel); const s = await fs.stat(t); if (s === undefined) return []; const entries = await fs.listDir(t); return entries.filter(function (e) { return e && e.type === 'file' }).map(function (e) { return e.name }) } catch (e) { return [] } }
|
|
171
214
|
async function listDirsAt(base, rel) { try { const t = await fs.resolve(rel, { cwd: base }); const s = await fs.stat(t); if (s === undefined) return []; const entries = await fs.listDir(t); return entries.filter(function (e) { return e && e.type === 'directory' }).map(function (e) { return e.name }) } catch (e) { return [] } }
|
|
172
|
-
async function readCurrentProject() {
|
|
173
|
-
|
|
215
|
+
async function readCurrentProject() {
|
|
216
|
+
// 按会话隔离的 current 文件(多会话并行时互不覆盖);无则回退旧共享文件
|
|
217
|
+
try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd: vibeRoot() }); const s = await fs.stat(t); if (s !== undefined) { const txt = await fs.readText(t); const j = safeJson(txt, null); const p = (j && j.project) ? String(j.project) : 'default'; return slugify(p) } } catch (e) {}
|
|
218
|
+
try { const t = await fs.resolve('current.json', { cwd: vibeRoot() }); const s = await fs.stat(t); if (s === undefined) return 'default'; const txt = await fs.readText(t); const j = safeJson(txt, null); const p = (j && j.project) ? String(j.project) : 'default'; return slugify(p) } catch (e) { return 'default' }
|
|
219
|
+
}
|
|
220
|
+
async function writeCurrentProject() {
|
|
221
|
+
try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd: vibeRoot() }); await fs.writeText(t, JSON.stringify({ project: currentProject }), undefined, undefined, getPolicy()) } catch (e) {}
|
|
222
|
+
}
|
|
174
223
|
|
|
175
224
|
// ================= subprocess =================
|
|
176
225
|
function psQuote(p) { return "'" + String(p).replace(/'/g, "''") + "'" }
|
|
@@ -292,7 +341,7 @@ export function apply(ctx) {
|
|
|
292
341
|
function pickProvider() { try { const names = subagents.list ? subagents.list() : []; if (names.indexOf('spawn') !== -1) return 'spawn'; if (names.indexOf('fork') !== -1) return 'fork' } catch (e) {} return 'spawn' }
|
|
293
342
|
function childAgentOptions() { const o = {}; try { if (rootAgent && rootAgent.options) { if (rootAgent.options.provider) o.provider = rootAgent.options.provider; if (rootAgent.options.model) o.model = rootAgent.options.model } } catch (e) {} if (params.provider) o.provider = params.provider; if (params.model) o.model = params.model; return o }
|
|
294
343
|
function buildToolFilter(role) { const allow = role === 'solver' ? params.solverToolAllow : role === 'verifier' ? params.verifierToolAllow : undefined; const deny = role === 'solver' ? params.solverToolDeny : role === 'verifier' ? params.verifierToolDeny : undefined; const f = {}; if (Array.isArray(allow) && allow.length > 0) f.allow = allow.slice(); if (Array.isArray(deny) && deny.length > 0) f.deny = deny.slice(); return (f.allow || f.deny) ? f : undefined }
|
|
295
|
-
async function spawnChild(label, promptText, meta) { const request = { prompt: [textBlock(promptText)], parent: rootAgent, agentOptions: childAgentOptions() }; const tf = buildToolFilter(meta && meta.role); if (tf) request.toolFilter = tf; let started; try { started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) } catch (e) { if (request.toolFilter) { delete request.toolFilter; console.error('vibe-math: startContinuable with toolFilter failed, retrying without it: ' + String((e && e.message) || e)); started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) } else { throw e } } agentRegistry[started.childId] = Object.assign({ createdAt: now() }, meta || {}); scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1; await saveAll(); return started.childId }
|
|
344
|
+
async function spawnChild(label, promptText, meta) { const request = { prompt: [textBlock(promptText)], parent: rootAgent, agentOptions: childAgentOptions() }; const tf = buildToolFilter(meta && meta.role); if (tf) request.toolFilter = tf; let started; try { started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) } catch (e) { if (request.toolFilter) { delete request.toolFilter; console.error('vibe-math: startContinuable with toolFilter failed, retrying without it: ' + String((e && e.message) || e)); started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) } else { throw e } } agentRegistry[started.childId] = Object.assign({ createdAt: now() }, meta || {}); childOwner.set(started.childId, sessionId); scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1; await saveAll(); return started.childId }
|
|
296
345
|
async function followupChild(childId, promptText) { await subagents.followup(rootAgent, childId, [textBlock(promptText)], { source: { kind: 'user' }, signal: makeSignal(30000) }); scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1; await saveAll() }
|
|
297
346
|
async function interruptChild(childId) { try { subagents.interrupt(childId, { kind: 'ancestor', agent: rootAgent }) } catch (e) {} }
|
|
298
347
|
|
|
@@ -324,10 +373,10 @@ export function apply(ctx) {
|
|
|
324
373
|
function statusFromStop(stopReason) { return (stopReason === 'completed' || stopReason === 'max-tokens') ? 'continue' : 'dead-end' }
|
|
325
374
|
|
|
326
375
|
// ================= init / control =================
|
|
327
|
-
async function
|
|
328
|
-
|
|
329
|
-
async function startScheduler(
|
|
330
|
-
async function resumeScheduler(
|
|
376
|
+
async function init() {
|
|
377
|
+
if (!rootAgent) return { ok: false, message: 'no root agent available' }; currentProject = await readCurrentProject(); await ensureDirs(); if ((await readText('qs/qs.csv')) === undefined) await writeText('qs/qs.csv', 'id,description,priority,status,deps\n'); params = Object.assign({}, DEFAULT_PARAMS); await loadSettings(); await migrateLegacyParams(); await loadState(); scheduler.activeCount = 0; await saveAll(); return { ok: true } }
|
|
378
|
+
async function startScheduler() { const r = await init(); 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() } }
|
|
379
|
+
async function resumeScheduler() { const r = await init(); 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() } }
|
|
331
380
|
async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
|
|
332
381
|
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 } }
|
|
333
382
|
// auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
|
|
@@ -368,7 +417,7 @@ export function apply(ctx) {
|
|
|
368
417
|
|
|
369
418
|
// ================= scheduler =================
|
|
370
419
|
function scheduleTick() { tick().catch(function (e) { console.error('vibe-math tick error: ' + String((e && e.stack) || e)) }) }
|
|
371
|
-
async function tick() { if (tickInFlight) return; if (!rootAgent) return; if (!scheduler.running) return; if (scheduler.gate) return; tickInFlight = true; try { await processVerification(); await reconcileTasks(); await processPromotion(); await processDecider(); await processSolve(); await maybeWriteReport(false); const qs = await getQs(); const unsolved = qs.filter(function (q) { return q.status !== 'solved' && !solvedByVerified[q.id] }); if (unsolved.length === 0 && Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0) { scheduler.running = false; logActivity('stop', 'no unsolved problems, no active agents/tasks — scheduler stopped'); await saveAll(); await maybeWriteReport(true) } } finally { tickInFlight = false } }
|
|
420
|
+
async function tick() { if (tickInFlight) return; if (!rootAgent) return; if (!scheduler.running) return; if (scheduler.gate) return; tickInFlight = true; lastTickAt = now(); try { await processVerification(); await reconcileTasks(); await processPromotion(); await processDecider(); await processSolve(); await maybeWriteReport(false); const qs = await getQs(); const unsolved = qs.filter(function (q) { return q.status !== 'solved' && !solvedByVerified[q.id] }); if (unsolved.length === 0 && Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0) { scheduler.running = false; logActivity('stop', 'no unsolved problems, no active agents/tasks — scheduler stopped'); await saveAll(); await maybeWriteReport(true) } } finally { tickInFlight = false } }
|
|
372
421
|
async function processSolve() { if (scheduler.activeCount >= params.maxParallelThreshold) return; const qs = await getQs(); const unsolved = qs.filter(function (q) { return q.status !== 'solved' && !solvedByVerified[q.id] }).sort(function (a, b) { return a.priority - b.priority }); for (let i = 0; i < unsolved.length; i++) { if (scheduler.activeCount >= params.maxParallelThreshold) break; const q = unsolved[i]; const busy = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && (m.role === 'brainstorm' || m.role === 'solver' || m.role === 'derive') }); if (busy) continue; const prog = await readProgress(q.id); if (prog.length === 0) { if ((brainstormRetries[q.id] || 0) >= 3) { await writeProgress(q.id, [{ direction_id: 'd_' + shortId(), title: 'brainstorm failed', method: '', core_assumption: '', round: 0, status: 'dead-end', survival_probability: 0, dead_end_reason: 'brainstorm produced no directions after 3 attempts', lemmas: [], sub_routes: [], aux_hypotheses: [], updated_at: String(now()) }]); continue } brainstormRetries[q.id] = (brainstormRetries[q.id] || 0) + 1; const label = 'brainstorm:' + q.id; const promptText = brainstormPrompt(q); const r = await maybeGate('spawn', 'brainstorm for problem ' + q.id, { label: label, promptText: promptText, meta: { role: 'brainstorm', qid: q.id } }, async function (d) { await spawnChild(d.label, d.promptText, d.meta); return { spawned: true } }); if (r && r.gated) return } else { for (let j = 0; j < prog.length; j++) { if (scheduler.activeCount >= params.maxParallelThreshold) break; const dir = prog[j]; if (dir.status === 'success' || dir.status === 'dead-end') continue; const running = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && m.direction === dir.direction_id && m.role === 'solver' }); if (running) continue; const label = 'solver:' + q.id + ':' + dir.direction_id; const promptText = solverPrompt(q, dir, Math.max(1, dir.round + 1)); const r = await maybeGate('spawn', 'solver for problem ' + q.id + ' direction ' + dir.direction_id, { label: label, promptText: promptText, meta: { role: 'solver', qid: q.id, description: q.description, direction: dir.direction_id, round: Math.max(1, dir.round + 1) } }, async function (d) { await spawnChild(d.label, d.promptText, d.meta); return { spawned: true } }); if (r && r.gated) return } if (scheduler.activeCount < params.maxParallelThreshold) { const activeDirs = prog.filter(function (d) { return d.status !== 'success' && d.status !== 'dead-end' }); if (activeDirs.length === 0 && (deriveRetries[q.id] || 0) < 3) { deriveRetries[q.id] = (deriveRetries[q.id] || 0) + 1; const dlabel = 'derive:' + q.id; const dprompt = deriveDirectionsPrompt(q, prog); const dr = await maybeGate('spawn', 'derive new directions for problem ' + q.id, { label: dlabel, promptText: dprompt, meta: { role: 'derive', qid: q.id, description: q.description } }, async function (d) { await spawnChild(d.label, d.promptText, d.meta); return { spawned: true } }); if (dr && dr.gated) return } } } } }
|
|
373
422
|
|
|
374
423
|
// ================= child result handling =================
|
|
@@ -507,21 +556,17 @@ export function apply(ctx) {
|
|
|
507
556
|
async function handleDecider(childId, meta, output) { delete agentRegistry[childId]; const parsed = parseJson(output); const qid = parsed && parsed.solves_qid; const processed = (await readJson('VibeMath_State/decided_verified.json')) || []; if (processed.indexOf(meta.verifiedFile) === -1) processed.push(meta.verifiedFile); if (qid && qid !== 'null') { const qs = await getQs(); const q = qs.find(function (x) { return x.id === qid }); if (q && q.status !== 'solved') { q.status = 'solved'; solvedByVerified[qid] = true; await writeQs(qs); const newName = qid + '-的解法_' + shortId() + '.csv'; await atomicMove('Verified/' + meta.verifiedFile, 'Verified/' + newName); processed.push(newName); logActivity('decider', 'problem ' + qid + ' solved; Verified file renamed to ' + newName) } } await writeJson('VibeMath_State/decided_verified.json', processed) }
|
|
508
557
|
|
|
509
558
|
// ================= events / timer =================
|
|
510
|
-
|
|
511
|
-
|
|
559
|
+
// NOTE: subagent/end listener and the tick timer are registered ONCE at the
|
|
560
|
+
// apply level (below), routing through childOwner/sessions — NOT here, because
|
|
561
|
+
// the standing-mount plugin instance is shared by every session.
|
|
512
562
|
|
|
513
563
|
// ================= tools =================
|
|
514
564
|
function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
name: name, description: description, parameters: parameters,
|
|
518
|
-
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
|
|
519
|
-
execute: async function (args, exec) { try { const agent = (exec && exec.agent) || undefined; await resolveRootAgent(agent); if (rootAgent) currentProject = await readCurrentProject(); return JSON.stringify(await executeFn(args || {}, agent)) } catch (e) { return JSON.stringify({ ok: false, error: String((e && e.message) || e) }) } },
|
|
520
|
-
}))
|
|
521
|
-
}
|
|
565
|
+
const handlers = {}
|
|
566
|
+
function registerTool(name, description, parameters, executeFn) { handlers[name] = executeFn }
|
|
522
567
|
|
|
523
|
-
registerTool('vibe_math_start', 'Start (or restart) the Vibe Math scheduler for the current project.', objParams({}), async function (
|
|
524
|
-
registerTool('vibe_math_resume', 'Resume the scheduler after a checkpoint/restart.', objParams({}), async function (
|
|
568
|
+
registerTool('vibe_math_start', 'Start (or restart) the Vibe Math scheduler for the current project.', objParams({}), async function () { return await startScheduler() })
|
|
569
|
+
registerTool('vibe_math_resume', 'Resume the scheduler after a checkpoint/restart.', objParams({}), async function () { return await resumeScheduler() })
|
|
525
570
|
registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), async function () { return await pauseScheduler() })
|
|
526
571
|
registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
|
|
527
572
|
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { await refreshParams(); return await getStatus() })
|
|
@@ -542,9 +587,9 @@ export function apply(ctx) {
|
|
|
542
587
|
registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), async function (args) { await interruptChild(args.childId); return { ok: true, message: 'interrupt requested' } })
|
|
543
588
|
|
|
544
589
|
// ================= slash command /vibe =================
|
|
545
|
-
async function dispatchVibeCommand(cmd, args
|
|
546
|
-
if (cmd === 'start') return await startScheduler(
|
|
547
|
-
if (cmd === 'resume') return await resumeScheduler(
|
|
590
|
+
async function dispatchVibeCommand(cmd, args) {
|
|
591
|
+
if (cmd === 'start') return await startScheduler()
|
|
592
|
+
if (cmd === 'resume') return await resumeScheduler()
|
|
548
593
|
if (cmd === 'pause') return await pauseScheduler()
|
|
549
594
|
if (cmd === 'abort') return await abortScheduler()
|
|
550
595
|
if (cmd === 'status') { await refreshParams(); return await getStatus() }
|
|
@@ -563,17 +608,85 @@ export function apply(ctx) {
|
|
|
563
608
|
if (cmd === 'agents') { const out = []; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) { const m = agentRegistry[ids[i]]; out.push({ childId: ids[i], role: m.role, qid: m.qid, direction: m.direction, round: m.round }) } return { ok: true, agents: out } }
|
|
564
609
|
return { ok: false, usage: 'start | resume | pause | abort | status | report | mode <auto|manual> | setup | save | template [global|project] | add <id> <description> | project [list | new <name> | <name>] | decisions | agents', message: 'unknown /vibe subcommand: ' + (cmd || '(empty)') }
|
|
565
610
|
}
|
|
611
|
+
|
|
612
|
+
// ================= session surface =================
|
|
613
|
+
return {
|
|
614
|
+
sessionId: sessionId,
|
|
615
|
+
scheduler: scheduler,
|
|
616
|
+
tickInFlight: tickInFlight,
|
|
617
|
+
scheduleTick: scheduleTick,
|
|
618
|
+
onChildEnd: onChildEnd,
|
|
619
|
+
dispatchVibeCommand: dispatchVibeCommand,
|
|
620
|
+
handlers: handlers,
|
|
621
|
+
// 每次工具调用前同步当前项目(按会话读 current.json;多会话互不干扰)
|
|
622
|
+
refreshProject: async function () { if (rootAgent) currentProject = await readCurrentProject() },
|
|
623
|
+
getRunning: function () { return scheduler.running },
|
|
624
|
+
// 会话自己的心跳节流:timer 每 1s 询问是否到点;tick 执行时刷新 lastTickAt
|
|
625
|
+
tickDue: function () { const iv = Math.max(200, Number(params.tickIntervalMs) || 2000); return (now() - lastTickAt) >= iv },
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// ================= apply-level registrations (ONCE per preset) =================
|
|
630
|
+
function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
|
|
631
|
+
function registerTool(name, description, parameters, handlerName) {
|
|
632
|
+
ctx.effect(() => tools.register({
|
|
633
|
+
name: name, description: description, parameters: parameters,
|
|
634
|
+
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
|
|
635
|
+
execute: async function (args, exec) {
|
|
636
|
+
try {
|
|
637
|
+
const s = getSession(exec && exec.agent)
|
|
638
|
+
if (!s) return JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' })
|
|
639
|
+
await s.refreshProject()
|
|
640
|
+
return JSON.stringify(await s.handlers[handlerName](args || {}, exec && exec.agent))
|
|
641
|
+
} catch (e) { return JSON.stringify({ ok: false, error: String((e && e.message) || e) }) }
|
|
642
|
+
},
|
|
643
|
+
}))
|
|
644
|
+
}
|
|
645
|
+
registerTool('vibe_math_start', 'Start (or restart) the Vibe Math scheduler for the current project.', objParams({}), 'vibe_math_start')
|
|
646
|
+
registerTool('vibe_math_resume', 'Resume the scheduler after a checkpoint/restart.', objParams({}), 'vibe_math_resume')
|
|
647
|
+
registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), 'vibe_math_pause')
|
|
648
|
+
registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), 'vibe_math_abort')
|
|
649
|
+
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), 'vibe_math_status')
|
|
650
|
+
registerTool('vibe_math_report', 'Return the full progress report (status + recent activity + params) and write it to Progress_Logs/report.json.', objParams({}), 'vibe_math_report')
|
|
651
|
+
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']), 'vibe_math_set_mode')
|
|
652
|
+
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' } }), 'vibe_math_set_params')
|
|
653
|
+
registerTool('vibe_math_setup', 'Return the interactive parameter schema (each param: name, type, current, default, description, options, suggestion) for guided configuration.', objParams({}), 'vibe_math_setup')
|
|
654
|
+
registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), 'vibe_math_save_settings')
|
|
655
|
+
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'] } }), 'vibe_math_template')
|
|
656
|
+
registerTool('vibe_math_add_problem', 'Add a problem to the current project qs.csv.', objParams({ id: { type: 'string' }, description: { type: 'string' }, priority: { type: 'integer' } }, ['id', 'description']), 'vibe_math_add_problem')
|
|
657
|
+
registerTool('vibe_math_new_project', 'Create a new math project folder and switch to it.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_new_project')
|
|
658
|
+
registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_set_project')
|
|
659
|
+
registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), 'vibe_math_list_projects')
|
|
660
|
+
registerTool('vibe_math_list_decisions', 'List pending manual decisions.', objParams({}), 'vibe_math_list_decisions')
|
|
661
|
+
registerTool('vibe_math_decide', 'Resolve a pending manual decision.', objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'string', enum: ['true', 'false'] } }, ['id', 'action']), 'vibe_math_decide')
|
|
662
|
+
registerTool('vibe_math_list_agents', 'List tracked sub-agents (child sessions).', objParams({}), 'vibe_math_list_agents')
|
|
663
|
+
registerTool('vibe_math_message_agent', 'Send a message to a tracked child agent (next turn).', objParams({ childId: { type: 'string' }, message: { type: 'string' } }, ['childId', 'message']), 'vibe_math_message_agent')
|
|
664
|
+
registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), 'vibe_math_interrupt_agent')
|
|
665
|
+
|
|
666
|
+
// /vibe slash command (registered once; routed per session)
|
|
566
667
|
ctx.effect(() => commands.register({
|
|
567
668
|
name: 'vibe',
|
|
568
669
|
description: 'control the Vibe Math solver (start/pause/projects/setup/save/decisions/agents)',
|
|
569
670
|
input: { hint: '[start|resume|pause|abort|status|report|mode <auto|manual>|setup|save|template [global|project]|add <id> <desc>|project [list|new <name>|<name>]|decisions|agents]' },
|
|
570
671
|
handler: async function (invocation) {
|
|
672
|
+
const s = getSession(invocation && invocation.agent)
|
|
673
|
+
if (!s) return { kind: 'success', text: JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' }) }
|
|
571
674
|
const line = String(invocation && invocation.rawInput ? invocation.rawInput : '').trim()
|
|
572
675
|
const parts = line.length > 0 ? line.split(/\s+/) : []
|
|
573
676
|
const cmd = parts[0] || ''
|
|
574
677
|
const rest = parts.slice(1)
|
|
575
|
-
const result = await dispatchVibeCommand(cmd, rest
|
|
678
|
+
const result = await s.dispatchVibeCommand(cmd, rest)
|
|
576
679
|
return { kind: 'success', text: JSON.stringify(result, null, 2) }
|
|
577
680
|
},
|
|
578
681
|
}))
|
|
682
|
+
|
|
683
|
+
// subagent/end (registered once; routed to the owning session via childOwner)
|
|
684
|
+
ctx.on('subagent/end', function (info) {
|
|
685
|
+
const sid = childOwner.get(info.id)
|
|
686
|
+
const s = sid !== undefined ? sessions.get(sid) : undefined
|
|
687
|
+
if (s) s.onChildEnd(info).catch(function (e) { console.error('vibe-math onChildEnd reject: ' + String((e && e.stack) || e)) })
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
// tick timer (registered once; ticks every running session at its own pace)
|
|
691
|
+
ctx.effect(() => { const t = setInterval(function () { for (const s of sessions.values()) { if (s.getRunning() && !s.tickInFlight && s.tickDue() && s.scheduler.gate === null) s.scheduleTick() } }, 1000); return () => clearInterval(t) })
|
|
579
692
|
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
每隔一段时间(如:当有变动、出现新进展或发生新调用,或者当有代理的会话结束时),委托主代理总结汇告当前进展、各代理情况/进度等信息。
|
|
10
10
|
|
|
11
|
+
- 3. 多会话并行隔离:DSH 的 agent preset 是 standing mount——同一 preset 的所有会话共享同一个插件实例,因此插件必须自行按根会话 id 隔离内部状态(rootAgent、当前项目、调度器、代理注册表、决策队列、参数、任务栈等全部 per-session)。两个会话可同时各跑一个项目,各自的子代理挂在各自会话名下、互不干扰;当前项目按会话持久化(`VibeMath/current.<会话id>.json`,兼容读取旧 `current.json`)。
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
# Vibe Mathematics —— 多代理数学问题求解与验证框架
|
|
13
15
|
|
|
@@ -19,6 +19,13 @@
|
|
|
19
19
|
export const name = 'vibe-math-v2'
|
|
20
20
|
export const inject = ['subagents', 'agents', 'fs', 'tools', 'commands']
|
|
21
21
|
|
|
22
|
+
// Standing mount: DSH mounts each agent preset ONCE per preset and joins every
|
|
23
|
+
// session that names it to that SAME plugin instance (see @deepseek-ai/dsh-agent-presets).
|
|
24
|
+
// This plugin must therefore isolate ALL per-session state itself, keyed by the
|
|
25
|
+
// root agent (session) id — otherwise two sessions running the preset at the same
|
|
26
|
+
// time (e.g. project A and project B) would share one rootAgent/scheduler/registry
|
|
27
|
+
// and spawn children under the wrong parent session. Each session gets its own
|
|
28
|
+
// Session instance below via makeSession(rootAgent, sessionId).
|
|
22
29
|
export function apply(ctx) {
|
|
23
30
|
const subagents = ctx.subagents
|
|
24
31
|
const agents = ctx.agents
|
|
@@ -28,7 +35,41 @@ export function apply(ctx) {
|
|
|
28
35
|
const subprocess = ctx.get('subprocess')
|
|
29
36
|
const sandboxPolicy = ctx.get('sandboxPolicy')
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
// ================= per-session registry =================
|
|
39
|
+
const sessions = new Map() // rootAgentId -> Session
|
|
40
|
+
const childOwner = new Map() // childId -> rootAgentId (route subagent/end back to its session)
|
|
41
|
+
|
|
42
|
+
function sessionIdOf(agent) { try { return (agent && agent.id) ? String(agent.id) : undefined } catch (e) { return undefined } }
|
|
43
|
+
// Walk up the durable session lineage to the top-level (root) agent of this session,
|
|
44
|
+
// so calls from a child agent (which inherits this preset) still route to its session.
|
|
45
|
+
function rootOf(agent) {
|
|
46
|
+
try {
|
|
47
|
+
let cur = agent
|
|
48
|
+
const seen = new Set()
|
|
49
|
+
while (cur) {
|
|
50
|
+
const id = cur.id
|
|
51
|
+
if (seen.has(id)) return cur
|
|
52
|
+
seen.add(id)
|
|
53
|
+
const parentId = (cur.session && cur.session.header) ? cur.session.header.parentSession : undefined
|
|
54
|
+
if (parentId === undefined) return cur
|
|
55
|
+
const parent = agents.get(parentId)
|
|
56
|
+
if (!parent) return cur
|
|
57
|
+
cur = parent
|
|
58
|
+
}
|
|
59
|
+
} catch (e) { /* fall through */ }
|
|
60
|
+
return agent
|
|
61
|
+
}
|
|
62
|
+
function getSession(agent) {
|
|
63
|
+
const root = rootOf(agent)
|
|
64
|
+
const sid = sessionIdOf(root)
|
|
65
|
+
if (sid === undefined) return undefined
|
|
66
|
+
let s = sessions.get(sid)
|
|
67
|
+
if (!s) { s = makeSession(root, sid); sessions.set(sid, s) }
|
|
68
|
+
return s
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ================= per-session plugin body =================
|
|
72
|
+
function makeSession(rootAgent, sessionId) {
|
|
32
73
|
let currentProject = 'default'
|
|
33
74
|
const DEFAULT_PARAMS = {
|
|
34
75
|
mode: 'auto', // auto | manual
|
|
@@ -74,6 +115,7 @@ export function apply(ctx) {
|
|
|
74
115
|
let lastPushReport = 0
|
|
75
116
|
let reportDirty = false
|
|
76
117
|
let tickInFlight = false
|
|
118
|
+
let lastTickAt = 0
|
|
77
119
|
let explorerRetries = {}
|
|
78
120
|
// Process epoch: written to state at init; a DIFFERENT persisted epoch means a
|
|
79
121
|
// previous DSH process wrote this state (in-flight children are gone), while an
|
|
@@ -91,6 +133,7 @@ export function apply(ctx) {
|
|
|
91
133
|
function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
|
|
92
134
|
function frameworkRoot() { return projectRoot(currentProject) }
|
|
93
135
|
function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
|
|
136
|
+
function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
|
|
94
137
|
function getPolicy() { try { if (sandboxPolicy && rootAgent && rootAgent.session) return sandboxPolicy.resolve({ session: rootAgent.session }) } catch (e) {} try { if (sandboxPolicy) return sandboxPolicy.resolve({}) } catch (e) {} return undefined }
|
|
95
138
|
function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
|
|
96
139
|
function blocksToText(blocks) { if (!blocks) return ''; let out = ''; for (let i = 0; i < blocks.length; i++) { const b = blocks[i]; if (b && b.type === 'text' && typeof b.text === 'string') out += b.text + '\n' } return out.trim() }
|
|
@@ -166,8 +209,14 @@ export function apply(ctx) {
|
|
|
166
209
|
async function listDirsAt(base, rel) { try { const t = await fs.resolve(rel, { cwd: base }); const s = await fs.stat(t); if (s === undefined) return []; const entries = await fs.listDir(t); return entries.filter(function (e) { return e && e.type === 'directory' }).map(function (e) { return e.name }) } catch (e) { return [] } }
|
|
167
210
|
async function readTextAbs(path) { try { const t = await fs.resolve(path); const s = await fs.stat(t); if (s === undefined) return undefined; return await fs.readText(t) } catch (e) { return undefined } }
|
|
168
211
|
async function writeTextAbs(path, content) { try { const t = await fs.resolve(path); await fs.writeText(t, content, undefined, undefined, getPolicy()); return true } catch (e) { return false } }
|
|
169
|
-
async function readCurrentProject() {
|
|
170
|
-
|
|
212
|
+
async function readCurrentProject() {
|
|
213
|
+
// 按会话隔离的 current 文件(多会话并行时互不覆盖);无则回退旧共享文件
|
|
214
|
+
try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd: vibeRoot() }); const s = await fs.stat(t); if (s !== undefined) { const txt = await fs.readText(t); const j = safeJson(txt, null); const p = (j && j.project) ? String(j.project) : 'default'; return slugify(p) } } catch (e) {}
|
|
215
|
+
try { const t = await fs.resolve('current.json', { cwd: vibeRoot() }); const s = await fs.stat(t); if (s === undefined) return 'default'; const txt = await fs.readText(t); const j = safeJson(txt, null); const p = (j && j.project) ? String(j.project) : 'default'; return slugify(p) } catch (e) { return 'default' }
|
|
216
|
+
}
|
|
217
|
+
async function writeCurrentProject() {
|
|
218
|
+
try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd: vibeRoot() }); await fs.writeText(t, JSON.stringify({ project: currentProject }), undefined, undefined, getPolicy()) } catch (e) {}
|
|
219
|
+
}
|
|
171
220
|
|
|
172
221
|
// ================= subprocess =================
|
|
173
222
|
function psQuote(p) { return "'" + String(p).replace(/'/g, "''") + "'" }
|
|
@@ -388,6 +437,7 @@ export function apply(ctx) {
|
|
|
388
437
|
if (request.toolFilter) { delete request.toolFilter; console.error('vibe-math-v2: startContinuable with toolFilter failed, retrying without it: ' + String((e && e.message) || e)); started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) } else { throw e }
|
|
389
438
|
}
|
|
390
439
|
agentRegistry[started.childId] = Object.assign({ createdAt: now() }, meta || {})
|
|
440
|
+
childOwner.set(started.childId, sessionId)
|
|
391
441
|
scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1
|
|
392
442
|
await saveAll(); return started.childId
|
|
393
443
|
}
|
|
@@ -477,6 +527,7 @@ export function apply(ctx) {
|
|
|
477
527
|
function directionSummary(d) {
|
|
478
528
|
return 'id ' + d.id + '「' + d.title + '」method=' + d.method + ' | round=' + d.round + ' status=' + d.status +
|
|
479
529
|
' survival=' + d.survival +
|
|
530
|
+
(d.lemmas && d.lemmas.length ? ' | lemmas: ' + d.lemmas.map(function (l) { return '「' + l.title + '」(' + l.id + ')' }).join('; ') : '') +
|
|
480
531
|
(d.routes && d.routes.length ? ' | routes: ' + d.routes.map(function (r) { return r.title + '[' + (r.feasibility_signal || '') + ']' }).join('; ') : '') +
|
|
481
532
|
(d.lessons && d.lessons.length ? ' | lessons: ' + d.lessons.join('; ') : '') +
|
|
482
533
|
(d.blockers && d.blockers.length ? ' | blockers: ' + d.blockers.join('; ') : '')
|
|
@@ -567,6 +618,7 @@ export function apply(ctx) {
|
|
|
567
618
|
async function tick() {
|
|
568
619
|
if (tickInFlight) return; if (!rootAgent) return; if (!scheduler.running) return; if (scheduler.gate) return
|
|
569
620
|
tickInFlight = true
|
|
621
|
+
lastTickAt = now()
|
|
570
622
|
try {
|
|
571
623
|
await processStatusUpdates()
|
|
572
624
|
await processPriorityAdjust()
|
|
@@ -840,7 +892,7 @@ export function apply(ctx) {
|
|
|
840
892
|
if (parsed.lessons) dir.lessons = (dir.lessons || []).concat(parsed.lessons)
|
|
841
893
|
if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason
|
|
842
894
|
if (typeof parsed.survival_probability === 'number') dir.survival = clamp01(parsed.survival_probability)
|
|
843
|
-
if (parsed.lemmas && parsed.lemmas.length) { for (let i = 0; i < parsed.lemmas.length; i++) await addLemmaAsProposition(qid, parsed.lemmas[i]) }
|
|
895
|
+
if (parsed.lemmas && parsed.lemmas.length) { for (let i = 0; i < parsed.lemmas.length; i++) { const lid = await addLemmaAsProposition(qid, parsed.lemmas[i]); if (lid) { dir.lemmas = dir.lemmas || []; dir.lemmas.push({ id: lid, title: parsed.lemmas[i].title || '' }) } } }
|
|
844
896
|
if (parsed.sub_questions && parsed.sub_questions.length) { for (let i = 0; i < parsed.sub_questions.length; i++) { const sq = parsed.sub_questions[i]; if (sq && sq.q_sub_statement && dir.sub_questions && dir.sub_questions.some(function (x) { return x.statement === sq.q_sub_statement })) continue; const rec = await addSubQuestion(qid, dirId, sq); if (rec) { dir.sub_questions = dir.sub_questions || []; dir.sub_questions.push(rec) } } }
|
|
845
897
|
}
|
|
846
898
|
if (status === 'success') {
|
|
@@ -902,6 +954,7 @@ export function apply(ctx) {
|
|
|
902
954
|
}
|
|
903
955
|
await upsertProposition(p)
|
|
904
956
|
logActivity('proposition', 'lemma「' + lemma.title + '」→ ' + p.id)
|
|
957
|
+
return p.id
|
|
905
958
|
}
|
|
906
959
|
// 点5(q_sub 严格化):solver 报告子问题 q_sub 时,注册三个对象:
|
|
907
960
|
// 1) q_sub 本身(问题类,完整陈述)入 qs.json;
|
|
@@ -1185,9 +1238,8 @@ export function apply(ctx) {
|
|
|
1185
1238
|
}
|
|
1186
1239
|
|
|
1187
1240
|
// ================= init / control =================
|
|
1188
|
-
async function
|
|
1189
|
-
|
|
1190
|
-
await resolveRootAgent(agent); if (!rootAgent) return { ok: false, message: 'no root agent available' }
|
|
1241
|
+
async function init(fresh) {
|
|
1242
|
+
if (!rootAgent) return { ok: false, message: 'no root agent available' }
|
|
1191
1243
|
currentProject = await readCurrentProject(); await ensureDirs()
|
|
1192
1244
|
if ((await readJson('qs/qs.json')) === undefined) await writeJson('qs/qs.json', [])
|
|
1193
1245
|
params = Object.assign({}, DEFAULT_PARAMS); await loadSettings(); await migrateLegacyParams(); await loadState()
|
|
@@ -1208,8 +1260,8 @@ export function apply(ctx) {
|
|
|
1208
1260
|
await saveAll()
|
|
1209
1261
|
return { ok: true }
|
|
1210
1262
|
}
|
|
1211
|
-
async function startScheduler(
|
|
1212
|
-
async function resumeScheduler(
|
|
1263
|
+
async function startScheduler() { const r = await init(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() } }
|
|
1264
|
+
async function resumeScheduler() { const r = await init(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() } }
|
|
1213
1265
|
async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
|
|
1214
1266
|
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 } }
|
|
1215
1267
|
// auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
|
|
@@ -1253,20 +1305,16 @@ export function apply(ctx) {
|
|
|
1253
1305
|
}
|
|
1254
1306
|
|
|
1255
1307
|
// ================= events / timer =================
|
|
1256
|
-
|
|
1257
|
-
|
|
1308
|
+
// NOTE: subagent/end listener and the tick timer are registered ONCE at the
|
|
1309
|
+
// apply level (below), routing through childOwner/sessions — NOT here, because
|
|
1310
|
+
// the standing-mount plugin instance is shared by every session.
|
|
1258
1311
|
|
|
1259
1312
|
// ================= tools =================
|
|
1260
1313
|
function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
execute: async function (args, exec) { try { const agent = (exec && exec.agent) || undefined; await resolveRootAgent(agent); if (rootAgent) currentProject = await readCurrentProject(); return JSON.stringify(await executeFn(args || {}, agent)) } catch (e) { return JSON.stringify({ ok: false, error: String((e && e.message) || e) }) } },
|
|
1266
|
-
}))
|
|
1267
|
-
}
|
|
1268
|
-
registerTool('vibe_math_start', 'Start (or restart) the Vibe Math V2 scheduler for the current project.', objParams({}), async function (args, agent) { return await startScheduler(agent) })
|
|
1269
|
-
registerTool('vibe_math_resume', 'Resume the Vibe Math V2 scheduler after a checkpoint/restart.', objParams({}), async function (args, agent) { return await resumeScheduler(agent) })
|
|
1314
|
+
const handlers = {}
|
|
1315
|
+
function registerTool(name, description, parameters, executeFn) { handlers[name] = executeFn }
|
|
1316
|
+
registerTool('vibe_math_start', 'Start (or restart) the Vibe Math V2 scheduler for the current project.', objParams({}), async function () { return await startScheduler() })
|
|
1317
|
+
registerTool('vibe_math_resume', 'Resume the Vibe Math V2 scheduler after a checkpoint/restart.', objParams({}), async function () { return await resumeScheduler() })
|
|
1270
1318
|
registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), async function () { return await pauseScheduler() })
|
|
1271
1319
|
registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
|
|
1272
1320
|
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { await refreshParams(); return await getStatus() })
|
|
@@ -1292,9 +1340,9 @@ export function apply(ctx) {
|
|
|
1292
1340
|
registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), async function (args) { await interruptChild(args.childId); return { ok: true, message: 'interrupt requested' } })
|
|
1293
1341
|
|
|
1294
1342
|
// ================= slash command /vibe =================
|
|
1295
|
-
async function dispatchVibeCommand(cmd, args
|
|
1296
|
-
if (cmd === 'start') return await startScheduler(
|
|
1297
|
-
if (cmd === 'resume') return await resumeScheduler(
|
|
1343
|
+
async function dispatchVibeCommand(cmd, args) {
|
|
1344
|
+
if (cmd === 'start') return await startScheduler()
|
|
1345
|
+
if (cmd === 'resume') return await resumeScheduler()
|
|
1298
1346
|
if (cmd === 'pause') return await pauseScheduler()
|
|
1299
1347
|
if (cmd === 'abort') return await abortScheduler()
|
|
1300
1348
|
if (cmd === 'status') { await refreshParams(); return await getStatus() }
|
|
@@ -1315,17 +1363,87 @@ export function apply(ctx) {
|
|
|
1315
1363
|
if (cmd === 'agents') { const out = []; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) { const m = agentRegistry[ids[i]]; out.push({ childId: ids[i], role: m.role, qid: m.qid, direction: m.direction, round: m.round }) } return { ok: true, agents: out } }
|
|
1316
1364
|
return { ok: false, usage: 'start | resume | pause | abort | status | report | mode <auto|manual> | setup | save | template [global|project] | add <id> <desc> | add-proposition <id> <概述> | list-propositions | project [list|new <name>|<name>] | decisions | agents', message: 'unknown /vibe subcommand: ' + (cmd || '(empty)') }
|
|
1317
1365
|
}
|
|
1366
|
+
|
|
1367
|
+
// ================= session surface =================
|
|
1368
|
+
return {
|
|
1369
|
+
sessionId: sessionId,
|
|
1370
|
+
scheduler: scheduler,
|
|
1371
|
+
tickInFlight: tickInFlight,
|
|
1372
|
+
scheduleTick: scheduleTick,
|
|
1373
|
+
onChildEnd: onChildEnd,
|
|
1374
|
+
dispatchVibeCommand: dispatchVibeCommand,
|
|
1375
|
+
handlers: handlers,
|
|
1376
|
+
// 每次工具调用前同步当前项目(按会话读 current.json;多会话互不干扰)
|
|
1377
|
+
refreshProject: async function () { if (rootAgent) currentProject = await readCurrentProject() },
|
|
1378
|
+
getRunning: function () { return scheduler.running },
|
|
1379
|
+
// 会话自己的心跳节流:timer 每 1s 询问是否到点;tick 执行时刷新 lastTickAt
|
|
1380
|
+
tickDue: function () { const iv = Math.max(200, Number(params.tickIntervalMs) || 2000); return (now() - lastTickAt) >= iv },
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// ================= apply-level registrations (ONCE per preset) =================
|
|
1385
|
+
function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
|
|
1386
|
+
function registerTool(name, description, parameters, handlerName) {
|
|
1387
|
+
ctx.effect(() => tools.register({
|
|
1388
|
+
name: name, description: description, parameters: parameters,
|
|
1389
|
+
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
|
|
1390
|
+
execute: async function (args, exec) {
|
|
1391
|
+
try {
|
|
1392
|
+
const s = getSession(exec && exec.agent)
|
|
1393
|
+
if (!s) return JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' })
|
|
1394
|
+
await s.refreshProject()
|
|
1395
|
+
return JSON.stringify(await s.handlers[handlerName](args || {}, exec && exec.agent))
|
|
1396
|
+
} catch (e) { return JSON.stringify({ ok: false, error: String((e && e.message) || e) }) }
|
|
1397
|
+
},
|
|
1398
|
+
}))
|
|
1399
|
+
}
|
|
1400
|
+
registerTool('vibe_math_start', 'Start (or restart) the Vibe Math V2 scheduler for the current project.', objParams({}), 'vibe_math_start')
|
|
1401
|
+
registerTool('vibe_math_resume', 'Resume the Vibe Math V2 scheduler after a checkpoint/restart.', objParams({}), 'vibe_math_resume')
|
|
1402
|
+
registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), 'vibe_math_pause')
|
|
1403
|
+
registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), 'vibe_math_abort')
|
|
1404
|
+
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), 'vibe_math_status')
|
|
1405
|
+
registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), 'vibe_math_report')
|
|
1406
|
+
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']), 'vibe_math_set_mode')
|
|
1407
|
+
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' }, explorerPersona: { type: 'string' }, knowledgeContext: { 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' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' } }), 'vibe_math_set_params')
|
|
1408
|
+
registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), 'vibe_math_setup')
|
|
1409
|
+
registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), 'vibe_math_save_settings')
|
|
1410
|
+
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'] } }), 'vibe_math_template')
|
|
1411
|
+
registerTool('vibe_math_add_problem', 'Add a problem to the current project qs/qs.json.', objParams({ id: { type: 'string' }, description: { type: 'string' }, priority: { type: 'integer' } }, ['id', 'description']), 'vibe_math_add_problem')
|
|
1412
|
+
registerTool('vibe_math_add_proposition', 'Add a proposition to Propos/ (with 概述, 布尔估计, 细类型, 优先级, 价值/关键性).', objParams({ id: { type: 'string' }, 概述: { type: 'string' }, 布尔估计: { type: 'number' }, 优先级: { type: 'integer' }, '价值/关键性': { type: 'number' }, 细类型: { type: 'object' } }, ['id', '概述']), 'vibe_math_add_proposition')
|
|
1413
|
+
registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index: id, 概述, 布尔估计, 优先级, 价值/关键性, category).', objParams({}), 'vibe_math_list_propositions')
|
|
1414
|
+
registerTool('vibe_math_new_project', 'Create a new math project folder and switch to it.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_new_project')
|
|
1415
|
+
registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_set_project')
|
|
1416
|
+
registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), 'vibe_math_list_projects')
|
|
1417
|
+
registerTool('vibe_math_list_decisions', 'List pending manual decisions.', objParams({}), 'vibe_math_list_decisions')
|
|
1418
|
+
registerTool('vibe_math_decide', 'Resolve a pending manual decision (verdict override uses verdict: 1|0).', objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'number' } }, ['id', 'action']), 'vibe_math_decide')
|
|
1419
|
+
registerTool('vibe_math_list_agents', 'List tracked sub-agents (child sessions).', objParams({}), 'vibe_math_list_agents')
|
|
1420
|
+
registerTool('vibe_math_message_agent', 'Send a message to a tracked child agent (next turn).', objParams({ childId: { type: 'string' }, message: { type: 'string' } }, ['childId', 'message']), 'vibe_math_message_agent')
|
|
1421
|
+
registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), 'vibe_math_interrupt_agent')
|
|
1422
|
+
|
|
1423
|
+
// /vibe slash command (registered once; routed per session)
|
|
1318
1424
|
ctx.effect(() => commands.register({
|
|
1319
1425
|
name: 'vibe',
|
|
1320
1426
|
description: 'control the Vibe Math V2 solver (start/pause/projects/setup/save/decisions/agents/propositions)',
|
|
1321
1427
|
input: { hint: '[start|resume|pause|abort|status|report|mode <auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|project [list|new <name>|<name>]|decisions|agents]' },
|
|
1322
1428
|
handler: async function (invocation) {
|
|
1429
|
+
const s = getSession(invocation && invocation.agent)
|
|
1430
|
+
if (!s) return { kind: 'success', text: JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' }) }
|
|
1323
1431
|
const line = String(invocation && invocation.rawInput ? invocation.rawInput : '').trim()
|
|
1324
1432
|
const parts = line.length > 0 ? line.split(/\s+/) : []
|
|
1325
1433
|
const cmd = parts[0] || ''
|
|
1326
1434
|
const rest = parts.slice(1)
|
|
1327
|
-
const result = await dispatchVibeCommand(cmd, rest
|
|
1435
|
+
const result = await s.dispatchVibeCommand(cmd, rest)
|
|
1328
1436
|
return { kind: 'success', text: JSON.stringify(result, null, 2) }
|
|
1329
1437
|
},
|
|
1330
1438
|
}))
|
|
1439
|
+
|
|
1440
|
+
// subagent/end (registered once; routed to the owning session via childOwner)
|
|
1441
|
+
ctx.on('subagent/end', function (info) {
|
|
1442
|
+
const sid = childOwner.get(info.id)
|
|
1443
|
+
const s = sid !== undefined ? sessions.get(sid) : undefined
|
|
1444
|
+
if (s) s.onChildEnd(info).catch(function (e) { console.error('vibe-math-v2 onChildEnd reject: ' + String((e && e.stack) || e)) })
|
|
1445
|
+
})
|
|
1446
|
+
|
|
1447
|
+
// tick timer (registered once; ticks every running session at its own pace)
|
|
1448
|
+
ctx.effect(() => { const t = setInterval(function () { for (const s of sessions.values()) { if (s.getRunning() && !s.tickInFlight && s.tickDue() && s.scheduler.gate === null) s.scheduleTick() } }, 1000); return () => clearInterval(t) })
|
|
1331
1449
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
1. **断点续跑**:保存各代理的对话记录及任务栈,支持状态恢复。
|
|
6
6
|
2. **人工干预**:允许在任意时刻手动介入工作流,例如调整代理参数、控制会话、决策关键节点,并可随时在“人工 / 自动(预设)”模式间切换。
|
|
7
7
|
3. **进度汇报**:每隔一段时间(如发生变动、出现新进展、发生新调用或代理会话结束时),委托主代理汇总当前进展及各代理状态。
|
|
8
|
+
4. **多会话并行隔离**:DSH 的 agent preset 是 standing mount——同一 preset 的所有会话共享同一个插件实例,因此插件**必须自行按根会话 id 隔离内部状态**(`rootAgent`、当前项目、调度器、代理注册表、决策队列、参数、任务栈等全部 per-session)。两个会话可同时各跑一个项目,各自的子代理挂在各自会话名下、互不干扰;当前项目按会话持久化(`VibeMath/current.<会话id>.json`,兼容读取旧 `current.json`)。
|
|
8
9
|
|
|
9
10
|
---
|
|
10
11
|
|