dsh-vibe-math 1.4.0 → 1.4.1

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 CHANGED
@@ -65,6 +65,8 @@
65
65
  > 🔧 **v1.3.9 进一步按哲学打磨**:**① 会议议程来源**确认是非 bug(r-1 的 `propose_meeting` 发起);**② 压缩后重申核心规则**——每次压缩触发时在提示开头重申短核心规则(治"压缩遗忘规则"),其余轮仍极简;**③ 验证 `verdict` 明确为 0–1 正确概率**——1=绝对为真/判真、0=绝对为假/判假、0.5=不确定,"全体一致判真(verdict=1)/判假(verdict=0)才算数",未全票留库附平均正确概率(兼容旧字符串);**④ 创建项目不立即启动**——新增 `vibe_v4_configure {project?, problem?, params?}` 只建/配项目与参数(持久化 `State/settings.json`)不唤醒常驻,随后 `vibe_v4_start` 才启动;支持设置**项目名**。详见 §22。
66
66
  >
67
67
  > 🔧 **v1.4.0 verdict 改为纯概率数值 + 全面审计修复**:**① `verdict` 是纯 0–1 正确概率(程度),不再二分类**——仅当**全体一致给 1(真)或全体一致给 0(假)**才按真/假写入 Verified/,否则只作为概率数值保留、附全组平均正确概率(0.97 不再算"真",属更严格的绝对一致口径);**② 审计修复**:`resume` 补 `loadSettings()`(跨进程不再重置参数)、`configure` 直接写出问题卡、补上缺失的 `/v4 set` 分支、真实 `/compact` 成功后置 `needCompact` 以**重申核心规则**。详见 §23。
68
+ >
69
+ > 🔧 **v1.4.1 自主发明理论 + 模型/工具权限参数 + 压缩重申泄漏修复**:**① 初始提示**告知常驻可(不强迫)**自主构建新的理论框架/工具**——抽象化/一般化出更一般的结构并不断完善、推得定理性质(类比群论/泛函分析的发明),并阐明对原问题的价值;**② 补齐"模型继承 + 工具权限"参数**——`model`/`provider`(空 = 常驻继承主代理的 LLM 路由,此前为声明未用)与 `toolAllow`/`toolDeny`(经 `startContinuable` 的 `toolFilter` 做作用域 `tools.restrict()`,空 = 继承全部工具)真正接入 `spawnResident`;**③ 修复"[核心规则重申]+[CONTEXT COMPACT]"在提示开头重复泄漏**——根因是压缩指令被注入到 meeting/verify 唤醒而其回复从不释放 `needCompact`,标志卡死后每轮重复;现**只对 normal 研究轮注入完整压缩指令**、`needCompact` 只重申一次并立即清位、`postmark` 在**所有分支**统一记账,杜绝泄漏;**④ 会议发言顺序轮换**——不再是 r-1 永远先发言看不到别人,各常驻轮流先发言;**⑤ 解释 HRT 收敛**——三名常驻诚实给出"HRT-4 很可能为假(0.8+,未确立)+ 完整必要筛 + 机制 + 判定方程 + 未决点",无 Verified/、无人 declare solved,run 被外部暂停而非框架强收口,符合"诚实、不编造、框架不强加"哲学。详见 §24。
68
70
 
69
71
  ---
70
72
 
@@ -484,6 +486,23 @@ dsh plugin --profile <你的 profile> add github:ChongCyrus/Vibe-Mathematics
484
486
  | `projectLockTimeoutMs` | 60000 | 项目锁等待超时(同项目同一时刻只允许一个会话调度) |
485
487
  | `methodKeeperPersona` | 空 | 注入方法整理代理提示词开头的人格/要求 |
486
488
 
489
+ ### v4(常驻自组织 · 实验)默认值
490
+
491
+ `vibe_v4_set` 可调(持久化到 `State/settings.json`):
492
+
493
+ | 参数 | 默认 | 说明 |
494
+ |---|---|---|
495
+ | `residentCount` | 4 | 常驻数(可 `vibe_v4_add_member` 增减) |
496
+ | `compactThreshold` | 66 | 常驻上下文占比达此值触发软压缩(自述指令) |
497
+ | `compactAfterRounds` | 8 | 常驻每累计 N 轮(未压缩)触发一次软压缩 |
498
+ | `meetingKeepEvery` | 5 | 每积累 N 个新产物自动触发一次同步会议 |
499
+ | `maxParallel` | 3 | 同时唤醒的常驻上限(框架侧并发闸,非指派) |
500
+ | `activityTimeoutMs` | 120000 | 空闲心跳间隔(超时才触发 CHECKPOINT 唤醒,推动收敛) |
501
+ | `verdictMaxRounds` | 3 | 验证在独立初评后进入辩论的最大轮数 |
502
+ | `provider` / `model` | 空 | **常驻 LLM 路由**(空 = 常驻继承主代理的 provider/model;此前声明未用,v1.4.1 真正接入) |
503
+ | `residentPersona` | 空 | 注入每个常驻提示词开头的人格/要求 |
504
+ | `toolAllow` / `toolDeny` | `[]` | **常驻工具权限**(经 `startContinuable` 的 `toolFilter` 做作用域 `tools.restrict()`;空 = 继承全部工具;⚠️ 空 `allow:[]` 会拒绝一切工具) |
505
+
487
506
  ---
488
507
 
489
508
  ## 📝 断点续跑 & 人工干预(两大硬性需求)
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 — FOUR agent presets in one install: vibe-math-v1 (classic pipeline), vibe-math-v2 (probability-driven: qs.json + Propos knowledge base + explorer→solver→review/debate verdict), vibe-math-v3 (THIRD-generation, recommended: paper-style Markdown knowledge base with Problems/Progress/Propos/Methods/Verified + planner-agent scheduling that decides the next N actions + universal theory/method invention library + agents write their own Markdown directly via a per-file write lock), and vibe-math-v4 (FOURTH-generation: persistent self-organizing resident subagents that message & meet to decide all tasks, verify only by unanimous consensus, /compact at a context threshold, and stop only when all agree the problem is solved). Installing this bundle auto-installs all four presets into the DSH preset root.",
4
- "version": "1.4.0",
4
+ "version": "1.4.1",
5
5
  "type": "module",
6
6
  "main": "installer.js",
7
7
  "exports": {
@@ -36,6 +36,13 @@
36
36
  team genuinely discusses/debates), convenes and records meetings, exposes a shared task
37
37
  board, and stops the run only when the whole team agrees the problem is solved.
38
38
 
39
+ At brainstorm, residents are told they MAY (but are never forced to) autonomously build a
40
+ NEW general theory/framework/tool — by abstracting/generalising a structure (like inventing
41
+ group theory to solve polynomial equations, or building functional analysis as a general
42
+ framework). If they do, they must state its value to the original problem and may refine /
43
+ generalise it over time; such artifacts go in their Methods/<resident>/ library. This is an
44
+ encouragement, not an assignment.
45
+
39
46
  **YOUR ROLE — LET THEM SELF-ORGANIZE (hands-off):** you are NOT a moderator/coordinator.
40
47
  Do NOT inject agendas, priorities, division-of-labor, or verification decisions, and do
41
48
  NOT direct the residents' work. After `vibe_v4_start`, stay passive: read `vibe_v4_status`
@@ -54,7 +61,7 @@
54
61
  Main controls (recommended flow: configure FIRST, then start):
55
62
  - vibe_v4_configure {project?, problem?, params?} — create/configure the project (name, problem, params) WITHOUT starting a run; set everything here first.
56
63
  - vibe_v4_start {problem?, residentCount?, seedDirections?} — begin the run (spawn residents, brainstorm). If problem was configured, omit it.
57
- - vibe_v4_set {residentCount, compactThreshold, compactAfterRounds, meetingKeepEvery, maxParallel, activityTimeoutMs, verdictMaxRounds} — tune params (persisted to the settings file).
64
+ - vibe_v4_set {residentCount, compactThreshold, compactAfterRounds, meetingKeepEvery, maxParallel, activityTimeoutMs, verdictMaxRounds, provider, model, residentPersona, toolAllow, toolDeny} — tune params (persisted to the settings file). provider/model override the residents' LLM route (empty = they inherit YOUR model/provider); toolAllow/toolDeny are per-resident tool permissions (empty = they inherit all tools).
58
65
  - vibe_v4_resume / vibe_v4_pause / vibe_v4_abort / vibe_v4_status / vibe_v4_report.
59
66
  - vibe_v4_message {to|all, content} — inject a message to a resident (human/assistant intervention).
60
67
  - vibe_v4_meeting {agenda} — force a meeting.
@@ -32,7 +32,13 @@ export function apply(ctx) {
32
32
  residentCount: 4, compactThreshold: 66, compactAfterRounds: 8,
33
33
  maxParallel: 3, activityTimeoutMs: 120000, verdictMaxRounds: 3,
34
34
  meetingKeepEvery: 5, // 每积累 N 个新产物自动触发一次同步会议
35
+ // model/provider inheritance: '' = the resident inherits the parent (main assistant)
36
+ // route (provider + model). Set them to override the resident's LLM backend/model.
35
37
  provider: '', model: '', residentPersona: '',
38
+ // tool permissions: an allow/deny list of tool names applied via startContinuable's
39
+ // toolFilter (scoped tools.restrict() in the child). Empty = inherit all tools.
40
+ // CAUTION: only set one of these; an empty allow:[] would deny EVERY tool.
41
+ toolAllow: [], toolDeny: [],
36
42
  }
37
43
  let params = Object.assign({}, DEFAULT_PARAMS)
38
44
  let running = false, autoDone = false, phase = 'idle'
@@ -59,6 +65,19 @@ export function apply(ctx) {
59
65
  function logActivity(event,detail){ activityLog.push({at:now(),event,detail:String(detail||'')}); if(activityLog.length>activityLogCap) activityLog.shift() }
60
66
  function logDecision(kind,detail){ decisions.push({at:now(),kind,detail:String(detail||'')}) }
61
67
  function pickProvider(){ try { const n=subagents.list?subagents.list():[]; if(n.indexOf('spawn')!==-1) return 'spawn'; if(n.indexOf('fork')!==-1) return 'fork' } catch(e){} return 'spawn' }
68
+ // Per-resident model/provider inheritance: when params.provider / params.model are set,
69
+ // the resident uses that exact route; when left '' the resident inherits the parent's
70
+ // (main assistant) route — the documented DSH default (resolveChildAgentOptions merges
71
+ // requested over parent). No override is applied for empty values.
72
+ function residentAgentOptions(){ const ao={}; if(params.provider) ao.provider=params.provider; if(params.model) ao.model=params.model; return ao }
73
+ // Tool permission (scoped toolFilter). Only emit a filter when allow or deny has entries;
74
+ // an empty object is rejected by DSH ("must declare allow and/or deny").
75
+ function residentToolFilter(){
76
+ const allow=Array.isArray(params.toolAllow)?params.toolAllow.filter(x=>String(x).trim()):[]
77
+ const deny=Array.isArray(params.toolDeny)?params.toolDeny.filter(x=>String(x).trim()):[]
78
+ if(allow.length===0 && deny.length===0) return undefined
79
+ const f={}; if(allow.length) f.allow=allow; if(deny.length) f.deny=deny; return f
80
+ }
62
81
  function makeSignal(ms){ return AbortSignal.timeout(ms||30000) }
63
82
  function workspaceRoot(){ try { if(rootAgent&&rootAgent.session&&rootAgent.session.header&&rootAgent.session.header.cwd) return rootAgent.session.header.cwd } catch(e){} if(sandboxPolicy&&sandboxPolicy.workspaceRoot) return sandboxPolicy.workspaceRoot; return '.' }
64
83
  function vibeRoot(){ return (workspaceRoot()+'/VibeMath').replace(/\\/g,'/') }
@@ -150,6 +169,12 @@ export function apply(ctx) {
150
169
  s.push('### 可用工具')
151
170
  s.push(toolList())
152
171
  s.push('')
172
+ if(level==='full'){
173
+ s.push('### 可自主发明理论/工具(鼓励,但不强迫)')
174
+ s.push('请注意:你可以(但**不强迫**,完全视实际需要而定)尝试自主构建新的理论框架或工具——例如对某种系统做抽象化、一般化,抽离/推广出更一般的结构或理论框架;然后不断完善这个理论框架,在该框架下推得各种定理、性质、结论,以利于该框架下问题的解决。这就像为解决方程问题发明了群论、为分析需要建立了泛函分析框架——它比单纯解决当前问题更有学术价值,因为你直接得到了一类更普遍的方法/理论体系。')
175
+ s.push('若你发明了这样的理论/工具,请**阐明它对原问题的用处、价值**;后续可根据需要不断**完善、一般化、推广**它。把这类成果记入你的 Methods/<你>/ 库。')
176
+ s.push('')
177
+ }
153
178
  s.push('### 规则')
154
179
  s.push('- 只有 Verified/(或卡片标"已验证·真/假")算已确立;其余都是你的实验性工作,请区分"猜想/已知"。')
155
180
  s.push('- 验证必须**全组一致**(全真或全假);你只信全票结果。未全票的对象留在库里带概率。')
@@ -210,7 +235,8 @@ export function apply(ctx) {
210
235
  let residentSeq = 0
211
236
  function newResident(dir){ const rId='r-'+(++residentSeq); return {rId,childId:'',direction:dir||'',status:'brainstorm',rounds:0,roundsSinceCompact:0,lastActiveAt:now(),insight:'',contextPct:0,contextSeed:'',needCompact:false} }
212
237
  async function spawnResident(r){
213
- const started=await subagents.startContinuable({provider:pickProvider(),label:r.rId,request:{prompt:[textBlock(brainstormPrompt(r))],parent:rootAgent,agentOptions:{}},signal:makeSignal(params.activityTimeoutMs||60000)})
238
+ const ao=residentAgentOptions(); const tf=residentToolFilter()
239
+ const started=await subagents.startContinuable({provider:pickProvider(),label:r.rId,request:{prompt:[textBlock(brainstormPrompt(r))],parent:rootAgent,agentOptions:ao,...(tf?{toolFilter:tf}:{})},signal:makeSignal(params.activityTimeoutMs||60000)})
214
240
  r.childId=started.childId; r.status='brainstorm'; r.lastActiveAt=now()
215
241
  childOwner.set(started.childId,sessionId); busy.add(r.rId); wakeKind.set(r.rId,'normal'); currentResident=r.rId
216
242
  residents.set(r.rId,r); await saveAll(); logActivity('spawn',r.rId+' ('+(r.direction||'brainstorm')+')')
@@ -220,16 +246,28 @@ export function apply(ctx) {
220
246
  clearHeartbeat()
221
247
  busy.add(r.rId); wakeKind.set(r.rId,kind||'normal'); currentResident=r.rId
222
248
  r.lastActiveAt=now(); r.rounds+=1; r.roundsSinceCompact+=1
223
- // context / /compact: if the resident reports high context (or reached the round proxy),
224
- // prepend a compact directive it condenses its working state to a self-summary that the
225
- // framework uses as the next context seed (equivalent to /compact's "consolidate & forget").
249
+ // Context compaction has TWO distinct needs. Confusing them is the bug that made
250
+ // '[核心规则重申]+[CONTEXT COMPACT]' repeat at the start of nearly every prompt:
251
+ // (a) r.needCompact (set by a REAL /compact) => the resident's rules may be blurred, so
252
+ // re-anchor the short core rules on the next wake of ANY kind, then CLEAR the flag.
253
+ // (Short recap only; no self-summary directive — the real compact already condensed.)
254
+ // (b) soft-compact trigger (contextPct>=threshold OR roundsSinceCompact>=afterRounds) =>
255
+ // the resident's context genuinely grew; ask it to self-summary. ONLY on a normal
256
+ // research round (kind==='normal'): a meeting/verify reply has no contextPct/compacted
257
+ // fields, so a directive injected there is never acknowledged and would repeat forever.
226
258
  let prompt = promptText
227
- if(r.needCompact || (Number(r.contextPct)>=Number(params.compactThreshold)) || (r.roundsSinceCompact>=Number(params.compactAfterRounds))){
259
+ const isNormal = (kind||'normal')==='normal'
260
+ const wantSoft = isNormal && (Number(r.contextPct)>=Number(params.compactThreshold) || Number(r.roundsSinceCompact)>=Number(params.compactAfterRounds))
261
+ const wantReanchor = r.needCompact
262
+ if(wantSoft){
228
263
  prompt = coreRulesBrief() + '\n' +
229
264
  '[CONTEXT COMPACT — your conversation is at/near the limit. Do NOT re-derive history.\n' +
230
265
  'Condense your current working state into ONE tight self-summary (findings so far, active direction, key artifacts you recorded, next concrete steps, open questions), then answer this round in the normal JSON format as usual.\n' +
231
266
  'Set "contextPct": 15 (your post-compact usage) and "compacted": true in the reply so the framework records the condensed seed.]\n\n' + promptText
232
267
  r.needCompact = true
268
+ } else if(wantReanchor){
269
+ prompt = coreRulesBrief() + '\n' + prompt
270
+ r.needCompact = false
233
271
  }
234
272
  try { await subagents.followup(rootAgent,r.childId,[textBlock(prompt)],{source:{kind:'user'},signal:makeSignal(params.activityTimeoutMs||60000)}); return true }
235
273
  catch(e){ console.error('vibe-v4 wake '+r.rId+' failed: '+String((e&&e.message)||e)); busy.delete(r.rId); return false }
@@ -261,6 +299,18 @@ export function apply(ctx) {
261
299
  async function saveTaskboard(){ await writeJson('State/taskboard.json',taskboard); await writeTaskboard() }
262
300
  function listTasks(){ return taskboard.filter(t=>t.status!=='done') }
263
301
  async function reportContext(rId,pct){ const r=residents.get(rId); if(r){ r.contextPct=clPct(pct); if(Number(pct)<30) r.needCompact=false; } return {ok:true} }
302
+ // Apply context/compact bookkeeping from a resident's reply, so the flag can clear even when
303
+ // the reply came through a meeting/verify branch (defensive) as well as the normal branch.
304
+ function postmark(r, parsed){
305
+ if(typeof parsed.contextPct==='number') r.contextPct=clPct(parsed.contextPct)
306
+ if(parsed.compacted===true || (r.needCompact && parsed.summary)){
307
+ r.contextSeed=String(parsed.summary||r.contextSeed||'')
308
+ r.contextPct=Math.min(r.contextPct||15,25)
309
+ r.roundsSinceCompact=0
310
+ r.needCompact=false
311
+ logActivity('compact', r.rId+' consolidated context')
312
+ }
313
+ }
264
314
 
265
315
  // ---- messaging ----
266
316
  async function postMessage(from,to,content){
@@ -293,15 +343,21 @@ export function apply(ctx) {
293
343
  async function startMeeting(agenda,type,targetId){
294
344
  if(meetingState) return {ok:false,message:'meeting already in progress'}
295
345
  clearHeartbeat()
296
- meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[]}
346
+ const ids=Array.from(residents.keys())
347
+ // Rotate the per-meeting speaking order so the SAME resident isn't always the "first speaker
348
+ // who sees no one else's contribution"; a real discussion lets each member lead sometimes.
349
+ const rot=Math.floor(Math.random()*Math.max(1,ids.length))
350
+ const order=ids.slice(rot).concat(ids.slice(0,rot))
351
+ meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[],order}
297
352
  logActivity('meeting','start: '+agenda); await saveAll(); await scheduleNext(); return {ok:true,id:meetingState.id}
298
353
  }
299
354
  async function continueMeetingRound(){
300
355
  if(!meetingState) return
301
356
  const ids=Array.from(residents.keys()); const allSpoke=ids.every(id=>meetingState.inputs[id]!==undefined)
302
357
  if(allSpoke){ await finalizeMeeting(); return }
303
- // only wake IDLE un-spoken residents; in-flight ones re-trigger this on end.
304
- const id=ids.find(x=>meetingState.inputs[x]===undefined && !busy.has(x)); if(!id) return
358
+ // only wake IDLE un-spoken residents (rotated order); in-flight ones re-trigger this on end.
359
+ const order=meetingState.order||ids
360
+ const id=order.find(x=>meetingState.inputs[x]===undefined && !busy.has(x)); if(!id) return
305
361
  const r=residents.get(id)
306
362
  await wakeResident(r, meetingPrompt(r,meetingState), 'meeting'); await saveAll()
307
363
  }
@@ -490,6 +546,7 @@ export function apply(ctx) {
490
546
  realCompact(r).catch(()=>{}) // best-effort real DSH /compact of this resident while idle
491
547
  const output=blocksToText(info&&info.lastAssistantMessage)
492
548
  const parsed=parseReply(output)
549
+ postmark(r, parsed) // context/compact bookkeeping, regardless of wake kind (clears any leak)
493
550
  const kind=wakeKind.get(r.rId)||'normal'
494
551
  if(kind==='meeting' && meetingState){
495
552
  meetingState.inputs[r.rId]={input:parsed.input||parsed.summary||'',voteSolved:typeof parsed.voteSolved==='boolean'?parsed.voteSolved:null,propose_verify:parsed.propose_verify||null,propose_task:parsed.propose_task||null,task_desc:parsed.task_desc||'',claim_task:parsed.claim_task||null}
@@ -512,9 +569,6 @@ export function apply(ctx) {
512
569
  // normal turn
513
570
  if(r.status==='brainstorm'){ r.insight=parsed.summary||output; r.status='active' }
514
571
  if(typeof parsed.solved==='boolean') reports.push({rId:r.rId,solved:parsed.solved,summary:parsed.summary||'',at:now()})
515
- // context / compact: record the condensed seed + post-compact usage, clear the flag
516
- if(typeof parsed.contextPct==='number'){ r.contextPct=clPct(parsed.contextPct) }
517
- if(parsed.compacted===true || (r.needCompact && parsed.summary)){ r.contextSeed=String(parsed.summary||''); r.contextPct=Math.min(r.contextPct||15,25); r.roundsSinceCompact=0; r.needCompact=false; logActivity('compact',r.rId+' consolidated context') }
518
572
  if(parsed.propose_verify) pendingVerify={targetId:parsed.propose_verify,targetType:guessTargetType(parsed.propose_verify),proposer:r.rId,at:now()}
519
573
  // group-conversation relay: the resident may choose to speak to the whole team (input) —
520
574
  // forward it to the others so this is a real discussion group, not private monologues.
@@ -559,14 +613,22 @@ export function apply(ctx) {
559
613
  }
560
614
  function status(){ return { ok:true, running, phase, autoDone, project:currentProject, residentCount:residents.size,
561
615
  residents:listResidents(), busy:[...busy], taskboard:taskboard.length,
562
- params:['residentCount','compactAfterRounds','compactThreshold','maxParallel','activityTimeoutMs','meetingKeepEvery','verdictMaxRounds'].map(k=>k+'='+params[k]).join(', ') } }
616
+ params:['residentCount','compactAfterRounds','compactThreshold','maxParallel','activityTimeoutMs','meetingKeepEvery','verdictMaxRounds','provider','model','residentPersona','toolAllow','toolDeny'].map(k=>k+'='+(Array.isArray(params[k])?params[k].join(','):params[k])).join(', ') } }
563
617
  function report(){ return { ok:true, running, phase, autoDone, project:currentProject, problem:problemText,
564
618
  residents:listResidents(), taskboard:taskboard.filter(t=>t.status!=='done'),
565
619
  verify: verifyState?{target:verifyState.targetId,stage:verifyState.stage}:null, meetings:meetings.length,
566
620
  recentActivity: activityLog.slice(-8) } }
567
621
  async function addMember(direction){ const r=newResident(direction||''); await spawnResident(r); return {ok:true,id:r.rId,direction:r.direction} }
568
622
  async function removeMember(id){ const r=residents.get(id); if(!r) return {ok:false}; if(r.childId){ try{ subagents.interrupt(r.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } residents.delete(id); busy.delete(id); mailboxes.delete(id); await saveAll(); return {ok:true} }
569
- function setParams(upd){ for(const k of Object.keys(upd||{})){ if(k in params) params[k]=upd[k] } saveSettings().catch(()=>{}); return {ok:true} }
623
+ // Normalize one parameter value to its intended type so a string from /v4 set or configure
624
+ // becomes the right number/array. Keeps settings.json clean regardless of how it was set.
625
+ function normalizeParam(k, v){
626
+ const INT_KEYS=['residentCount','compactThreshold','compactAfterRounds','maxParallel','activityTimeoutMs','verdictMaxRounds','meetingKeepEvery']
627
+ if(INT_KEYS.includes(k)){ const n=Number(v); return Number.isFinite(n)?n:v }
628
+ if(k==='toolAllow'||k==='toolDeny'){ if(Array.isArray(v)) return v.map(x=>String(x).trim()).filter(Boolean); if(typeof v==='string') return v.split(',').map(x=>x.trim()).filter(Boolean); return [] }
629
+ return v
630
+ }
631
+ function setParams(upd){ for(const k of Object.keys(upd||{})){ if(k in params) params[k]=normalizeParam(k, upd[k]) } saveSettings().catch(()=>{}); return {ok:true} }
570
632
  // ---- create / configure (no auto-start) + settings-file persistence ----
571
633
  async function loadSettings(){ const s=await readJson('State/settings.json'); if(s&&typeof s==='object'){ for(const k of Object.keys(s)){ if(k in params) params[k]=s[k] } } }
572
634
  async function saveSettings(){ await writeJson('State/settings.json', params) }
@@ -575,7 +637,7 @@ export function apply(ctx) {
575
637
  async function configure(cfg){
576
638
  if(cfg && cfg.project && String(cfg.project).trim()) currentProject=String(cfg.project).trim()
577
639
  if(cfg && cfg.problem) problemText=String(cfg.problem)
578
- if(cfg && cfg.params && typeof cfg.params==='object') for(const k of Object.keys(cfg.params)) if(k in params) params[k]=cfg.params[k]
640
+ if(cfg && cfg.params && typeof cfg.params==='object') setParams(cfg.params)
579
641
  await writeCurrentProject(); await ensureDirs(); await saveSettings()
580
642
  // create the problem card so the project is complete BEFORE the run starts
581
643
  if(problemText){ const pid=slugify(problemText.slice(0,40))||'problem'; await writeText('Problems/'+pid+'.md','# 问题|'+pid+'\n- ID: '+pid+'\n- 类型: 问题\n- 状态: 求解中\n- 优先级: 1\n- 依赖: []\n\n## 陈述\n'+problemText+'\n') }
@@ -624,7 +686,10 @@ export function apply(ctx) {
624
686
  registerTool('vibe_v4_list_members','List residents.',objParams({}),(s)=>({ok:true,residents:s.listResidents()}))
625
687
  registerTool('vibe_v4_add_member','Add a resident.',objParams({direction:{type:'string'}}),(s,a)=>s.addMember(a.direction))
626
688
  registerTool('vibe_v4_remove_member','Close a resident.',objParams({id:{type:'string'}},['id']),(s,a)=>s.removeMember(a.id))
627
- registerTool('vibe_v4_set','Set V4 parameters.',objParams({residentCount:{type:'integer'},compactAfterRounds:{type:'integer'},compactThreshold:{type:'integer'},meetingKeepEvery:{type:'integer'},maxParallel:{type:'integer'},activityTimeoutMs:{type:'integer'},verdictMaxRounds:{type:'integer'}}),(s,a)=>{ s.setParams(a); return {ok:true} })
689
+ // model/provider inheritance: set model/provider to override the residents' LLM route (''=inherit
690
+ // the main assistant's route). toolAllow/toolDeny are per-resident tool permissions (scoped
691
+ // restrict). residentPersona prepends a persona line to every resident prompt.
692
+ registerTool('vibe_v4_set','Set V4 parameters. model/provider override resident LLM route (empty=inherit main); toolAllow/toolDeny restrict resident tools (arrays of tool names); residentPersona adds a persona line.',objParams({residentCount:{type:'integer'},compactAfterRounds:{type:'integer'},compactThreshold:{type:'integer'},meetingKeepEvery:{type:'integer'},maxParallel:{type:'integer'},activityTimeoutMs:{type:'integer'},verdictMaxRounds:{type:'integer'},provider:{type:'string'},model:{type:'string'},residentPersona:{type:'string'},toolAllow:{type:'array',items:{type:'string'}},toolDeny:{type:'array',items:{type:'string'}}}),(s,a)=>{ s.setParams(a); return {ok:true} })
628
693
  // resident-facing tools: route to the CALLING resident (exec.agent.id === childId);
629
694
  // fall back to the last-woken resident when called by the host/assistant.
630
695
  registerTool('vibe_v4_send_message','(resident) Send a message to another resident.',objParams({to:{type:'string'},content:{type:'string'}},['to','content']),(s,a,x)=>s.postMessage(s.residentIdOf(x),a.to,a.content))
@@ -480,6 +480,41 @@ VibeMath/Projects/<project>/
480
480
  - **补上缺失的 `/v4 set` 分支**:此前 usage 列了 `set` 但命令处理器没实现,`/v4 set` 会掉到 usage;现支持 `key=value` 解析并 `setParams`(也持久化到 settings)。
481
481
  - **真实 `/compact` 后重申规则**:`realCompact` 成功压缩常驻真实会话后,置 `needCompact=true`,使下一次唤醒**重申核心规则**(与软压缩一致),避免真实压缩后常驻淡忘规则。
482
482
 
483
+ ---
484
+
485
+ ## 24. 自主发明理论 / 模型与工具权限 / 压缩重申泄漏修复(v1.4.1)
486
+
487
+ 针对一次真实 3 常驻 run(HRT 4 猜想)的深入审计 + 用户三条新增要求:
488
+
489
+ ### 1. 初始提示告知"可自主构建新的理论框架/工具"(v1.4.1-①)
490
+ 在 `contextBrief(r,'full')`(**仅首轮 brainstorm**,符合"背景只讲一次")新增一节 **"可自主发明理论/工具(鼓励,但不强迫)"**:
491
+ - 常驻可(但**不强迫**、完全视实际需要)**自主尝试构建新的理论框架或工具**——对某种系统做**抽象化、一般化**,抽离/推广出更一般的结构或理论框架;然后**不断完善**它,在该框架下推得各种**定理、性质、结论**,以利于该框架下问题的解决。
492
+ - 类比:为解决方程问题发明了**群论**、为分析需要建立了**泛函分析**框架——这比单纯解决当前问题更有价值,因为直接得到了一类**更普遍的方法/理论体系**。
493
+ - 若发明了这样的理论/工具,请**阐明它对原问题的用处、价值**;后续可**不断完善、一般化、推广**它,并把这类成果记入 `Methods/<你>/` 库。
494
+ - 这是**鼓励,不是指派**;不写固定模板、不强制"每轮必须发明"。
495
+
496
+ ### 2. 补齐"模型继承关系 + 工具权限"参数(v1.4.1-②)
497
+ 此前 `provider`/`model` 参数在 `DEFAULT_PARAMS` 里**声明但从未被使用**(死参数),且**没有任何工具权限参数**。现补齐并真正落地:
498
+ - **模型继承**:默认**常驻继承主代理(main assistant)的 provider/model 路由**(DSH `resolveChildAgentOptions` 把请求项并到父路由之上)。通过 `params.provider`/`params.model` 可**覆盖**常驻的 LLM 后端/模型(空=继承)。
499
+ - **工具权限**:新增 `params.toolAllow` / `params.toolDeny`,经 `startContinuable` 的 `toolFilter` 做**作用域 `tools.restrict()`**(被点名的工具从常驻提示里消失且拒绝执行)。默认两者为空 → **常驻继承全部工具**(含 fs 与各 `vibe_v4_*`);只有显式配置才收权。⚠️ 注意"deny-all 陷阱":**空 `allow:[]` 会拒绝一切工具**,因此只有当 allow 或 deny 至少有一项时才会发出 filter。
500
+ - **接线**:`spawnResident` 现把 `residentAgentOptions()`(provider/model)与 `residentToolFilter()`(toolAllow/toolDeny)传入 `startContinuable`;`setParams` 做类型归一(整数 / 逗号分隔的数组);`vibe_v4_set` schema 与 `status()` 参数串也已加这些新参数。
501
+
502
+ ### 3. 修复"[核心规则重申]+[CONTEXT COMPACT]"在提示开头重复泄漏(v1.4.1-③)
503
+ 这是真实 run 的核心 bug。**根因**(从 `State/residents.json` 证实):r-1 的 `needCompact:true` 常年不释放、`roundsSinceCompact:10`(>8)——框架在**所有**唤醒(含 meeting/verify)里都根据"`needCompact || contextPct>=阈值 || rounds>=afterRounds`"注入压缩指令;而 meeting/verify 分支收到回复后**提前 return,从不处理 `contextPct/compacted/needCompact`**,于是 `needCompact` 卡死为 true,**每个后续提示都在开头重复"核心规则重申 + CONTEXT COMPACT"**。修复:
504
+ - **只对 normal 研究轮注入完整压缩指令**;meeting/verify/CHECKPOINT 不再注入(它们的回复没有 `compacted/contextPct` 字段,注入了也永远无法被确认 → 无限重复)。
505
+ - **`needCompact`(真实 `/compact` 后)只重申一次短规则并立即清位**(下次任一唤醒即可,清位后不再重复),不再要求再做一次自述。
506
+ - 新增 `postmark(r, parsed)`,在 `onResidentEnd` **所有分支**(含 meeting/verify)开头统一处理 context/compact 记账,杜绝任何情况下的标志泄漏。
507
+ - 这样"规则重申/压缩指令"只出现在**真正发生了压缩的那次唤醒**(软压缩每 `compactAfterRounds` 轮一次、真实 `/compact` 后一次),不再"每轮都重复"。
508
+
509
+ ### 4. 会议发言顺序轮换(v1.4.1-④,改善"第一位发言者看不到别人")
510
+ 真实 run 里 r-1 几乎总是在会议里 **第一个发言**(因 `residents` 按插入序 r-1,r-2,r-3,`continueMeetingRound` 取第一个未发言者),于是它本次会议内**看不到** r-2/r-3 的后续发言。现改为**每次会议随机轮换发言顺序**(`meetingState.order`),让不同常驻轮流先发言,讨论更公平、各成员都能看到别人。
511
+
512
+ ### 5. 关于 HRT run 收敛与"第7轮后看不到别人消息"的解释(审计结论,非代码 bug)
513
+ - **收敛合理且符合哲学**:三名常驻经约 18/24/29 轮与 6 次会议,**独立且一致**地给出诚实结论——"HRT-4 一般形式很可能为假(0.8+,未确立)+ 完整必要筛 + 统一机制 + 判定方程(♯) + 明确标注未决点(显式反例=外部无全文阻塞、generic 证明=HRT 核心困难未证)";**无任何 Verified/ 对象**;**无人 declare solved**(每会 `voteSolved` 均非全 true)。框架没有强行收口(`autoDone=false`),run 是被**外部暂停**(`running:false, phase:active`)。这正是哲学要求的诚实:常驻不编造"已解决",框架也不强加结论。
514
+ - **"第7轮后看不到别人消息/群聊"合理且有两点成因**:① run 后期以**会议为主**(r-1 的后续唤醒几乎都是 Meeting 提示,而非"第 N 轮"研究轮);② **`[群聊]` 群聊转发在会议主导期被积压**——`scheduleNext` 让会议/验证优先于邮箱投递,会议期间 `[群聊]` 只排队不投递,r-1 邮箱里积压了大量未投递的群聊消息(`State/mailboxes.json` 可见)。会议提示里虽会把"他人已发言"转给常驻,但 r-1 作为最常的先发言者,本次会议内看不到后续发言。这是**自组织在"深度协作+会议主导"下的自然表现**;已通过 §24.4 的发言顺序轮换改善,`[群聊]` 积压会在恢复 normal 阶段被正常投递。
515
+
516
+ > 测试:`selfdrive-v4.mjs` 21/21;`e2e-v4-fixes.test.mjs` 36/36(新增 T10 模型/工具权限接线、T11 默认继承、T12 自主发明理论提示、T13 压缩指令不泄漏进 meetings);v3 E2E 100/100、v2 regression 14/14、v2 business 24/24、multisession 25/25 全绿。
517
+
483
518
 
484
519
 
485
520