dsh-vibe-math 2.4.0 → 2.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.
@@ -109,17 +109,9 @@ export function apply(ctx) {
109
109
  const activityLogCap = 200
110
110
 
111
111
  // ---- utils ----
112
- function now(){ return Date.now() }
113
- function uuid(){ const h='0123456789abcdef'; let s=''; for(let i=0;i<36;i++){ if(i===8||i===13||i===18||i===23) s+='-'; else s+=h[Math.floor(Math.random()*16)] } return s }
114
- function shortId(){ const h='0123456789abcdef'; let s=''; for(let i=0;i<8;i++) s+=h[Math.floor(Math.random()*16)]; return s }
115
- function clamp01(v){ const n=Number(v); if(!Number.isFinite(n)) return 0.5; return Math.max(0,Math.min(1,n)) }
116
- function fmtTime(ts){ try { return new Date(ts||now()).toISOString().replace('T',' ').slice(0,19) } catch(e){ return String(ts||'') } }
117
- function cl(x){ return clamp01(Number(x)) }
118
112
  // contextPct is a PERCENT (0-100); never clamp to 0-1 or the compactThreshold
119
113
  // comparison (e.g. 66) becomes `1.0 >= 66` and never fires.
120
- function clPct(x){ const n=Number(x); if(!Number.isFinite(n)) return 0; return Math.max(0,Math.min(100,n)) }
121
114
  function textBlock(t){ return { type:'text', text:String(t) } }
122
- function blocksToText(b){ if(!b) return ''; let out=''; for(const x of b){ if(x&&x.type==='text'&&typeof x.text==='string') out+=x.text+'\n' } return out.trim() }
123
115
  function logActivity(event,detail){ activityLog.push({at:now(),event,detail:String(detail||'')}); if(activityLog.length>activityLogCap) activityLog.shift() }
124
116
  function logDecision(kind,detail){ decisions.push({at:now(),kind,detail:String(detail||'')}) }
125
117
  // Record that the project made real progress (new artifact, meeting, verify, task, or a
@@ -135,7 +127,6 @@ export function apply(ctx) {
135
127
  // (misconfigured via vibe_v4_set) would otherwise make recoverStallMs negative → every meeting/
136
128
  // verify watchdog fires INSTANTLY (abandoning all consensus) and A-fill's idle window would never
137
129
  // elapse (waking everyone every pass). Guard every duration read with this.
138
- function posMs(v,def){ const n=Number(v); return (Number.isFinite(n)&&n>0)?n:(def||120000) }
139
130
  function recoverStallMs(){ return posMs(params.activityTimeoutMs,120000) * 2 }
140
131
  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' }
141
132
  // Per-resident model/provider inheritance: when params.provider / params.model are set,
@@ -155,16 +146,11 @@ export function apply(ctx) {
155
146
  function workspaceRoot(){ try { if(rootAgent&&rootAgent.session&&rootAgent.session.header&&rootAgent.session.header.cwd) return rootAgent.session.header.cwd } catch(e){} const sp=sandboxPolicyOf(); if(sp&&sp.workspaceRoot) return sp.workspaceRoot; return '.' }
156
147
  function vibeRoot(){ return (workspaceRoot()+'/VibeMath').replace(/\\/g,'/') }
157
148
  function frameworkRoot(){ return vibeRoot()+'/Projects/'+currentProject }
158
- function slugify(s){ const t=String(s==null?'':s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g,'-').replace(/^-+|-+$/g,''); return t||'project' }
159
149
  // Object ids (verify targets, recorded cards) become FILE NAMES and DIRECTORY PATHS
160
150
  // (Verified/命题/<id>.md, Shared/debates/<id>.md, Propos/<r>/<id>.md, source-card scans).
161
151
  // A hostile/sloppy id containing path separators ('../../x') or Windows-forbidden chars would
162
152
  // escape the project tree. Keep every harmless character (incl. Chinese) and replace only
163
153
  // separators/control chars; strip leading/trailing dots/dashes so the name is never '.'/'..'.
164
- function idSafe(s){
165
- const t=String(s==null?'':s).trim().replace(/[\\/:*?"<>|\u0000-\u001f]+/g,'-').replace(/-{2,}/g,'-').replace(/^[.\-]+|[.\-]+$/g,'')
166
- return t||'id'
167
- }
168
154
  let warnedNoPolicy = false
169
155
  function warnNoPolicyOnce(){ if(!warnedNoPolicy){ warnedNoPolicy=true; console.error('vibe-math-v4: sandboxPolicy unavailable; writes go out with no explicit policy') } }
170
156
  function getPolicy(){ const sp=sandboxPolicyOf(); if(!sp){ warnNoPolicyOnce(); return undefined } try { if(rootAgent&&rootAgent.session) return sp.resolve({session:rootAgent.session}) } catch(e){ warnNoPolicyOnce() } try { const p=sp.resolve({}); if(!warnedNoPolicy){ warnedNoPolicy=true; console.error('vibe-math-v4: falling back to sandboxPolicy.resolve({}) — the fence root is the host-configured workspace, not necessarily this session cwd') } return p } catch(e){ warnNoPolicyOnce() } return undefined }
@@ -273,13 +259,6 @@ export function apply(ctx) {
273
259
  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 } }
274
260
  async function readCurrentProject(){ try { const t=await readTextAbs(vibeRoot()+'/.current'); if(t) return String(t).trim() } catch(e){} return currentProject }
275
261
  async function writeCurrentProject(){ try { await writeTextAbs(vibeRoot()+'/.current', currentProject) } catch(e){} }
276
- function tryJson(s){ try { return JSON.parse(s) } catch(e){ return undefined } }
277
- function parseReply(text){
278
- let obj; const fence=/```(?:json)?[ \t]*([\s\S]*?)```/gi; let m
279
- while((m=fence.exec(text))!==null){ const o=tryJson(m[1].trim()); if(o&&typeof o==='object'&&!Array.isArray(o)) obj=o }
280
- if(!obj){ const w=tryJson(text.trim()); if(w&&typeof w==='object'&&!Array.isArray(w)) obj=w }
281
- return obj||{}
282
- }
283
262
 
284
263
  // ================= Lean formal verification ==============================
285
264
  // Contract: docs/formal-verification.md (shared by v2/v3/v4/v5).
@@ -1622,23 +1601,6 @@ export function apply(ctx) {
1622
1601
  // fail with no operator-facing message. The host's own rejection lists every registered tool, so
1623
1602
  // the retry below never guesses. FAIL CLOSED: if nothing survives the filter we rethrow instead of
1624
1603
  // 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
1604
  async function startWithToolFilter(toolFilter, makeSpec){
1643
1605
  try {
1644
1606
  return await subagents.startContinuable(makeSpec(toolFilter))
@@ -2259,3 +2221,86 @@ export function apply(ctx) {
2259
2221
  .finally(()=>{ if(s.forgetAgent) s.forgetAgent(info.id) })
2260
2222
  })
2261
2223
  }
2224
+
2225
+ // ---- test seam: pure, stateless helpers --------------------------------
2226
+ // These helpers were declared inside `apply()` and are now declared at module scope, so
2227
+ // `apply()` closes over exactly the same function objects this export hands out. The audit
2228
+ // suites therefore exercise the REAL implementations by importing this module, instead of
2229
+ // extracting source text and compiling function bodies through the Function constructor
2230
+ // (dynamic code execution, rejected by the plugin-catalog security scan as
2231
+ // DANGEROUS_DYNAMIC_EXECUTION).
2232
+ //
2233
+ // Contract: no member may touch `ctx`, session state or mutable module state. Most are pure;
2234
+ // three are deliberately non-deterministic (`uuid`/`shortId` use Math.random, `fmtTime` falls back
2235
+ // to the clock) and `parseProgress` normalises the object it is handed in place (pre-existing).
2236
+ // Nothing here is used by the plugin at runtime except through `apply()`, and behaviour is
2237
+ // byte-identical to the previous in-`apply` declarations.
2238
+ export const __testHelpers = {
2239
+ uuid,
2240
+ shortId,
2241
+ clamp01,
2242
+ fmtTime,
2243
+ cl,
2244
+ clPct,
2245
+ blocksToText,
2246
+ posMs,
2247
+ slugify,
2248
+ idSafe,
2249
+ tryJson,
2250
+ parseReply,
2251
+ sanitizeToolFilter,
2252
+ registeredToolsFromError,
2253
+ }
2254
+
2255
+ function now(){ return Date.now() }
2256
+
2257
+ function uuid(){ const h='0123456789abcdef'; let s=''; for(let i=0;i<36;i++){ if(i===8||i===13||i===18||i===23) s+='-'; else s+=h[Math.floor(Math.random()*16)] } return s }
2258
+
2259
+ function shortId(){ const h='0123456789abcdef'; let s=''; for(let i=0;i<8;i++) s+=h[Math.floor(Math.random()*16)]; return s }
2260
+
2261
+ function clamp01(v){ const n=Number(v); if(!Number.isFinite(n)) return 0.5; return Math.max(0,Math.min(1,n)) }
2262
+
2263
+ function fmtTime(ts){ try { return new Date(ts||now()).toISOString().replace('T',' ').slice(0,19) } catch(e){ return String(ts||'') } }
2264
+
2265
+ function cl(x){ return clamp01(Number(x)) }
2266
+
2267
+ function clPct(x){ const n=Number(x); if(!Number.isFinite(n)) return 0; return Math.max(0,Math.min(100,n)) }
2268
+
2269
+ function blocksToText(b){ if(!b) return ''; let out=''; for(const x of b){ if(x&&x.type==='text'&&typeof x.text==='string') out+=x.text+'\n' } return out.trim() }
2270
+
2271
+ function posMs(v,def){ const n=Number(v); return (Number.isFinite(n)&&n>0)?n:(def||120000) }
2272
+
2273
+ function slugify(s){ const t=String(s==null?'':s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g,'-').replace(/^-+|-+$/g,''); return t||'project' }
2274
+
2275
+ function idSafe(s){
2276
+ const t=String(s==null?'':s).trim().replace(/[\\/:*?"<>|\u0000-\u001f]+/g,'-').replace(/-{2,}/g,'-').replace(/^[.\-]+|[.\-]+$/g,'')
2277
+ return t||'id'
2278
+ }
2279
+
2280
+ function tryJson(s){ try { return JSON.parse(s) } catch(e){ return undefined } }
2281
+
2282
+ function parseReply(text){
2283
+ let obj; const fence=/```(?:json)?[ \t]*([\s\S]*?)```/gi; let m
2284
+ while((m=fence.exec(text))!==null){ const o=tryJson(m[1].trim()); if(o&&typeof o==='object'&&!Array.isArray(o)) obj=o }
2285
+ if(!obj){ const w=tryJson(text.trim()); if(w&&typeof w==='object'&&!Array.isArray(w)) obj=w }
2286
+ return obj||{}
2287
+ }
2288
+
2289
+ function sanitizeToolFilter(filter, known){
2290
+ if(!filter || !(known instanceof Set) || known.size === 0) return filter
2291
+ const out = {}
2292
+ for(const key of ['allow','deny']){
2293
+ const list = filter[key]
2294
+ if(!Array.isArray(list)) continue
2295
+ const kept = list.filter(function(n){ return known.has(String(n).trim()) })
2296
+ if(kept.length > 0) out[key] = kept
2297
+ }
2298
+ return (out.allow || out.deny) ? out : undefined
2299
+ }
2300
+
2301
+ function registeredToolsFromError(message){
2302
+ const m = /known global tools:\s*([^]*)$/.exec(String(message || ''))
2303
+ if(!m) return undefined
2304
+ const names = m[1].split(',').map(function(s){ return s.trim() }).filter(Boolean)
2305
+ return names.length > 0 ? new Set(names) : undefined
2306
+ }
@@ -129,19 +129,13 @@ export function apply(ctx) {
129
129
  }
130
130
 
131
131
  // ---- utils -------------------------------------------------------------
132
- const now = () => Date.now()
133
132
  function hex(n) { let s = ''; for (let i = 0; i < n; i++) s += '0123456789abcdef'[Math.floor(Math.random() * 16)]; return s }
134
133
  const shortId = () => hex(8)
135
- function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
136
134
  // contextPct is a PERCENT (0-100); never clamp to 0-1 or the compactThreshold
137
135
  // comparison (e.g. 66) becomes `1.0 >= 66` and never fires (v4 §17 defect).
138
- function clPct(x) { const n = Number(x); if (!Number.isFinite(n)) return 0; return Math.max(0, Math.min(100, n)) }
139
136
  // Positive duration with a safe fallback: a NEGATIVE/NaN duration parameter must
140
137
  // never make a watchdog fire instantly or an idle window never elapse (v4 §30-T41).
141
- function posMs(v, def) { const n = Number(v); return (Number.isFinite(n) && n > 0) ? n : (def || 120000) }
142
138
  const textBlock = (t) => ({ type: 'text', text: String(t) })
143
- function blocksToText(b) { if (!b) return ''; let o = ''; for (const x of b) { if (x && x.type === 'text' && typeof x.text === 'string') o += x.text + '\n' } return o }
144
- function fmtTime(ts) { try { return new Date(ts || now()).toISOString().replace('T', ' ').slice(0, 19) } catch (e) { return String(ts || '') } }
145
139
  function makeSignal(ms) { try { return AbortSignal.timeout(posMs(ms, 30000)) } catch (e) { return undefined } }
146
140
 
147
141
  // Object ids (verify targets, card ids, member ids) become FILE NAMES and DIRECTORY
@@ -149,10 +143,6 @@ export function apply(ctx) {
149
143
  // characters would escape the project tree. Keep every harmless character (incl.
150
144
  // Chinese) and replace only separators/control chars; strip leading/trailing dots
151
145
  // and dashes so the name is never '.' or '..' (v4 §30-T39).
152
- function idSafe(s) {
153
- const t = String(s == null ? '' : s).trim().replace(/[\\/:*?"<>|\u0000-\u001f]+/g, '-').replace(/-{2,}/g, '-').replace(/^[.\-]+|[.\-]+$/g, '')
154
- return t
155
- }
156
146
  // Advisory write-scope normalisation, ported from DSH agent-teams: backslashes to
157
147
  // '/', strip a leading './' and trailing '/', reject empty/absolute/drive-letter/
158
148
  // '..'-segment scopes.
@@ -3476,30 +3466,6 @@ export function apply(ctx) {
3476
3466
  }
3477
3467
 
3478
3468
  // ---- reply parsing -----------------------------------------------------
3479
- function tryJson(s) { try { return JSON.parse(s) } catch (e) { return undefined } }
3480
- function parseReply(text) {
3481
- let obj
3482
- const fence = /```(?:json)?[ \t]*([\s\S]*?)```/gi
3483
- let m
3484
- while ((m = fence.exec(text)) !== null) {
3485
- const o = tryJson(String(m[1]).trim())
3486
- if (o && typeof o === 'object' && !Array.isArray(o)) obj = o
3487
- }
3488
- if (!obj) {
3489
- const w = tryJson(String(text || '').trim())
3490
- if (w && typeof w === 'object' && !Array.isArray(w)) obj = w
3491
- }
3492
- if (!obj) {
3493
- // Last resort: the outermost {...} span (models sometimes wrap prose around it).
3494
- const t = String(text || '')
3495
- const i = t.indexOf('{'), j = t.lastIndexOf('}')
3496
- if (i !== -1 && j > i) {
3497
- const o = tryJson(t.slice(i, j + 1))
3498
- if (o && typeof o === 'object' && !Array.isArray(o)) obj = o
3499
- }
3500
- }
3501
- return obj || {}
3502
- }
3503
3469
  function normVerdictNumber(v) {
3504
3470
  // verdict is a PURE 0-1 probability. Models often send a quoted number, and a
3505
3471
  // quoted "0.9" used to fall through to a 0.5 default and be silently recorded
@@ -3704,23 +3670,6 @@ export function apply(ctx) {
3704
3670
  // Same guard as v2/v3/v4: `tools.restrict` throws for an unregistered name and the throw escapes
3705
3671
  // child creation, so a stale `vibe_v5_set{toolAllow|toolDeny|tempToolAllow|tempToolDeny}` would
3706
3672
  // make every member spawn fail silently. The host names the registered tools in its rejection.
3707
- function registeredToolsFromError(message){
3708
- const m = /known global tools:\s*([^]*)$/.exec(String(message || ''))
3709
- if(!m) return undefined
3710
- const names = m[1].split(',').map(function(s){ return s.trim() }).filter(Boolean)
3711
- return names.length > 0 ? new Set(names) : undefined
3712
- }
3713
- function sanitizeToolFilter(filter, known){
3714
- if(!filter || !(known instanceof Set) || known.size === 0) return filter
3715
- const out = {}
3716
- for(const key of ['allow','deny']){
3717
- const list = filter[key]
3718
- if(!Array.isArray(list)) continue
3719
- const kept = list.filter(function(n){ return known.has(String(n).trim()) })
3720
- if(kept.length > 0) out[key] = kept
3721
- }
3722
- return (out.allow || out.deny) ? out : undefined
3723
- }
3724
3673
  async function startWithToolFilter(toolFilter, makeSpec){
3725
3674
  try {
3726
3675
  return await subagents.startContinuable(makeSpec(toolFilter))
@@ -3904,10 +3853,6 @@ export function apply(ctx) {
3904
3853
  '',
3905
3854
  ].join('\n'))
3906
3855
  }
3907
- function slugify(s) {
3908
- const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, '')
3909
- return t || ''
3910
- }
3911
3856
  async function doStart(args) {
3912
3857
  const a = args || {}
3913
3858
  if (running && !autoDone) return { ok: false, code: 'V5_INSTITUTE_STATE', message: 'already running' }
@@ -4486,3 +4431,97 @@ export function apply(ctx) {
4486
4431
  .finally(() => s.forgetAgent(info.id))
4487
4432
  })
4488
4433
  }
4434
+
4435
+ const now = () => Date.now()
4436
+
4437
+ // ---- test seam: pure, stateless helpers --------------------------------
4438
+ // These helpers were declared inside `apply()` and are now declared at module scope, so
4439
+ // `apply()` closes over exactly the same function objects this export hands out. The audit
4440
+ // suites therefore exercise the REAL implementations by importing this module, instead of
4441
+ // extracting source text and compiling function bodies through the Function constructor
4442
+ // (dynamic code execution, rejected by the plugin-catalog security scan as
4443
+ // DANGEROUS_DYNAMIC_EXECUTION).
4444
+ //
4445
+ // Contract: no member may touch `ctx`, session state or mutable module state. Most are pure;
4446
+ // three are deliberately non-deterministic (`uuid`/`shortId` use Math.random, `fmtTime` falls back
4447
+ // to the clock) and `parseProgress` normalises the object it is handed in place (pre-existing).
4448
+ // Nothing here is used by the plugin at runtime except through `apply()`, and behaviour is
4449
+ // byte-identical to the previous in-`apply` declarations.
4450
+ export const __testHelpers = {
4451
+ clamp01,
4452
+ clPct,
4453
+ posMs,
4454
+ blocksToText,
4455
+ fmtTime,
4456
+ idSafe,
4457
+ slugify,
4458
+ tryJson,
4459
+ parseReply,
4460
+ sanitizeToolFilter,
4461
+ registeredToolsFromError,
4462
+ }
4463
+
4464
+ function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
4465
+
4466
+ function clPct(x) { const n = Number(x); if (!Number.isFinite(n)) return 0; return Math.max(0, Math.min(100, n)) }
4467
+
4468
+ function posMs(v, def) { const n = Number(v); return (Number.isFinite(n) && n > 0) ? n : (def || 120000) }
4469
+
4470
+ function blocksToText(b) { if (!b) return ''; let o = ''; for (const x of b) { if (x && x.type === 'text' && typeof x.text === 'string') o += x.text + '\n' } return o }
4471
+
4472
+ function fmtTime(ts) { try { return new Date(ts || now()).toISOString().replace('T', ' ').slice(0, 19) } catch (e) { return String(ts || '') } }
4473
+
4474
+ function idSafe(s) {
4475
+ const t = String(s == null ? '' : s).trim().replace(/[\\/:*?"<>|\u0000-\u001f]+/g, '-').replace(/-{2,}/g, '-').replace(/^[.\-]+|[.\-]+$/g, '')
4476
+ return t
4477
+ }
4478
+
4479
+ function slugify(s) {
4480
+ const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, '')
4481
+ return t || ''
4482
+ }
4483
+
4484
+ function tryJson(s) { try { return JSON.parse(s) } catch (e) { return undefined } }
4485
+
4486
+ function parseReply(text) {
4487
+ let obj
4488
+ const fence = /```(?:json)?[ \t]*([\s\S]*?)```/gi
4489
+ let m
4490
+ while ((m = fence.exec(text)) !== null) {
4491
+ const o = tryJson(String(m[1]).trim())
4492
+ if (o && typeof o === 'object' && !Array.isArray(o)) obj = o
4493
+ }
4494
+ if (!obj) {
4495
+ const w = tryJson(String(text || '').trim())
4496
+ if (w && typeof w === 'object' && !Array.isArray(w)) obj = w
4497
+ }
4498
+ if (!obj) {
4499
+ // Last resort: the outermost {...} span (models sometimes wrap prose around it).
4500
+ const t = String(text || '')
4501
+ const i = t.indexOf('{'), j = t.lastIndexOf('}')
4502
+ if (i !== -1 && j > i) {
4503
+ const o = tryJson(t.slice(i, j + 1))
4504
+ if (o && typeof o === 'object' && !Array.isArray(o)) obj = o
4505
+ }
4506
+ }
4507
+ return obj || {}
4508
+ }
4509
+
4510
+ function sanitizeToolFilter(filter, known){
4511
+ if(!filter || !(known instanceof Set) || known.size === 0) return filter
4512
+ const out = {}
4513
+ for(const key of ['allow','deny']){
4514
+ const list = filter[key]
4515
+ if(!Array.isArray(list)) continue
4516
+ const kept = list.filter(function(n){ return known.has(String(n).trim()) })
4517
+ if(kept.length > 0) out[key] = kept
4518
+ }
4519
+ return (out.allow || out.deny) ? out : undefined
4520
+ }
4521
+
4522
+ function registeredToolsFromError(message){
4523
+ const m = /known global tools:\s*([^]*)$/.exec(String(message || ''))
4524
+ if(!m) return undefined
4525
+ const names = m[1].split(',').map(function(s){ return s.trim() }).filter(Boolean)
4526
+ return names.length > 0 ? new Set(names) : undefined
4527
+ }