@fugood/buttress-server 2.25.9 → 2.25.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fugood/buttress-server",
3
- "version": "2.25.9",
3
+ "version": "2.25.11",
4
4
  "main": "lib/index.mjs",
5
5
  "types": "lib/index.d.ts",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "scripts": {
21
21
  "typecheck": "tsc --noEmit",
22
- "build": "tsdown -c rolldown.config.js --config-loader native && tsc --noCheck --emitDeclarationOnly && cp ../buttress-backend-core/src/backends/mlx-bridge.py lib/mlx-bridge.py",
22
+ "build": "tsdown -c rolldown.config.js --config-loader native && tsc --noCheck --emitDeclarationOnly && cp ../buttress-backend-core/src/backends/mlx-bridge.py lib/mlx-bridge.py && cp ../../node_modules/quickjs-wasi/quickjs.wasm lib/quickjs.wasm",
23
23
  "build:dist": "$npm_execpath scripts/build-distribution.js",
24
24
  "release": "$npm_execpath scripts/build-distribution.js",
25
25
  "release-beta": "$npm_execpath scripts/build-distribution.js --beta",
@@ -37,12 +37,12 @@
37
37
  ],
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
- "@earendil-works/pi-agent-core": "^0.85.0",
41
- "@earendil-works/pi-ai": "0.85.0",
42
- "@earendil-works/pi-tui": "^0.85.0",
40
+ "@earendil-works/pi-agent-core": "^1.0.0",
41
+ "@earendil-works/pi-ai": "1.0.0",
42
+ "@earendil-works/pi-tui": "^1.0.0",
43
43
  "@elysiajs/cors": "^1.1.1",
44
44
  "@elysiajs/node": "^1.4.2",
45
- "@fugood/llama.node": "^1.8.0-rc.6",
45
+ "@fugood/llama.node": "^1.8.0-rc.9",
46
46
  "@fugood/whisper.node": "^1.2.0-rc.0",
47
47
  "@huggingface/gguf": "^0.3.2",
48
48
  "@iarna/toml": "^3.0.0",
@@ -69,7 +69,8 @@
69
69
  "zod": "^3.25.76"
70
70
  },
71
71
  "devDependencies": {
72
- "@fugood/bricks-cli": "^2.25.9",
72
+ "@fugood/agent-tool-runtime": "^2.25.10",
73
+ "@fugood/bricks-cli": "^2.25.11",
73
74
  "@fugood/bricks-devtools-core": "^2.25.5",
74
75
  "tsdown": "^0.22.4",
75
76
  "typescript": "^7.0.2"
@@ -603,6 +603,27 @@
603
603
  </div>
604
604
  </div>
605
605
 
606
+ <!-- GGML Typed Decision Status (hidden unless active) -->
607
+ <div class="card" id="ggmlDecisionCard" style="display:none">
608
+ <div class="card-header">
609
+ <span class="card-title">Typed Decision Status</span>
610
+ <span class="badge badge-info" id="ggmlDecisionGeneratorCount">0 generators</span>
611
+ </div>
612
+ <div id="ggmlDecisionGenerators"></div>
613
+ <div class="section">
614
+ <div class="section-title collapsible" onclick="toggleSection(this)">Model Load History</div>
615
+ <div class="collapsible-content" id="ggmlDecisionModelHistory">
616
+ <div class="empty-state">No model load history</div>
617
+ </div>
618
+ </div>
619
+ <div class="section">
620
+ <div class="section-title collapsible" onclick="toggleSection(this)">Decision History</div>
621
+ <div class="collapsible-content" id="ggmlDecisionHistory">
622
+ <div class="empty-state">No decision history</div>
623
+ </div>
624
+ </div>
625
+ </div>
626
+
606
627
  <!-- Local Functions (hidden unless enabled) -->
607
628
  <div class="card" id="functionsCard" style="display:none">
608
629
  <div class="card-header">
@@ -1049,6 +1070,54 @@
1049
1070
  })
1050
1071
  }
1051
1072
 
1073
+ function renderGgmlDecisionGenerators(generators) {
1074
+ const container = document.getElementById('ggmlDecisionGenerators')
1075
+ document.getElementById('ggmlDecisionGeneratorCount').textContent =
1076
+ `${generators.length} generator${generators.length !== 1 ? 's' : ''}`
1077
+
1078
+ withScrollPreserve(container, () => {
1079
+ if (generators.length === 0) {
1080
+ container.innerHTML = '<div class="empty-state">No typed decision generators loaded</div>'
1081
+ return
1082
+ }
1083
+
1084
+ container.innerHTML = `
1085
+ <div class="table-wrapper">
1086
+ <div class="table-inner">
1087
+ <table>
1088
+ <thead>
1089
+ <tr>
1090
+ <th>ID</th>
1091
+ <th>Model</th>
1092
+ <th>Type</th>
1093
+ <th>Quant</th>
1094
+ <th>Variant</th>
1095
+ <th>GPU</th>
1096
+ <th>Model Size</th>
1097
+ <th>Context</th>
1098
+ </tr>
1099
+ </thead>
1100
+ <tbody>
1101
+ ${generators.map(g => `
1102
+ <tr>
1103
+ <td class="mono">${escapeHtml(g.id?.slice(0, 8) || '-')}</td>
1104
+ <td>${formatModelName(g.repoId)}${g.mmprojFilename ? '<br><span style="font-size:11px;color:var(--text-muted)">+ ' + escapeHtml(g.mmprojFilename) + '</span>' : ''}</td>
1105
+ <td>${escapeHtml(g.decisionType || '-')}</td>
1106
+ <td>${escapeHtml(g.quantization || '-')}</td>
1107
+ <td>${escapeHtml(g.variant || '-')}</td>
1108
+ <td><span class="badge ${g.useGpu ? 'badge-success' : 'badge-info'}">${g.useGpu ? 'yes' : 'no'}</span></td>
1109
+ <td>${formatBytes(g.modelBytes)}</td>
1110
+ <td>${g.hasContext ? '<span class="badge badge-success">loaded</span>' : '-'}</td>
1111
+ </tr>
1112
+ `).join('')}
1113
+ </tbody>
1114
+ </table>
1115
+ </div>
1116
+ </div>
1117
+ `
1118
+ })
1119
+ }
1120
+
1052
1121
  // Escape HTML
1053
1122
  function escapeHtml(str) {
1054
1123
  if (str == null) return ''
@@ -1077,6 +1146,7 @@
1077
1146
  const onnxStt = status.onnxStt || {}
1078
1147
  const onnxTts = status.onnxTts || {}
1079
1148
  const ggmlTts = status.ggmlTts || {}
1149
+ const ggmlDecision = status.ggmlDecision || {}
1080
1150
 
1081
1151
  // LLM
1082
1152
  renderLlmGenerators(llm.generators || [])
@@ -1288,6 +1358,39 @@
1288
1358
  },
1289
1359
  ])
1290
1360
 
1361
+ // GGML typed decisions
1362
+ const hasGgmlDecision = (ggmlDecision.generators?.length > 0) ||
1363
+ (ggmlDecision.history?.modelLoads?.length > 0) ||
1364
+ (ggmlDecision.history?.decisions?.length > 0)
1365
+ document.getElementById('ggmlDecisionCard').style.display = hasGgmlDecision ? '' : 'none'
1366
+ renderGgmlDecisionGenerators(ggmlDecision.generators || [])
1367
+
1368
+ renderHistory('ggmlDecisionModelHistory', ggmlDecision.history?.modelLoads || [], [
1369
+ { label: 'Time', render: i => `<span class="timestamp">${formatRelativeTime(i.timestamp)}</span>` },
1370
+ { label: 'Model', render: i => formatModelName(i.repoId) },
1371
+ { label: 'Type', render: i => escapeHtml(i.decisionType || '-') },
1372
+ { label: 'Quant', render: i => escapeHtml(i.quantization || '-') },
1373
+ { label: 'Variant', render: i => escapeHtml(i.variant || '-') },
1374
+ { label: 'Duration', render: i => `${(i.durationMs / 1000).toFixed(1)}s` },
1375
+ { label: 'Status', render: i => i.success ?
1376
+ '<span class="badge badge-success">Success</span>' :
1377
+ `<span class="badge badge-error">Failed: ${escapeHtml(i.error || 'Unknown')}</span>`
1378
+ },
1379
+ ])
1380
+
1381
+ renderHistory('ggmlDecisionHistory', ggmlDecision.history?.decisions || [], [
1382
+ { label: 'Time', render: i => `<span class="timestamp">${formatRelativeTime(i.timestamp)}</span>` },
1383
+ { label: 'Model', render: i => formatModelName(i.repoId) },
1384
+ { label: 'Questions', render: i => i.questionCount ?? '-' },
1385
+ { label: 'Images', render: i => i.imageCount || '-' },
1386
+ { label: 'Tokens', render: i => i.inputTokens ?? '-' },
1387
+ { label: 'Duration', render: i => `${i.durationMs} ms` },
1388
+ { label: 'Status', render: i => i.success ?
1389
+ '<span class="badge badge-success">Success</span>' :
1390
+ `<span class="badge badge-error">Failed: ${escapeHtml(i.error || 'Unknown')}</span>`
1391
+ },
1392
+ ])
1393
+
1291
1394
  renderFunctionsStatus(status.functions || {})
1292
1395
  }
1293
1396
 
@@ -1447,7 +1550,7 @@
1447
1550
  const response = await fetch('/buttress/status')
1448
1551
  const data = await response.json()
1449
1552
  // Direct JSON response (not tRPC wrapped)
1450
- if (data.ggmlLlm || data.ggmlStt || data.mlxLlm || data.onnxStt || data.onnxTts || data.ggmlTts) {
1553
+ if (data.ggmlLlm || data.ggmlStt || data.mlxLlm || data.onnxStt || data.onnxTts || data.ggmlTts || data.ggmlDecision) {
1451
1554
  updateUI(data)
1452
1555
  setConnectionStatus('connected')
1453
1556
  }
@@ -1,3 +0,0 @@
1
- import type { FileSystem } from '@earendil-works/pi-agent-core';
2
- /** File storage for agent sessions that classifies errors structurally across vm realms. */
3
- export declare const createSessionFs: (rootDir: string) => FileSystem;
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import e from"node:path";import t from"node:os";import n from"bytes";import r from"ms";import i from"node-machine-id";const a=(e={},t={})=>{let n=Array.isArray(e)?[...e]:{...e};return Object.entries(t||{}).forEach(([e,t])=>{t&&typeof t==`object`&&!Array.isArray(t)?n[e]=a(n[e]||{},t):n[e]=t}),n},o=e=>e&&typeof e==`object`?structuredClone(e):null,s=(e,t)=>{let n=o(e)||{},r=o(t)||{};return a(n,r)},c=(e,t)=>a(structuredClone(e.global),t||{}),l=(e,t,n,r)=>{if(e.generators.length>0){let i=e.generators.filter(e=>e?.type===n);if(i.length>0&&r){let a=i.find(e=>t.getModelIdentifier(n,e)===r);if(a)return c(e,a)}}return Object.keys(e.global).length>0?c(e,{}):null},u={udp:{port:8089,announcements:{enabled:!0,interval:5e3},requests:{enabled:!0,responseDelay:100}},http:{enabled:!0,path:`/buttress/info`,cors:!0}},d=e=>e?e===!0?{...u}:a(u,e):null,f=(e,t)=>{if(!e.generators||e.generators.length===0)return t.map(e=>({type:e}));let n=new Set;return e.generators.forEach(e=>{e.type&&n.add(e.type)}),n.size===0?t.map(e=>({type:e})):Array.from(n).map(e=>({type:e}))},p=(e,t,n)=>e===void 0?n:typeof e==`number`?e:t(e)??n,m=6e4,h=1024*1024*50,g=s=>{let c=i.machineIdSync(),l={server:{id:`buttress-${c}`,name:`Buttress Server (${c.slice(-8)})`,port:2080,temp_file_dir:e.join(t.tmpdir(),`.buttress`),session_timeout:m,max_body_size:h},autodiscover:!1},u=a(l,o(s)||{}),f=Array.isArray(u.generators)?u.generators:[],{server:g,generators:_,autodiscover:v,...y}=u;return{autodiscover:d(v),server:{id:g.id,name:g.name,port:g.port,log_level:g.log_level,temp_file_dir:g.temp_file_dir,max_body_size:p(g.max_body_size,n.parse,h),session_timeout:p(g.session_timeout,e=>r(e),m)},global:y,generators:f}},_=/^[A-Za-z0-9_-]{1,64}$/,v=n=>n===`~`||n.startsWith(`~/`)?e.join(t.homedir(),n.slice(1)):n;var y=class extends Error{constructor(e){super(`[Agents] ${e}`),this.name=`AgentsConfigError`}};const b=(e,t,n)=>{if(t==null)return n;if(t===0||t===!1)return null;if(typeof t==`number`){if(Number.isFinite(t)&&t>0)return t}else if(typeof t==`string`){let e;try{e=r(t)}catch{e=void 0}if(typeof e==`number`&&e>0)return e}throw new y(`agents_options.${e} must be a duration string (e.g. "30d") or a positive number of milliseconds; use 0 to disable it`)},x=(e,t)=>{if(t==null)return{};if(typeof t!=`object`||Array.isArray(t))throw new y(`agent "${e}": mcp_servers must be a table`);let n={};for(let[r,i]of Object.entries(t)){if(!_.test(r))throw new y(`agent "${e}": MCP server name "${r}" must match ${_}`);let t=!!(typeof i?.url==`string`&&i.url),a=!!(typeof i?.command==`string`&&i.command);if(t===a)throw new y(`agent "${e}": MCP server "${r}" needs exactly one of url / command`);n[r]={url:t?i.url:void 0,headers:i?.headers&&typeof i.headers==`object`?i.headers:void 0,command:a?i.command:void 0,args:Array.isArray(i?.args)?i.args.map(String):void 0,env:i?.env&&typeof i.env==`object`?i.env:void 0,optional:i?.optional===!0}}return n},S=(e,t,n)=>{let r=e?.name;if(typeof r!=`string`||!_.test(r))throw new y(`[[agents]] #${t+1}: name is required and must match ${_}`);let i=e?.model;if(typeof i!=`string`||!i.includes(`/`))throw new y(`agent "${r}": model must be "provider/model-id" (e.g. "buttress/ggml-org/gpt-oss-20b-GGUF")`);let a=i.indexOf(`/`),o=i.slice(0,a),s=i.slice(a+1);if(!o||!s)throw new y(`agent "${r}": invalid model reference "${i}"`);if(o===`buttress`&&!n.generators.filter(e=>e.type===`ggml-llm`||e.type===`mlx-llm`).some(e=>(e.model?.repo_id||e.model?.repository)===s))throw new y(`agent "${r}": model "${s}" does not match any configured [[generators]] ggml-llm/mlx-llm repo_id`);if(e?.system_prompt!=null&&e?.system_prompt_file!=null)throw new y(`agent "${r}": set system_prompt or system_prompt_file, not both`);let c=[];if(e?.tools!=null){if(!Array.isArray(e.tools))throw new y(`agent "${r}": tools must be an array of function names`);for(let t of e.tools){if(typeof t!=`string`||!_.test(t))throw new y(`agent "${r}": tool name "${t}" must match ${_} (local function names only)`);if(c.includes(t))throw new y(`agent "${r}": duplicate tool "${t}"`);c.push(t)}}if(e?.local_devices!==void 0&&typeof e.local_devices!=`boolean`)throw new y(`agent "${r}": local_devices must be a boolean`);let l=Number(e?.max_turns??30);if(!Number.isInteger(l)||l<1)throw new y(`agent "${r}": max_turns must be a positive integer`);let u=null;if(e?.max_tokens_per_run!=null&&(u=Number(e.max_tokens_per_run),!Number.isInteger(u)||u<1))throw new y(`agent "${r}": max_tokens_per_run must be a positive integer`);let{name:d,model:f,system_prompt:p,system_prompt_file:m,tools:h,local_devices:g=!1,mcp_servers:v,max_turns:b,max_tokens_per_run:S,thinking:C,...w}=e;return{name:r,provider:o,modelId:s,modelRef:i,systemPrompt:typeof p==`string`?p:null,systemPromptFile:typeof m==`string`?m:null,tools:c,localDevices:g,mcpServers:x(r,v),maxTurns:l,maxTokensPerRun:u,thinking:typeof C==`string`?C:void 0,generation:w}},C=(t,{configDir:n=process.cwd()}={})=>{let r=t.global||{},i=r.agents;if(i==null)return null;if(!Array.isArray(i))throw new y(`agents must be an array of tables ([[agents]])`);if(i.length===0)return null;let a=i.map((e,n)=>S(e,n,t)),o=new Set;for(let e of a){if(o.has(e.name))throw new y(`duplicate agent name "${e.name}"`);o.add(e.name)}let s=r.agents_options||{},c=typeof s.sessions_dir==`string`&&s.sessions_dir?s.sessions_dir:`./.buttress-agent/sessions`,l=v(c),u=e.isAbsolute(l)?l:e.resolve(n,l);for(let t of a)if(t.systemPromptFile){let r=v(t.systemPromptFile);t.systemPromptFile=e.isAbsolute(r)?r:e.resolve(n,r)}let d=Number(s.max_depth??2);if(!Number.isInteger(d)||d<1)throw new y(`agents_options.max_depth must be a positive integer`);let f=s.session_max_count,p=500;if(f===0||f===!1)p=null;else if(f!=null&&(p=Number(f),!Number.isInteger(p)||p<1))throw new y(`agents_options.session_max_count must be a positive integer`);return{agents:a,configDir:n,sessionsDir:u,runtimeTokenFile:e.join(e.dirname(u),`runtime-token`),sessionMaxAgeMs:b(`session_max_age`,s.session_max_age,2592e6),sessionMaxCount:p,maxDepth:d,allowUnauthenticated:s.allow_unauthenticated===!0}};export{g as a,o as i,C as n,l as o,s as r,f as s,y as t};