osborn 0.9.126 → 0.9.128

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.
@@ -29,6 +29,17 @@ response and check off; `scripts/review-run.sh <clip>` in the harness does the
29
29
  frame extraction. Media is reviewed by you AND delivered to the user, every
30
30
  run, no exceptions.
31
31
 
32
+ **DELIVER MEDIA INLINE via the `send-media` skill — MANDATORY.** Artifacts on the
33
+ machine's disk are INVISIBLE to the user (mobile especially). Upload each clip and
34
+ key frame, then render the public URL in your reply so it shows in chat:
35
+ ```bash
36
+ URL=$(curl -s -X POST "https://www.voice-native.com/api/upload" -F "file=@/path/to/clip.mp4" \
37
+ | python3 -c "import sys,json;print(json.load(sys.stdin)['url'])")
38
+ ```
39
+ Render as `[Image: step-3.png](URL)` (inline, tap-to-open) or `[File: run.mp4](URL)`.
40
+ The live URL is the during-run window; these uploads are the after-the-fact proof
41
+ the user can actually see. Never upload artifacts containing secrets. See `send-media`.
42
+
32
43
  ## UPDATE CHECK (AUTO-UPDATE — first-party tooling, no prompt)
33
44
  This packaged copy ships with every machine and may lag the served version.
34
45
  This is first-party tooling you own — AUTO-UPDATE silently (industry standard
@@ -0,0 +1,41 @@
1
+ # Slack (read-only) — search and read Slack from the shell
2
+
3
+ > 📅 **Last updated: 2026-08-09.** Recreated from scratch after the Mac data-loss events wiped the
4
+ > original global install. READ-ONLY by design: no posting, no reactions, no edits.
5
+
6
+ Use this skill when asked to find, read, or summarize Slack messages, threads, or channels.
7
+
8
+ ## Auth
9
+ Requires a Slack token in `SLACK_TOKEN` (bot `xoxb-…` or user `xoxp-…`).
10
+ Load it from `/workspace/.claude/slack.env` if present:
11
+ ```bash
12
+ [ -f /workspace/.claude/slack.env ] && export $(grep -v '^#' /workspace/.claude/slack.env | xargs)
13
+ ```
14
+ If `SLACK_TOKEN` is unset, STOP and tell the user: "No Slack token on this machine — add one to
15
+ /workspace/.claude/slack.env (SLACK_TOKEN=xoxb-…). Create at api.slack.com/apps with read scopes:
16
+ channels:history channels:read groups:history groups:read search:read users:read."
17
+
18
+ ## Read-only rules (hard)
19
+ - ONLY call GET/read Slack Web API methods listed below. NEVER `chat.postMessage`, `reactions.add`,
20
+ or any method that writes. If asked to send/post, refuse and suggest the user do it themselves.
21
+
22
+ ## Core calls (curl; all return JSON)
23
+ ```bash
24
+ H="Authorization: Bearer $SLACK_TOKEN"
25
+ # list channels (public + private the token can see)
26
+ curl -s -H "$H" "https://slack.com/api/conversations.list?types=public_channel,private_channel&limit=200"
27
+ # channel history (channel id from list)
28
+ curl -s -H "$H" "https://slack.com/api/conversations.history?channel=C0XXXX&limit=50"
29
+ # a thread
30
+ curl -s -H "$H" "https://slack.com/api/conversations.replies?channel=C0XXXX&ts=1234567890.123456"
31
+ # search (user tokens only; bot tokens can't search)
32
+ curl -s -H "$H" --get --data-urlencode "query=from:@nick linkedin" "https://slack.com/api/search.messages"
33
+ # user lookup
34
+ curl -s -H "$H" "https://slack.com/api/users.list?limit=200"
35
+ ```
36
+
37
+ ## Output discipline
38
+ - Summarize; quote only the messages that matter, with author + date.
39
+ - `ok:false` → report the exact `error` field (common: `missing_scope`, `not_in_channel` — the bot
40
+ must be invited to a channel to read it; `search.messages` needs a USER token).
41
+ - Never echo the token into output or files.
@@ -766,9 +766,11 @@ export class ClaudeLLM extends llm.LLM {
766
766
  pushMessage(userText, sdkOptions, callbacks) {
767
767
  // Auto-compact threshold — fires PreCompact when context fills to this %.
768
768
  // 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';
769
+ // per turn, but less headroom for the next reply). Raised 85→92 because
770
+ // users reported compaction firing too often; 92% pushes it as late as is
771
+ // safe before Claude starts hard-capping replies near the 1M limit. Pairs
772
+ // with autoCompactWindow=1_000_000 (settings.json, read via settingSources).
773
+ process.env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = '92';
772
774
  const userMessage = {
773
775
  type: 'user',
774
776
  message: { role: 'user', content: [{ type: 'text', text: userText }] },
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.126",
3
+ "version": "0.9.128",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {