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.
|
package/dist/claude-llm.js
CHANGED
|
@@ -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
|
|
770
|
-
//
|
|
771
|
-
|
|
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 = `✨
|
|
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 = `✨
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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;
|