osborn 0.9.243 → 0.9.245

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.
Files changed (2) hide show
  1. package/dist/claude-llm.js +55 -8
  2. package/package.json +1 -1
@@ -1002,17 +1002,64 @@ class MessageChannel {
1002
1002
  * refuses, caps, or blocks a dispatch — the agent reads it and decides. Returns
1003
1003
  * '' on any error so it can never break the turn.
1004
1004
  */
1005
- function buildMachineNudge() {
1005
+ /**
1006
+ * Instant CPU busy% from /proc/stat (Linux). One aggregate 'cpu' line: idle+iowait
1007
+ * vs total. Two reads a short interval apart give real-time utilization across all
1008
+ * cores — unlike the 1-min loadavg, which lags a fresh burst of heavy commands by
1009
+ * ~30-60s. Returns null off-Linux / on any parse error (caller falls back to loadavg).
1010
+ */
1011
+ function readProcStatCpu() {
1012
+ try {
1013
+ const line = readFileSync('/proc/stat', 'utf8').split('\n', 1)[0];
1014
+ const n = line.trim().split(/\s+/).slice(1).map(Number); // drop the "cpu" label
1015
+ if (n.length < 5 || n.some(Number.isNaN))
1016
+ return null;
1017
+ const idle = n[3] + (n[4] || 0); // idle + iowait
1018
+ const total = n.reduce((a, b) => a + b, 0);
1019
+ return { idle, total };
1020
+ }
1021
+ catch {
1022
+ return null;
1023
+ }
1024
+ }
1025
+ async function buildMachineNudge() {
1006
1026
  try {
1007
1027
  const ncpu = Math.max(1, cpus().length);
1008
- const cpuPct = Math.round((loadavg()[0] / ncpu) * 100); // 1-min load ÷ cores
1028
+ // #3: prefer an INSTANT CPU read (proc/stat delta over ~200ms) over the lagging
1029
+ // 1-min loadavg, so a fresh storm of tsc/build/test shows up on THIS turn, not a
1030
+ // minute later. Falls back to loadavg÷cores off-Linux or if the delta is unusable.
1031
+ let cpuPct;
1032
+ const a = readProcStatCpu();
1033
+ if (a) {
1034
+ await new Promise((r) => setTimeout(r, 200));
1035
+ const b = readProcStatCpu();
1036
+ const dTotal = b ? b.total - a.total : 0;
1037
+ cpuPct = b && dTotal > 0
1038
+ ? Math.round(((dTotal - (b.idle - a.idle)) / dTotal) * 100)
1039
+ : Math.round((loadavg()[0] / ncpu) * 100);
1040
+ }
1041
+ else {
1042
+ cpuPct = Math.round((loadavg()[0] / ncpu) * 100);
1043
+ }
1009
1044
  const memPct = Math.round((1 - freemem() / totalmem()) * 100);
1010
1045
  const subagents = statusManager.getActiveDispatchIds().length;
1011
- const hot = cpuPct >= 80 || subagents >= 4;
1012
- const head = `[MACHINE${hot ? ' ⚠️' : ''}] CPU ~${cpuPct}% · MEM ${memPct}% · sub-agents ${subagents}`;
1013
- if (!hot)
1014
- return head;
1015
- return `${head} — load is high; prefer finishing in-flight work over spawning new sub-agents this turn, and stop any that look stuck. (advisory only — not enforced)`;
1046
+ // Recommended concurrent heavy sub-agents, adapted to live headroom. A concrete
1047
+ // DEFAULT the agent follows unless a task warrants otherwise — never enforced.
1048
+ const recMax = (cpuPct >= 80 || memPct >= 90) ? Math.max(1, ncpu - 1) // hot: pull back below cores
1049
+ : (cpuPct >= 60) ? ncpu // busy: ~cores
1050
+ : ncpu + 1; // calm: a little headroom
1051
+ const over = subagents > recMax;
1052
+ const warn = over || cpuPct >= 80;
1053
+ // #4: name the actual CPU culprits — the spikes come from heavy COMMANDS sub-agents
1054
+ // run, not their count. One tsc/build/test pegs a core for 30-120s; ten file-reading
1055
+ // agents barely register. Tell the agent WHAT to throttle.
1056
+ const culprits = 'CPU cost is heavy commands (tsc/typecheck, build, test, npm/pnpm install, eslint) — not agent count. Prefer scoped/incremental runs, cache, and never fire several at once.';
1057
+ const head = `[MACHINE${warn ? ' ⚠️' : ''}] CPU ${cpuPct}% · MEM ${memPct}% · sub-agents ${subagents}/${recMax} recommended`;
1058
+ if (over)
1059
+ return `${head} — over the recommended concurrency; wind down to ≤${recMax} (finish or stop in-flight sub-agents) unless a task must continue. ${culprits} (recommendation — the agent decides, not enforced)`;
1060
+ if (cpuPct >= 80)
1061
+ return `${head} — load high; hold new sub-agents beyond ${recMax} unless needed. ${culprits} (recommendation — the agent decides, not enforced)`;
1062
+ return head;
1016
1063
  }
1017
1064
  catch {
1018
1065
  return '';
@@ -2055,7 +2102,7 @@ class ClaudeLLMStream extends llm.LLMStream {
2055
2102
  // Lightweight machine-load nudge (main agent only). Cheap synchronous
2056
2103
  // snapshot, prepended so it's present even when recall degrades under load.
2057
2104
  // Advisory only — never caps/refuses a dispatch.
2058
- const machineNudge = fromSubagent ? '' : buildMachineNudge();
2105
+ const machineNudge = fromSubagent ? '' : await buildMachineNudge();
2059
2106
  const additionalContext = [machineNudge, reminder, recalled].filter(Boolean).join('\n\n');
2060
2107
  console.log(`📌 UserPromptSubmit: injected turn-shape reminder (${reminder.length} chars)${recalled ? ` + recall (${recalled.length} chars)` : ''}${machineNudge ? ` + machine(${machineNudge.includes('⚠️') ? 'HOT' : 'ok'})` : ''} for prompt="${promptPreview}..." [tool budget reset to 0/${TOOL_CALL_BUDGET}]`);
2061
2108
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.243",
3
+ "version": "0.9.245",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {