osborn 0.9.80 → 0.9.82
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.
- package/.claude/skills/voice-e2e/SKILL.md +32 -0
- package/dist/index.js +43 -7
- package/package.json +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Voice-E2E — browser testing with ears, a mouth, hands and a brain
|
|
2
|
+
|
|
3
|
+
## SKILL IDENTITY
|
|
4
|
+
Name: voice-e2e
|
|
5
|
+
Version: 1 (packaged seed — the served copy at the URL below is source of truth)
|
|
6
|
+
Served from: https://www.voice-native.com/api/test-skill
|
|
7
|
+
Landing page: https://www.voice-native.com/test-skill
|
|
8
|
+
|
|
9
|
+
Agent-driven end-to-end testing for voice and web apps in a REAL browser:
|
|
10
|
+
speak into the page's mic (synthesized on the fly), hear and transcribe what
|
|
11
|
+
it plays back, click via a natural-language brain, improvise conversations,
|
|
12
|
+
and get video + audio replays per run. Works on any website.
|
|
13
|
+
|
|
14
|
+
## UPDATE CHECK (notify-first, run before major test/research runs)
|
|
15
|
+
This packaged copy ships with every machine and may lag the served version.
|
|
16
|
+
Check, then NOTIFY the user before updating — never silently force:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
SERVED_V=$(curl -sf --max-time 10 https://www.voice-native.com/api/test-skill | grep -m1 '^Version:' | awk '{print $2}')
|
|
20
|
+
LOCAL_V=$(grep -m1 '^Version:' ~/.claude/skills/voice-e2e/SKILL.md 2>/dev/null | awk '{print $2}')
|
|
21
|
+
[ -n "$SERVED_V" ] && [ "$SERVED_V" != "$LOCAL_V" ] && echo "UPDATE AVAILABLE: voice-e2e v${LOCAL_V:-none} -> v$SERVED_V — tell the user what's new and ask before updating (curl -sS https://www.voice-native.com/api/test-skill -o ~/.claude/skills/voice-e2e/SKILL.md), then refresh the harness bundle per the served INSTALLATION."
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If the user approves (or has pre-approved auto-updates), update the skill
|
|
25
|
+
file AND re-run the served INSTALLATION step 2 (harness bundle refresh) so
|
|
26
|
+
the accompanying files stay in lockstep with the skill.
|
|
27
|
+
|
|
28
|
+
## FIRST USE
|
|
29
|
+
Fetch the served copy and follow its INSTALLATION section — it materializes
|
|
30
|
+
the harness (~/voice-e2e-harness/), installs dependencies, and asks the user
|
|
31
|
+
for a GOOGLE_API_KEY (required, Gemini brain) plus optional OPENAI/DEEPGRAM
|
|
32
|
+
keys for the voice mouth and audible verification.
|
package/dist/index.js
CHANGED
|
@@ -70,7 +70,7 @@ function loadSkillsList(agentDir) {
|
|
|
70
70
|
// Extract description from first paragraph after heading
|
|
71
71
|
const descMatch = content.match(/^#[^\n]+\n+([^\n#]+)/m);
|
|
72
72
|
const description = descMatch ? descMatch[1].trim() : '';
|
|
73
|
-
skills.push({ name, description });
|
|
73
|
+
skills.push({ name, description, folder: skillName });
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -1237,6 +1237,8 @@ async function main() {
|
|
|
1237
1237
|
// Track state
|
|
1238
1238
|
let pendingSessionClose = null; // Tracks async session close for reconnect safety
|
|
1239
1239
|
let currentSession = null;
|
|
1240
|
+
// 0.9.81: callable handler reference — see registration site for why.
|
|
1241
|
+
let participantConnectedHandler = null;
|
|
1240
1242
|
let currentAgent = null; // For updateChatCtx() context injection
|
|
1241
1243
|
let currentLLM = null;
|
|
1242
1244
|
// Agent-side "alone in room" leave timer (see Room-presence lifecycle note up
|
|
@@ -1384,8 +1386,8 @@ async function main() {
|
|
|
1384
1386
|
if (Date.now() - last < 60_000)
|
|
1385
1387
|
return;
|
|
1386
1388
|
adoptAttempts.set(p.identity, Date.now());
|
|
1387
|
-
console.log(`📥 [ADOPT-POLL] ${p.identity} present with no session —
|
|
1388
|
-
|
|
1389
|
+
console.log(`📥 [ADOPT-POLL] ${p.identity} present with no session — invoking ParticipantConnected handler directly`);
|
|
1390
|
+
participantConnectedHandler?.(p).catch((e) => console.error('📥 [ADOPT-POLL] handler error:', e instanceof Error ? e.message : e));
|
|
1389
1391
|
}
|
|
1390
1392
|
catch (err) {
|
|
1391
1393
|
console.error('📥 [ADOPT-POLL] error:', err instanceof Error ? err.message : err);
|
|
@@ -3067,7 +3069,13 @@ async function main() {
|
|
|
3067
3069
|
console.error('❌ Reconnect attempt threw (should not happen — connectWithRetry loops):', err);
|
|
3068
3070
|
});
|
|
3069
3071
|
});
|
|
3070
|
-
|
|
3072
|
+
// 0.9.81: handler extracted to a named reference so the adopt-poll and
|
|
3073
|
+
// /connect-room adopt can CALL it directly. Synthetic `room.emit(...)` does
|
|
3074
|
+
// NOT reach listeners on rtc-node's Room (observed 2026-07-28: ADOPT-POLL
|
|
3075
|
+
// re-emitted for three successive participants, zero sessions created) —
|
|
3076
|
+
// the event dispatch is internal, so direct invocation is the only
|
|
3077
|
+
// reliable path for state-driven adoption.
|
|
3078
|
+
participantConnectedHandler = async (participant) => {
|
|
3071
3079
|
console.log(`\n👤 User joined: ${participant.identity}`);
|
|
3072
3080
|
// A user is present — cancel any pending agent-side "alone" leave.
|
|
3073
3081
|
if (aloneTimer) {
|
|
@@ -3929,7 +3937,8 @@ async function main() {
|
|
|
3929
3937
|
catch (err) {
|
|
3930
3938
|
console.error('❌ Failed to start session:', err);
|
|
3931
3939
|
}
|
|
3932
|
-
}
|
|
3940
|
+
};
|
|
3941
|
+
room.on(RoomEvent.ParticipantConnected, (p) => { participantConnectedHandler?.(p); });
|
|
3933
3942
|
room.on(RoomEvent.ParticipantDisconnected, (participant) => {
|
|
3934
3943
|
console.log(`👋 User left: ${participant.identity}`);
|
|
3935
3944
|
// Full cleanup — stop all background work to avoid accumulating API usage
|
|
@@ -4549,6 +4558,33 @@ async function main() {
|
|
|
4549
4558
|
}
|
|
4550
4559
|
}
|
|
4551
4560
|
}
|
|
4561
|
+
else if (data.type === 'skill_get') {
|
|
4562
|
+
const folder = (data.name || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
4563
|
+
const p = join(sessionBaseDir, '.claude', 'skills', folder, 'SKILL.md');
|
|
4564
|
+
if (folder && existsSync(p)) {
|
|
4565
|
+
await sendToFrontend({ type: 'skill_content', name: folder, content: readFileSync(p, 'utf-8') });
|
|
4566
|
+
}
|
|
4567
|
+
else {
|
|
4568
|
+
await sendToFrontend({ type: 'skill_content', name: folder, error: 'skill not found' });
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
else if (data.type === 'skill_remove') {
|
|
4572
|
+
const folder = (data.name || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
4573
|
+
const dir = join(sessionBaseDir, '.claude', 'skills', folder);
|
|
4574
|
+
if (!folder || !existsSync(dir)) {
|
|
4575
|
+
await sendToFrontend({ type: 'skill_remove_result', success: false, error: 'skill not found' });
|
|
4576
|
+
}
|
|
4577
|
+
else {
|
|
4578
|
+
try {
|
|
4579
|
+
rmSync(dir, { recursive: true, force: true });
|
|
4580
|
+
console.log(`🗑️ Skill removed: ${folder}`);
|
|
4581
|
+
await sendToFrontend({ type: 'skill_remove_result', success: true, skills: loadSkillsList(sessionBaseDir) });
|
|
4582
|
+
}
|
|
4583
|
+
catch (err) {
|
|
4584
|
+
await sendToFrontend({ type: 'skill_remove_result', success: false, error: String(err) });
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4552
4588
|
else if (data.type === 'join_meeting') {
|
|
4553
4589
|
const meetingUrl = data.url;
|
|
4554
4590
|
if (meetingUrl) {
|
|
@@ -4810,8 +4846,8 @@ async function main() {
|
|
|
4810
4846
|
// Re-emit the event for every pre-existing participant so the session is
|
|
4811
4847
|
// created exactly as if they had joined after us.
|
|
4812
4848
|
for (const p of room.remoteParticipants.values()) {
|
|
4813
|
-
console.log(`👥 Participant already in room at join: ${p.identity} — adopting (
|
|
4814
|
-
|
|
4849
|
+
console.log(`👥 Participant already in room at join: ${p.identity} — adopting (invoking handler directly)`);
|
|
4850
|
+
participantConnectedHandler?.(p).catch((e) => console.error('👥 adopt handler error:', e instanceof Error ? e.message : e));
|
|
4815
4851
|
}
|
|
4816
4852
|
};
|
|
4817
4853
|
// 0.9.73: explicit frontend leave should also start the idle-exit countdown —
|