osborn 0.9.127 → 0.9.129

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.
@@ -483,7 +483,12 @@ export class ClaudeLLM extends llm.LLM {
483
483
  return 'claude.agent-sdk';
484
484
  }
485
485
  get model() {
486
- return this.#opts.model || 'claude-opus-4-8'; // Opus 4.8 orchestrator with named sub-agents
486
+ // The [1m] suffix opts into Opus 4.8's 1M context window (Claude Code's
487
+ // context-1m-2025-08-07 beta). WITHOUT it the SDK runs opus at its 200k
488
+ // base, so auto-compaction fired at ~153k every ~10 min (confirmed in the
489
+ // live agent log: compact_boundary pre_tokens≈153k). With [1m] the window
490
+ // is 1M and compaction happens far later. Overridable via opts.model.
491
+ return this.#opts.model || 'claude-opus-4-8[1m]';
487
492
  }
488
493
  get sessionId() {
489
494
  return this.#sessionId;
@@ -766,9 +771,14 @@ export class ClaudeLLM extends llm.LLM {
766
771
  pushMessage(userText, sdkOptions, callbacks) {
767
772
  // Auto-compact threshold — fires PreCompact when context fills to this %.
768
773
  // Higher = uses more of the window before compacting (more context retained
769
- // per turn, but less headroom for the next reply). 85% is the sweet spot
770
- // before Claude starts hard-capping replies near the limit.
771
- process.env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = '85';
774
+ // per turn, but less headroom for the next reply). 92% pushes it as late as
775
+ // is safe before Claude starts hard-capping replies near the 1M limit.
776
+ process.env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = '92';
777
+ // Compaction WINDOW in thousands of tokens (min(actual_context, this)). Now
778
+ // that the model runs with [1m] (1M context), set this to 1000 (=1,000,000)
779
+ // so the compaction budget matches the real window instead of being clamped
780
+ // to opus' 200k base — the actual cause of the ~153k early compaction.
781
+ process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = '1000';
772
782
  const userMessage = {
773
783
  type: 'user',
774
784
  message: { role: 'user', content: [{ type: 'text', text: userText }] },
@@ -1002,7 +1012,7 @@ class ClaudeLLMStream extends llm.LLMStream {
1002
1012
  permissionMode: this.#opts.permissionMode,
1003
1013
  allowedTools,
1004
1014
  // model: this.#opts.model || 'haiku', // haiku for speed with limited tools, sonnet for full research capabilities (including tool use trace in response)
1005
- model: this.#opts.model || 'claude-opus-4-8', // Opus 4.8 orchestrator with named sub-agents (Haiku tested but ignored delegation rules)
1015
+ model: this.#opts.model || 'claude-opus-4-8[1m]', // Opus 4.8 + [1m] → 1M context (see get model() note); prevents ~153k early compaction
1006
1016
  enableFileCheckpointing: true,
1007
1017
  settingSources: ['project', 'user'],
1008
1018
  extraArgs: { 'replay-user-messages': null },
package/dist/index.js CHANGED
@@ -53,6 +53,58 @@ import { z } from 'zod';
53
53
  // - Voice LLM with tool calling (ask_agent, respond_permission)
54
54
  // - Routes tasks to Claude agents for execution
55
55
  // ============================================================
56
+ // Build an enriched tool-use event for the frontend Logs drawer so it can
57
+ // render Claude-style review cards (Read/Edited/Ran with file names, +/- line
58
+ // counts, and an expandable diff) instead of a bare tool name. Best-effort:
59
+ // any field that can't be derived is simply omitted and the card degrades to
60
+ // the tool name. `input` is the raw tool arguments already carried on the
61
+ // tool_use/tool_result events emitted by claude-llm.
62
+ function buildToolLogEvent(name, input, status, agentRole) {
63
+ const ev = { type: 'tool_use', tool: name, status, agentRole };
64
+ const inp = input || {};
65
+ const basename = (p) => String(p).split('/').pop() || String(p);
66
+ const fp = inp.file_path || inp.path || inp.notebook_path;
67
+ if (fp) {
68
+ ev.filePath = fp;
69
+ ev.fileName = basename(fp);
70
+ }
71
+ if (typeof inp.command === 'string')
72
+ ev.command = inp.command;
73
+ if (typeof inp.pattern === 'string')
74
+ ev.pattern = inp.pattern;
75
+ if (typeof inp.url === 'string')
76
+ ev.url = inp.url;
77
+ if (typeof inp.description === 'string')
78
+ ev.description = inp.description;
79
+ try {
80
+ if (name === 'Edit' && typeof inp.old_string === 'string' && typeof inp.new_string === 'string') {
81
+ ev.linesRemoved = inp.old_string ? inp.old_string.split('\n').length : 0;
82
+ ev.linesAdded = inp.new_string ? inp.new_string.split('\n').length : 0;
83
+ ev.diff = createPatch(ev.fileName || 'edit', inp.old_string, inp.new_string, '', '', { context: 2 });
84
+ }
85
+ else if (name === 'MultiEdit' && Array.isArray(inp.edits)) {
86
+ let a = 0, r = 0;
87
+ const chunks = [];
88
+ for (const e of inp.edits) {
89
+ const o = String(e?.old_string ?? ''), n = String(e?.new_string ?? '');
90
+ r += o ? o.split('\n').length : 0;
91
+ a += n ? n.split('\n').length : 0;
92
+ chunks.push(createPatch(ev.fileName || 'edit', o, n, '', '', { context: 2 }));
93
+ }
94
+ ev.editCount = inp.edits.length;
95
+ ev.linesRemoved = r;
96
+ ev.linesAdded = a;
97
+ ev.diff = chunks.join('\n');
98
+ }
99
+ else if (name === 'Write' && typeof inp.content === 'string') {
100
+ ev.linesAdded = inp.content.split('\n').length;
101
+ ev.linesRemoved = 0;
102
+ ev.diff = inp.content.split('\n').slice(0, 60).map((l) => '+' + l).join('\n');
103
+ }
104
+ }
105
+ catch { /* diff is best-effort — omit on any failure */ }
106
+ return ev;
107
+ }
56
108
  // Load skills list with name + description for frontend display
57
109
  function loadSkillsList(agentDir) {
58
110
  const skillsDir = join(agentDir, '.claude', 'skills');
@@ -2542,7 +2594,7 @@ async function main() {
2542
2594
  // Inline chat bubble — reuses the existing claude_output path that's already working.
2543
2595
  if (event.type === 'compaction_started') {
2544
2596
  const triggerLabel = event.trigger ? ` (${event.trigger})` : '';
2545
- const text = `✨ _Learning from this session — saving your preferences and decisions…_${triggerLabel}`;
2597
+ const text = `✨ _Teaching Osborn from this session — saving your preferences and decisions…_${triggerLabel}`;
2546
2598
  sendToFrontend({
2547
2599
  type: 'claude_output',
2548
2600
  text,
@@ -2555,7 +2607,7 @@ async function main() {
2555
2607
  const names = Array.isArray(event.skillNames) && event.skillNames.length > 0
2556
2608
  ? ` — ${event.skillNames.join(', ')}`
2557
2609
  : '';
2558
- const text = `✨ Done learning — ${n} skill${n === 1 ? '' : 's'} updated${names}. I'll carry this forward.`;
2610
+ const text = `✨ Osborn learned — ${n} skill${n === 1 ? '' : 's'} updated${names}. I'll carry these forward.`;
2559
2611
  sendToFrontend({
2560
2612
  type: 'claude_output',
2561
2613
  text,
@@ -2630,11 +2682,11 @@ async function main() {
2630
2682
  // Wire up events from the Claude SDK wrapper to frontend
2631
2683
  directLLM.events.on('tool_use', (data) => {
2632
2684
  console.log(`🔧 Claude: ${data.name}`);
2633
- sendToFrontend({ type: 'tool_use', tool: data.name, agentRole: 'direct' });
2685
+ sendToFrontend(buildToolLogEvent(data.name, data.input, 'running', 'direct'));
2634
2686
  });
2635
2687
  directLLM.events.on('tool_result', (data) => {
2636
2688
  console.log(`✅ Done: ${data.name}`);
2637
- sendToFrontend({ type: 'tool_use', tool: data.name, status: 'completed', agentRole: 'direct' });
2689
+ sendToFrontend(buildToolLogEvent(data.name, data.input, 'completed', 'direct'));
2638
2690
  // Detect research artifact writes (session workspace or legacy research dir)
2639
2691
  if ((data.name === 'Write' || data.name === 'Edit') && data.input?.file_path) {
2640
2692
  const fp = data.input.file_path;
@@ -3069,11 +3121,11 @@ async function main() {
3069
3121
  // Wire up Claude events to frontend
3070
3122
  realtimeClaudeHandler.events.on('tool_use', (data) => {
3071
3123
  console.log(`🔧 Claude: ${data.name}`);
3072
- sendToFrontend({ type: 'tool_use', tool: data.name, agentRole: 'realtime' });
3124
+ sendToFrontend(buildToolLogEvent(data.name, data.input, 'running', 'realtime'));
3073
3125
  });
3074
3126
  realtimeClaudeHandler.events.on('tool_result', (data) => {
3075
3127
  console.log(`✅ Done: ${data.name}`);
3076
- sendToFrontend({ type: 'tool_use', tool: data.name, status: 'completed', agentRole: 'realtime' });
3128
+ sendToFrontend(buildToolLogEvent(data.name, data.input, 'completed', 'realtime'));
3077
3129
  // Detect research artifact writes (session workspace or legacy research dir)
3078
3130
  if ((data.name === 'Write' || data.name === 'Edit') && data.input?.file_path) {
3079
3131
  const fp = data.input.file_path;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.127",
3
+ "version": "0.9.129",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {