osborn 0.9.241 → 0.9.243

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.
@@ -125,10 +125,6 @@ export declare const VERIFIER_CHAIN: Record<string, {
125
125
  parallelAtStart?: string[];
126
126
  afterReminder?: string;
127
127
  }>;
128
- /**
129
- * Claude LLM - Wraps Claude Agent SDK for LiveKit
130
- * Research mode: reads anything, writes only to session workspace
131
- */
132
128
  export declare class ClaudeLLM extends llm.LLM {
133
129
  #private;
134
130
  constructor(opts?: ClaudeLLMOptions);
@@ -17,7 +17,7 @@ import { getEmbedder } from './embedder.js';
17
17
  import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
18
18
  import { join, dirname, resolve, basename } from 'node:path';
19
19
  import { fileURLToPath } from 'node:url';
20
- import { homedir } from 'node:os';
20
+ import { homedir, loadavg, totalmem, freemem, cpus } from 'node:os';
21
21
  // Directory of this module — used to locate co-located prompt files (e.g., turn-shape reminder).
22
22
  const __claudeLlmDir = dirname(fileURLToPath(import.meta.url));
23
23
  const TURN_SHAPE_REMINDER_PATH = join(__claudeLlmDir, 'prompts', 'turn-shape-reminder.md');
@@ -994,6 +994,30 @@ class MessageChannel {
994
994
  * Claude LLM - Wraps Claude Agent SDK for LiveKit
995
995
  * Research mode: reads anything, writes only to session workspace
996
996
  */
997
+ /**
998
+ * Lightweight machine-load nudge for the MAIN agent's UserPromptSubmit context.
999
+ * Pure in-memory/synchronous reads (os load average + memory + live sub-agent
1000
+ * dispatch count) — NO embedder, DB, or network — so it stays reliable even when
1001
+ * the box is starving, which is exactly when it matters. ADVISORY ONLY: it never
1002
+ * refuses, caps, or blocks a dispatch — the agent reads it and decides. Returns
1003
+ * '' on any error so it can never break the turn.
1004
+ */
1005
+ function buildMachineNudge() {
1006
+ try {
1007
+ const ncpu = Math.max(1, cpus().length);
1008
+ const cpuPct = Math.round((loadavg()[0] / ncpu) * 100); // 1-min load ÷ cores
1009
+ const memPct = Math.round((1 - freemem() / totalmem()) * 100);
1010
+ 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)`;
1016
+ }
1017
+ catch {
1018
+ return '';
1019
+ }
1020
+ }
997
1021
  export class ClaudeLLM extends llm.LLM {
998
1022
  #opts;
999
1023
  #sessionId = null;
@@ -2028,8 +2052,12 @@ class ClaudeLLMStream extends llm.LLMStream {
2028
2052
  buildRecallInjection(sid, this.#opts.workingDirectory, String(input?.prompt || '')),
2029
2053
  new Promise((res) => setTimeout(() => res(''), 6000)),
2030
2054
  ]);
2031
- const additionalContext = recalled ? `${reminder}\n\n${recalled}` : reminder;
2032
- console.log(`📌 UserPromptSubmit: injected turn-shape reminder (${reminder.length} chars)${recalled ? ` + recall (${recalled.length} chars)` : ''} for prompt="${promptPreview}..." [tool budget reset to 0/${TOOL_CALL_BUDGET}]`);
2055
+ // Lightweight machine-load nudge (main agent only). Cheap synchronous
2056
+ // snapshot, prepended so it's present even when recall degrades under load.
2057
+ // Advisory only — never caps/refuses a dispatch.
2058
+ const machineNudge = fromSubagent ? '' : buildMachineNudge();
2059
+ const additionalContext = [machineNudge, reminder, recalled].filter(Boolean).join('\n\n');
2060
+ 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}]`);
2033
2061
  return {
2034
2062
  hookSpecificOutput: {
2035
2063
  hookEventName: 'UserPromptSubmit',
package/dist/index.js CHANGED
@@ -2300,6 +2300,16 @@ async function main() {
2300
2300
  bgEnv[k] = v;
2301
2301
  }
2302
2302
  bgEnv['CLAUDE_CODE_DISABLE_AUTO_MEMORY'] = '1';
2303
+ // Claude Code refuses bypassPermissions / --dangerously-skip-permissions when the
2304
+ // process runs as root — and every Fly machine runs Osborn as root, so without this
2305
+ // EVERY headless background spawn dies on launch. IS_SANDBOX=1 is Claude Code's
2306
+ // sanctioned "this is an isolated container, root is acceptable" signal — accurate
2307
+ // for a per-user Fly VM. Set on the CHILD's env copy ONLY (bgEnv); the main voice
2308
+ // agent's process.env is never touched. Panel-spawned instances are user-initiated
2309
+ // and intentionally run on the machine's Claude subscription (Tier 1), same as the
2310
+ // foreground session — the "never use the subscription" rule is reserved for the
2311
+ // future autonomous cron/event background primitive, not these.
2312
+ bgEnv['IS_SANDBOX'] = '1';
2303
2313
  const bgSdkOptions = {
2304
2314
  cwd: slotWorkingDir,
2305
2315
  resume: sessionId,
@@ -5852,6 +5862,17 @@ async function main() {
5852
5862
  error: `Session ${resolvedSessionId.substring(0, 8)} is already running as a background slot`,
5853
5863
  });
5854
5864
  }
5865
+ else if (resolvedSessionId === (currentLLM?.sessionId || currentResumeSessionId)) {
5866
+ // Never background the session that's LIVE in the foreground. Pre-IS_SANDBOX this
5867
+ // failed harmlessly on the root check; post-fix it would SUCCEED, putting two
5868
+ // Claude processes appending to the same session JSONL concurrently → transcript
5869
+ // corruption. Refuse it explicitly.
5870
+ await sendToFrontend({
5871
+ type: 'background_session_error',
5872
+ slotId: resolvedSessionId,
5873
+ error: `Session ${resolvedSessionId.substring(0, 8)} is the active foreground session — pick a different session to background`,
5874
+ });
5875
+ }
5855
5876
  else {
5856
5877
  spawnBackgroundSession(resolvedSessionId, bgDir).catch((err) => {
5857
5878
  console.error('❌ spawnBackgroundSession failed:', err instanceof Error ? err.message : err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.241",
3
+ "version": "0.9.243",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {