dsh-vibe-math 2.3.16 → 2.4.0
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.en.md +49 -46
- package/README.md +48 -45
- package/cordis.patch.yml +1123 -10
- package/docs/AUDIT-CHECKLIST.md +45 -0
- package/docs/COMPAT-AUDIT-ROUND2.md +3 -0
- package/docs/COMPAT-AUDIT-ROUND3.md +139 -0
- package/docs/generate_framework_diagram_v5.mjs +27 -27
- package/docs/release-notes/RELEASE-NOTES-2.4.0.md +172 -0
- package/docs/test-timing.md +2 -1
- package/installer.js +89 -21
- package/package.json +33 -9
- package/preset-declaration.js +62 -0
- package/prompt-corpus-v5/prompt-corpus-v5.json +7 -7
- package/prompt-corpus-v5/prompt-corpus-v5.md +8 -129
- package/scripts/build-preset-rows.mjs +146 -0
- package/tests/audit-installer-compat.test.mjs +9 -3
- package/tests/audit-preset-rows.test.mjs +185 -0
- package/tests/audit-v5-integrity.mjs +49 -3
- package/tests/e2e-v5-round2.test.mjs +137 -67
- package/tests/formal-verify-v5.test.mjs +43 -35
- package/tests/prompt-v5-integrity.test.mjs +49 -47
- package/tests/selfdrive-v5.mjs +42 -55
- package/vibe-math-v2/vibe-math-v2.js +6 -2
- package/vibe-math-v3/vibe-math-v3.js +6 -2
- package/vibe-math-v4/vibe-math-v4.js +191 -13
- package/vibe-math-v5/agent.cordis.yml +8 -8
- package/vibe-math-v5/preset.yml +1 -1
- package/vibe-math-v5/vibe-math-v5.js +215 -91
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +56 -44
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +24 -22
- package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v5-en.svg +14 -14
- package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v5.svg +14 -14
|
@@ -4,9 +4,44 @@
|
|
|
4
4
|
// intervention). It NEVER assigns tasks: residents message & meet and decide all
|
|
5
5
|
// task allocation among themselves. Consumes HOST subagents/agents/fs/tools/commands.
|
|
6
6
|
// NOTE: must declare `inject` for every service read as a ctx property (the Guard
|
|
7
|
-
// rejects undeclared dependencies)
|
|
8
|
-
//
|
|
7
|
+
// rejects undeclared dependencies). `timer` IS injected and used (ctx.timeout) so every
|
|
8
|
+
// timer here is a fiber-owned disposer — but the global setTimeout/clearTimeout DO exist
|
|
9
|
+
// in this preset's runtime: a preset is a FILE row loaded by a plain host-realm import().
|
|
10
|
+
// The vm sandbox that traps require/setTimeout/setInterval/fetch wraps only a DYNAMIC
|
|
11
|
+
// package's host half (@deepseek-ai/dsh-cordis-host-runner/lib/types/sandbox.js, reached
|
|
12
|
+
// only from the dynamic-package start path); v2/v3 use those globals and work. An earlier
|
|
13
|
+
// version of this note claimed the globals do not exist — false for file rows, and it would
|
|
14
|
+
// only become true if this preset were ever converted to a dynamic package.
|
|
9
15
|
export const inject = ['subagents', 'agents', 'fs', 'tools', 'commands', 'timer']
|
|
16
|
+
|
|
17
|
+
// ---- host live-child cap (DSH ≥ 0.2) ----------------------------------------
|
|
18
|
+
// The host caps the number of LIVE continuable children PER ROOT AGENT. Evidence (installed
|
|
19
|
+
// dsh-subagent 0.2.0-rc.2): `materialize` calls `ActivationPool.reserve(this.maxActiveSubagents())`
|
|
20
|
+
// and `reserve` throws `SubagentError('subagent limit reached (active child limit: <capacity>);
|
|
21
|
+
// wait for an existing child to finish or complete this work with the current agents',
|
|
22
|
+
// 'ACTIVATION_LIMIT_REACHED')` when every slot is taken. The capacity comes from the `subagent`
|
|
23
|
+
// row's `maxActiveSubagents` Config key (default 8; no preset sets it), the cap is shared by all
|
|
24
|
+
// descendants of that root, and a slot is released when a child settles. The `.d.ts` does NOT
|
|
25
|
+
// document the throw.
|
|
26
|
+
//
|
|
27
|
+
// The detector and the remembered limit live at MODULE scope because the cap is a property of the
|
|
28
|
+
// HOST (the same number for every root of this process), not of one session. On DSH 0.1.x
|
|
29
|
+
// `subagents.startContinuable` never throws this code, so every branch below is inert there — the
|
|
30
|
+
// error code itself is the feature test, there is no version check anywhere.
|
|
31
|
+
let hostChildLimit // undefined until the host tells us its ceiling (via a refusal)
|
|
32
|
+
function isActivationLimitReached(e){ return String((e && e.code) || '') === 'ACTIVATION_LIMIT_REACHED' }
|
|
33
|
+
function noteChildLimit(e){
|
|
34
|
+
const m = /active child limit:\s*(\d+)/.exec(String((e && e.message) || e || ''))
|
|
35
|
+
if(m) hostChildLimit = Number(m[1])
|
|
36
|
+
return hostChildLimit
|
|
37
|
+
}
|
|
38
|
+
// One actionable sentence naming the HOST ceiling and the knob that raises it.
|
|
39
|
+
function hostChildLimitHint(limit){
|
|
40
|
+
const n = (limit === undefined || limit === null) ? '' : ('=' + limit)
|
|
41
|
+
return '本宿主对「同时在活的续聊子代理」有上限(宿主 subagent 行的 maxActiveSubagents 参数' + n
|
|
42
|
+
+ ',写满后子代理服务抛 ACTIVATION_LIMIT_REACHED)'
|
|
43
|
+
}
|
|
44
|
+
|
|
10
45
|
export function apply(ctx) {
|
|
11
46
|
const subagents = ctx.subagents
|
|
12
47
|
const agents = ctx.agents
|
|
@@ -384,9 +419,10 @@ export function apply(ctx) {
|
|
|
384
419
|
// contract (§7) additionally requires `handle.terminate()` on timeout, and a host that
|
|
385
420
|
// ignores/exceeds graceMs would otherwise leave the Lean process running while the framework
|
|
386
421
|
// reports LEAN_TIMEOUT. Race `done` against a cap-ms timer that terminates the handle and
|
|
387
|
-
// resolves a synthetic outcome. Note: the
|
|
388
|
-
//
|
|
389
|
-
//
|
|
422
|
+
// resolves a synthetic outcome. Note: the `timer` service (ctx.timeout) is the timer used here
|
|
423
|
+
// because it returns the disposer we clear below when `done` wins the race (a fiber-owned
|
|
424
|
+
// disposer), NOT because global timers are missing: they exist for file rows like this preset
|
|
425
|
+
// (see the header note — only a DYNAMIC package's host half is vm-sandboxed).
|
|
390
426
|
let killedByUs=false, timerDispose=null
|
|
391
427
|
let outcome
|
|
392
428
|
try {
|
|
@@ -999,15 +1035,82 @@ export function apply(ctx) {
|
|
|
999
1035
|
return L.join('\n')
|
|
1000
1036
|
}
|
|
1001
1037
|
|
|
1038
|
+
// ---- host live-child cap: a refused spawn must never kill the run --------------
|
|
1039
|
+
// A ROOT agent's live continuable children are capped by the host (see the module-scope note):
|
|
1040
|
+
// `startContinuable` throws ACTIVATION_LIMIT_REACHED once the cap is full. Without the handling
|
|
1041
|
+
// below that throw escaped `start()` mid-loop, so the run was left with `running=true`, a
|
|
1042
|
+
// half-built team and an unhandled rejection.
|
|
1043
|
+
let childLimitLoggedThisRound = false // ONE actionable line per ROUND, not one per refused child
|
|
1044
|
+
let pendingSpawns = [] // residents the cap refused: retried a round later
|
|
1045
|
+
function beginSpawnRound(){ childLimitLoggedThisRound = false }
|
|
1046
|
+
// LIVE continuable children of THIS framework: a resident that holds a childId and has not been
|
|
1047
|
+
// stopped. A refused resident carries childId '' so it can never inflate (or hide behind) the
|
|
1048
|
+
// real ceiling, and is never counted as a voter/speaker either (see noteSpawnRefused).
|
|
1049
|
+
function liveChildCount(){ let n=0; for(const [,r] of residents){ if(r.childId && r.status!=='stopped') n++ } return n }
|
|
1050
|
+
// Mark a resident whose spawn the host refused and say ONCE per round what the operator can do.
|
|
1051
|
+
// The resident is kept OUT of the live roster: a childless member must not be counted by the
|
|
1052
|
+
// meeting/verify consensus (they require EVERY resident to speak/vote, and no end event can ever
|
|
1053
|
+
// arrive for a child that was never created), and `wakeResident` could never deliver to it. It is
|
|
1054
|
+
// queued in `pendingSpawns` instead, so the work is deferred rather than lost.
|
|
1055
|
+
function noteSpawnRefused(r, limit){
|
|
1056
|
+
r.childId=''; r.status='refused'; r.lastActiveAt=now()
|
|
1057
|
+
// `start()`/`addMember` never put the resident in the roster, but a RESPAWN (resume) does:
|
|
1058
|
+
// drop it here (the queued retry re-registers it, and its own end event resumes the flow).
|
|
1059
|
+
if(residents.get(r.rId)===r) residents.delete(r.rId)
|
|
1060
|
+
busy.delete(r.rId); wakeKind.delete(r.rId)
|
|
1061
|
+
if(currentResident===r.rId) currentResident=''
|
|
1062
|
+
if(pendingSpawns.indexOf(r)<0) pendingSpawns.push(r)
|
|
1063
|
+
if(childLimitLoggedThisRound) return
|
|
1064
|
+
childLimitLoggedThisRound=true
|
|
1065
|
+
const line='vibe-math-v4: '+hostChildLimitHint(limit)+'。'+r.rId+' 等常驻本轮未能创建;'
|
|
1066
|
+
+'可在宿主的 subagent 行把 maxActiveSubagents 调大(或调小 residentCount),未创建的常驻已排队,有空位时自动重试。'
|
|
1067
|
+
console.error(line)
|
|
1068
|
+
logActivity('spawn-refused', line)
|
|
1069
|
+
}
|
|
1070
|
+
// One retry ROUND for the queued residents. Called from scheduleNext (the single choke point of
|
|
1071
|
+
// every scheduling pass) and paced by the heartbeat, so a capped team is never retried in a
|
|
1072
|
+
// tight loop; a successful retry registers the resident normally and its own end event resumes
|
|
1073
|
+
// the normal flow.
|
|
1074
|
+
async function retryPendingSpawns(){
|
|
1075
|
+
if(!pendingSpawns.length) return
|
|
1076
|
+
const queue=pendingSpawns.slice(); pendingSpawns=[]; beginSpawnRound()
|
|
1077
|
+
for(const r of queue){
|
|
1078
|
+
try { await spawnResident(r) }
|
|
1079
|
+
catch(e){
|
|
1080
|
+
// A non-cap failure has no other caller to report to here: say it once and drop this
|
|
1081
|
+
// resident (retrying a broken tool filter every round forever would be worse).
|
|
1082
|
+
console.error('vibe-math-v4: 重试创建常驻 '+r.rId+' 失败,已放弃该常驻:'+String((e&&e.message)||e))
|
|
1083
|
+
logActivity('spawn-failed', r.rId+' '+String((e&&e.message)||e))
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
await saveAll()
|
|
1087
|
+
if(pendingSpawns.length) armHeartbeat() // still capped: re-check on the next heartbeat window
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1002
1090
|
// ---- resident lifecycle ----
|
|
1003
1091
|
let residentSeq = 0
|
|
1004
1092
|
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} }
|
|
1005
1093
|
async function spawnResident(r){
|
|
1094
|
+
// Skip BEFORE touching the host when we already KNOW the ceiling and our own live children
|
|
1095
|
+
// fill it: the host would refuse this spawn too, and one line per ROUND (not per child) is
|
|
1096
|
+
// enough. The resident is queued and the next round retries it.
|
|
1097
|
+
if(hostChildLimit!==undefined && liveChildCount()>=hostChildLimit){ noteSpawnRefused(r, hostChildLimit); return false }
|
|
1006
1098
|
const ao=residentAgentOptions(); const tf=residentToolFilter()
|
|
1007
|
-
|
|
1099
|
+
let started
|
|
1100
|
+
try {
|
|
1101
|
+
started=await startWithToolFilter(tf, function(f){ return {provider:pickProvider(),label:r.rId,request:{prompt:[textBlock(brainstormPrompt(r))],parent:rootAgent,agentOptions:ao,...(f?{toolFilter:f}:{})},signal:makeSignal(params.activityTimeoutMs||60000)} })
|
|
1102
|
+
} catch(e){
|
|
1103
|
+
// The host's live-child cap is a HOST limit (maxActiveSubagents on the `subagent` row), not
|
|
1104
|
+
// a defect in this preset: remember the ceiling, keep the resident for a later round, and
|
|
1105
|
+
// do NOT let the throw escape (it used to abort start()'s spawn loop). Every OTHER start
|
|
1106
|
+
// failure keeps its previous behaviour and still propagates.
|
|
1107
|
+
if(!isActivationLimitReached(e)) throw e
|
|
1108
|
+
noteSpawnRefused(r, noteChildLimit(e)); return false
|
|
1109
|
+
}
|
|
1008
1110
|
r.childId=started.childId; r.status='brainstorm'; r.lastActiveAt=now()
|
|
1009
1111
|
childOwner.set(started.childId,sessionId); busy.add(r.rId); wakeKind.set(r.rId,'normal'); currentResident=r.rId
|
|
1010
1112
|
residents.set(r.rId,r); await saveAll(); logActivity('spawn',r.rId+' ('+(r.direction||'brainstorm')+')')
|
|
1113
|
+
return true
|
|
1011
1114
|
}
|
|
1012
1115
|
async function wakeResident(r, promptText, kind){
|
|
1013
1116
|
if(!r || !r.childId) return false // a removed resident must never be woken (else r.childId would crash)
|
|
@@ -1040,8 +1143,11 @@ export function apply(ctx) {
|
|
|
1040
1143
|
// The DSH continuable-wake API is subagents.sendMessage(sender, targetId, content, {signal}),
|
|
1041
1144
|
// NOT subagents.followup (which is only Agent.followup, and does NOT exist on the subagents
|
|
1042
1145
|
// service). Using a non-existent method threw TypeError and made EVERY wake fail silently →
|
|
1043
|
-
// the group went idle forever. Prefer sendMessage;
|
|
1044
|
-
//
|
|
1146
|
+
// the group went idle forever. Prefer sendMessage; the legacy `subagents.followup` branch is
|
|
1147
|
+
// kept for the earliest supported hosts only: that service method was dropped in DSH
|
|
1148
|
+
// 0.1.2-rc.1, so it is reachable only on 0.1.2-alpha.* — 0.1.2-rc.1 and every later release
|
|
1149
|
+
// this package targets expose startContinuable/sendMessage/drainContinuableChildren and
|
|
1150
|
+
// nothing else (checked against dsh-subagent 0.2.0-rc.2).
|
|
1045
1151
|
try {
|
|
1046
1152
|
if(typeof subagents.sendMessage==='function'){
|
|
1047
1153
|
await subagents.sendMessage(rootAgent, r.childId, [textBlock(prompt)], {signal: makeSignal(params.activityTimeoutMs||60000)})
|
|
@@ -1510,6 +1616,46 @@ export function apply(ctx) {
|
|
|
1510
1616
|
* plugin row where it is (nothing else moves in or out of the realm) while making
|
|
1511
1617
|
* both compaction paths agree on one instance.
|
|
1512
1618
|
*/
|
|
1619
|
+
// ---- toolFilter names the host may not register -------------------------
|
|
1620
|
+
// `tools.restrict` throws for a name outside the host's restrictable set, and that throw escapes
|
|
1621
|
+
// child creation, so one stale name in `vibe_v4_set{toolAllow|toolDeny}` would make every spawn
|
|
1622
|
+
// fail with no operator-facing message. The host's own rejection lists every registered tool, so
|
|
1623
|
+
// the retry below never guesses. FAIL CLOSED: if nothing survives the filter we rethrow instead of
|
|
1624
|
+
// spawning WITHOUT one (that would grant exactly what the operator denied).
|
|
1625
|
+
function registeredToolsFromError(message){
|
|
1626
|
+
const m = /known global tools:\s*([^]*)$/.exec(String(message || ''))
|
|
1627
|
+
if(!m) return undefined
|
|
1628
|
+
const names = m[1].split(',').map(function(s){ return s.trim() }).filter(Boolean)
|
|
1629
|
+
return names.length > 0 ? new Set(names) : undefined
|
|
1630
|
+
}
|
|
1631
|
+
function sanitizeToolFilter(filter, known){
|
|
1632
|
+
if(!filter || !(known instanceof Set) || known.size === 0) return filter
|
|
1633
|
+
const out = {}
|
|
1634
|
+
for(const key of ['allow','deny']){
|
|
1635
|
+
const list = filter[key]
|
|
1636
|
+
if(!Array.isArray(list)) continue
|
|
1637
|
+
const kept = list.filter(function(n){ return known.has(String(n).trim()) })
|
|
1638
|
+
if(kept.length > 0) out[key] = kept
|
|
1639
|
+
}
|
|
1640
|
+
return (out.allow || out.deny) ? out : undefined
|
|
1641
|
+
}
|
|
1642
|
+
async function startWithToolFilter(toolFilter, makeSpec){
|
|
1643
|
+
try {
|
|
1644
|
+
return await subagents.startContinuable(makeSpec(toolFilter))
|
|
1645
|
+
} catch(e){
|
|
1646
|
+
const message = String((e && e.message) || e)
|
|
1647
|
+
const retry = sanitizeToolFilter(toolFilter, registeredToolsFromError(message))
|
|
1648
|
+
if(retry===undefined){
|
|
1649
|
+
console.error('vibe-math-v4: 配置的工具过滤只包含本宿主未注册的工具名,拒绝在不带过滤的情况下启动子代理。filter=' + JSON.stringify(toolFilter) + ' 宿主提示:' + message)
|
|
1650
|
+
throw e
|
|
1651
|
+
}
|
|
1652
|
+
if(JSON.stringify(retry) === JSON.stringify(toolFilter)) throw e
|
|
1653
|
+
console.error('vibe-math-v4: 工具过滤里有本宿主未注册的名字,已只保留已注册的名字重试。dropped=' + JSON.stringify(toolFilter) + ' kept=' + JSON.stringify(retry))
|
|
1654
|
+
// a FRESH spec (and a fresh timeout signal) for the retry
|
|
1655
|
+
return await subagents.startContinuable(makeSpec(retry))
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1513
1659
|
function compactionForAgent(agent){
|
|
1514
1660
|
try { const c = agent && agent.ctx ? agent.ctx.get('compaction') : undefined; if(c && c.compactIfNeeded) return c } catch(e){}
|
|
1515
1661
|
// fallback: this plugin's own plane (host root for a row outside the realm)
|
|
@@ -1520,10 +1666,15 @@ export function apply(ctx) {
|
|
|
1520
1666
|
const agent = liveAgentOf(r.childId)
|
|
1521
1667
|
if(!agent || !agent.session) return
|
|
1522
1668
|
const compaction = compactionForAgent(agent)
|
|
1523
|
-
if(compaction===undefined
|
|
1669
|
+
if(compaction===undefined) return
|
|
1670
|
+
// `compactIfNeeded` is a POLICY call on the current host: it may return null without compacting
|
|
1671
|
+
// and nothing records that, so a real /compact would silently do nothing. `compactNow` is the
|
|
1672
|
+
// forcing verb; feature-detected so older hosts keep the policy call.
|
|
1673
|
+
const force = typeof compaction.compactNow === 'function'
|
|
1674
|
+
if(!force && typeof compaction.compactIfNeeded !== 'function') return
|
|
1524
1675
|
try {
|
|
1525
1676
|
const signal = makeSignal(params.activityTimeoutMs||60000)
|
|
1526
|
-
const result = await compaction.compactIfNeeded(agent, 'pressure', signal)
|
|
1677
|
+
const result = force ? await compaction.compactNow(agent, signal) : await compaction.compactIfNeeded(agent, 'pressure', signal)
|
|
1527
1678
|
if(result && (result.shadowedSeqs||[]).length>0){
|
|
1528
1679
|
// the resident's real session was compacted → its context is now a summary.
|
|
1529
1680
|
// Flag needCompact so the NEXT wake re-anchors the core rules (they may have been blurred).
|
|
@@ -1536,6 +1687,10 @@ export function apply(ctx) {
|
|
|
1536
1687
|
// ---- liveness / scheduling ----
|
|
1537
1688
|
async function scheduleNext(){
|
|
1538
1689
|
if(!running||autoDone){ clearHeartbeat(); return }
|
|
1690
|
+
// Residents the host's live-child cap refused are retried here — the single choke point every
|
|
1691
|
+
// scheduling pass goes through — so a refused spawn is deferred by a whole round (and paced by
|
|
1692
|
+
// the heartbeat) instead of being retried inside the loop that discovered the refusal.
|
|
1693
|
+
if(pendingSpawns.length) await retryPendingSpawns()
|
|
1539
1694
|
if(phase==='brainstorm'){ await maybeFinishBrainstorm(); return }
|
|
1540
1695
|
if(meetingState){ await continueMeetingRound(); return }
|
|
1541
1696
|
if(verifyState){ await continueVerifyRound(); return }
|
|
@@ -1581,6 +1736,10 @@ export function apply(ctx) {
|
|
|
1581
1736
|
const free = mp>0 ? (mp - busy.size) : Number.MAX_SAFE_INTEGER
|
|
1582
1737
|
if(free<=0) break // concurrency cap reached → stop filling
|
|
1583
1738
|
if((now()-r.lastActiveAt)<atOs) break // the remaining are all busy-or-not-idle-enough
|
|
1739
|
+
// A resident with no childId has nothing to wake (wakeResident would refuse): that is a
|
|
1740
|
+
// resident whose spawn the host cap refused, and it is retried by retryPendingSpawns() at
|
|
1741
|
+
// the top of the next pass — never in a tight loop here.
|
|
1742
|
+
if(!r.childId) continue
|
|
1584
1743
|
let ok=false
|
|
1585
1744
|
try { ok = await wakeResident(r, await heartbeatPrompt(r), 'normal') } catch(e){ ok=false }
|
|
1586
1745
|
if(ok) started++
|
|
@@ -1724,8 +1883,20 @@ export function apply(ctx) {
|
|
|
1724
1883
|
busy=new Set(); wakeKind=new Map(); currentResident=''; pendingMeeting=null; lastSyncMeetingAt=0; finalizeLock=null; verifiedRecently.clear() // fresh run must NOT inherit stale concurrency/coordination state (busy/wakeKind/currentResident/pendingMeeting) from a previous run on the same reused session
|
|
1725
1884
|
lastActivityAt=now(); lastProgressAt=now() // fresh stall/activity clock for the new run (else B could fire immediately on a reused session)
|
|
1726
1885
|
const dirs=Array.isArray(seedDirections)?seedDirections.slice(0,params.residentCount):[]
|
|
1727
|
-
|
|
1728
|
-
|
|
1886
|
+
pendingSpawns=[] // a fresh run replaces the roster: nothing is queued from before
|
|
1887
|
+
beginSpawnRound() // the whole spawn loop is ONE round (one cap notice, not N)
|
|
1888
|
+
let startedCount=0
|
|
1889
|
+
// spawnResident no longer throws for the host's live-child cap: it queues the refused resident
|
|
1890
|
+
// and returns false, so a capped host leaves a RUNNING team of the residents it accepted
|
|
1891
|
+
// instead of an aborted half-built run.
|
|
1892
|
+
for(let i=0;i<params.residentCount;i++){ const r=newResident(dirs[i]||''); if(await spawnResident(r)) startedCount++ }
|
|
1893
|
+
await saveAll()
|
|
1894
|
+
if(pendingSpawns.length) armHeartbeat() // retry the queued residents on the next round
|
|
1895
|
+
// Report the LIVE count when the host refused part of the team: "4 resident(s)" while only 2
|
|
1896
|
+
// exist would be exactly the kind of half-truth this fix exists to remove.
|
|
1897
|
+
const refused=pendingSpawns.length
|
|
1898
|
+
return {ok:true,message:'v4 started: '+(refused?(startedCount+'/'+params.residentCount):params.residentCount)+' resident(s) brainstorming'
|
|
1899
|
+
+(refused?('(宿主同时在活的子代理已达上限,'+refused+' 个常驻已排队、有空位时自动重试;'+hostChildLimitHint(hostChildLimit)+')'):''),project:currentProject}
|
|
1729
1900
|
}
|
|
1730
1901
|
async function resume(){
|
|
1731
1902
|
currentProject=await readCurrentProject(); await ensureDirs()
|
|
@@ -1761,6 +1932,7 @@ export function apply(ctx) {
|
|
|
1761
1932
|
for(const [,r] of residents){ r.childId=''; r.status='brainstorm'; r.insight=''; r.roundsSinceCompact=0 }
|
|
1762
1933
|
busy=new Set(); wakeKind=new Map(); currentResident=''; pendingMeeting=null; pendingVerify=[]; verifyState=null; meetingState=null; finalizeLock=null; verifiedRecently.clear()
|
|
1763
1934
|
}
|
|
1935
|
+
beginSpawnRound() // the whole re-spawn loop is ONE round for the cap notice
|
|
1764
1936
|
for(const [,r] of residents){ if(!r.childId){ await spawnResident(r) } }
|
|
1765
1937
|
if(!running){ running=true; autoDone=false; if(phase==='idle') phase='active' }
|
|
1766
1938
|
if(needRespawn && phase!=='brainstorm') phase='brainstorm' // let re-spawned residents re-bootstrap together
|
|
@@ -1773,6 +1945,10 @@ export function apply(ctx) {
|
|
|
1773
1945
|
}
|
|
1774
1946
|
function status(){ return { ok:true, running, phase, autoDone, project:currentProject, residentCount:residents.size,
|
|
1775
1947
|
residents:listResidents(), busy:[...busy], taskboard:taskboard.length,
|
|
1948
|
+
// Residents the host's live-child cap refused (maxActiveSubagents): queued, not lost. Without
|
|
1949
|
+
// this the only trace would be the one console line, and `residentCount` alone cannot tell a
|
|
1950
|
+
// short team from a deliberately small one.
|
|
1951
|
+
pendingSpawns: pendingSpawns.length, hostChildLimit: (hostChildLimit===undefined?null:hostChildLimit),
|
|
1776
1952
|
meetingInProgress: !!(meetingState), verifyInProgress: !!(verifyState), pendingVerify: pendingVerify.length?pendingVerify[0].targetId:null, pendingVerifyCount: pendingVerify.length,
|
|
1777
1953
|
parkedMeeting: pendingMeeting?pendingMeeting.agenda:null,
|
|
1778
1954
|
// The Lean knobs and the per-object formal records are part of the readable status: without
|
|
@@ -1803,7 +1979,9 @@ export function apply(ctx) {
|
|
|
1803
1979
|
// run is live: on a concluded (autoDone) or never-started/paused run the new member would work
|
|
1804
1980
|
// with nobody to coordinate (zombie work on a project the group already declared done).
|
|
1805
1981
|
if(!running || autoDone) return {ok:false,message:'no active run to join (start or resume first)'}
|
|
1806
|
-
const r=newResident(direction||'')
|
|
1982
|
+
const r=newResident(direction||'')
|
|
1983
|
+
beginSpawnRound()
|
|
1984
|
+
if(!await spawnResident(r)) return {ok:false,code:'ACTIVATION_LIMIT_REACHED',message:'本宿主同时在活的子代理已达上限,'+r.rId+' 未能创建(已排队、有空位时自动重试)。'+hostChildLimitHint(hostChildLimit)}
|
|
1807
1985
|
// Mid-meeting additions must join the meeting's speaking order; otherwise allSpoke (over CURRENT
|
|
1808
1986
|
// residents) can never be true for the new member (not in the snapshot order) and the meeting is
|
|
1809
1987
|
// only ever released by the stuck watchdog instead of finalizing with everyone's input.
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
# tasks — the academician does that, as a member subject to the same m-vote rule.
|
|
20
20
|
#
|
|
21
21
|
# The vibe-math-v5 plugin row consumes HOST subagents/agents/fs/tools/commands/
|
|
22
|
-
# timer
|
|
23
|
-
#
|
|
24
|
-
#
|
|
22
|
+
# timer services and publishes nothing, so it needs no isolate realm. Institute
|
|
23
|
+
# state lives in its own hardened JSON file (State/<institute>.v5state.json):
|
|
24
|
+
# the plugin appends NOTHING to any host session log.
|
|
25
25
|
|
|
26
26
|
# ── identity ────────────────────────────────────────────────────────────────
|
|
27
27
|
|
|
@@ -373,10 +373,10 @@
|
|
|
373
373
|
# organization & task assignment / m-vote boolean consensus verification /
|
|
374
374
|
# hire & fire / context compaction / resume / human intervention).
|
|
375
375
|
#
|
|
376
|
-
# It consumes HOST subagents/agents/fs/tools/commands/timer services
|
|
377
|
-
#
|
|
378
|
-
#
|
|
379
|
-
#
|
|
380
|
-
#
|
|
376
|
+
# It consumes HOST subagents/agents/fs/tools/commands/timer services; it provides
|
|
377
|
+
# no service of its own, so it sits loose (no isolate realm). Institute state is
|
|
378
|
+
# persisted by the plugin itself in State/<institute>.v5state.json — the host
|
|
379
|
+
# session log is never written (DSH refuses to load a session that carries an
|
|
380
|
+
# unknown event type, and Session.append cannot mark one ignorable).
|
|
381
381
|
- id: vibe-math-v5
|
|
382
382
|
name: './vibe-math-v5.js'
|
package/vibe-math-v5/preset.yml
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
name: Vibe Math V5
|
|
2
|
-
description: 研究所体系(第五代架构):设院士(领头人/组织协调中心)、常驻研究员、临时工三类职员;成员可群聊/开会、自主分工与雇佣解雇临时工;院士统筹全所、拆解并分派任务、设定优先级、主持会议、督导进度;每位成员有自己的 Progress/命题/方法/子问题库(progress 有完整定义与用途说明);任何结论须至少 m 名有表决权者一致给出布尔概率 1 或 0 才写入 Verified
|
|
2
|
+
description: 研究所体系(第五代架构):设院士(领头人/组织协调中心)、常驻研究员、临时工三类职员;成员可群聊/开会、自主分工与雇佣解雇临时工;院士统筹全所、拆解并分派任务、设定优先级、主持会议、督导进度;每位成员有自己的 Progress/命题/方法/子问题库(progress 有完整定义与用途说明);任何结论须至少 m 名有表决权者一致给出布尔概率 1 或 0 才写入 Verified/,否则留库附全组平均概率;上下文达阈值自动压缩;仅当全体有表决权者一致认为原问题已解决才停止;状态存于加固 JSON(State/<研究所>.v5state.json),支持断点续跑与多会话隔离。
|