osborn 0.9.244 → 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 +47 -9
  2. package/package.json +1 -1
@@ -1002,25 +1002,63 @@ 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
- // Recommended concurrent heavy sub-agents, adapted to live headroom. This is a
1012
- // concrete DEFAULT the agent should follow unless a task genuinely warrants
1013
- // otherwise — surfaced as an actual number/target, never 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.
1014
1048
  const recMax = (cpuPct >= 80 || memPct >= 90) ? Math.max(1, ncpu - 1) // hot: pull back below cores
1015
1049
  : (cpuPct >= 60) ? ncpu // busy: ~cores
1016
1050
  : ncpu + 1; // calm: a little headroom
1017
1051
  const over = subagents > recMax;
1018
1052
  const warn = over || cpuPct >= 80;
1019
- const head = `[MACHINE${warn ? ' ⚠️' : ''}] CPU ~${cpuPct}% · MEM ${memPct}% · sub-agents ${subagents}/${recMax} recommended`;
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`;
1020
1058
  if (over)
1021
- return `${head} — over the recommended concurrency; wind down to ≤${recMax} (finish or stop in-flight sub-agents) unless a task must continue. (recommendation — the agent decides, not enforced)`;
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)`;
1022
1060
  if (cpuPct >= 80)
1023
- return `${head} — load high; hold new sub-agents beyond ${recMax} unless needed. (recommendation — the agent decides, not enforced)`;
1061
+ return `${head} — load high; hold new sub-agents beyond ${recMax} unless needed. ${culprits} (recommendation — the agent decides, not enforced)`;
1024
1062
  return head;
1025
1063
  }
1026
1064
  catch {
@@ -2064,7 +2102,7 @@ class ClaudeLLMStream extends llm.LLMStream {
2064
2102
  // Lightweight machine-load nudge (main agent only). Cheap synchronous
2065
2103
  // snapshot, prepended so it's present even when recall degrades under load.
2066
2104
  // Advisory only — never caps/refuses a dispatch.
2067
- const machineNudge = fromSubagent ? '' : buildMachineNudge();
2105
+ const machineNudge = fromSubagent ? '' : await buildMachineNudge();
2068
2106
  const additionalContext = [machineNudge, reminder, recalled].filter(Boolean).join('\n\n');
2069
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}]`);
2070
2108
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.244",
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": {