osborn 0.8.14 → 0.8.16

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.
@@ -0,0 +1,50 @@
1
+ const { chromium } = require('playwright');
2
+
3
+ (async () => {
4
+ const browser = await chromium.launch({ headless: false });
5
+ const page = await browser.newPage();
6
+
7
+ await page.goto('https://caresource.wd1.myworkdayjobs.com/CareSource/job/Remote/AI-Developer_R10487/apply', { waitUntil: 'networkidle' });
8
+ await page.waitForTimeout(2000);
9
+
10
+ // Click Apply Manually
11
+ console.log('Clicking Apply Manually...');
12
+ await page.click('text=Apply Manually');
13
+ await page.waitForTimeout(3000);
14
+
15
+ // Fill in email
16
+ console.log('Filling email field...');
17
+ const emailInput = await page.$('input[type="email"]');
18
+ if (emailInput) {
19
+ await emailInput.fill('osbornojure@gmail.com');
20
+ console.log('Email filled: osbornojure@gmail.com');
21
+ }
22
+
23
+ // Fill in password
24
+ console.log('Filling password fields...');
25
+ const passwordInputs = await page.$$('input[type="password"]');
26
+ if (passwordInputs.length >= 2) {
27
+ await passwordInputs[0].fill('workday2026!');
28
+ await passwordInputs[1].fill('workday2026!');
29
+ console.log('Passwords filled');
30
+ }
31
+
32
+ await page.waitForTimeout(1000);
33
+
34
+ // Click Create Account
35
+ const createBtn = await page.$('text=Create Account');
36
+ if (createBtn) {
37
+ console.log('Clicking Create Account...');
38
+ await createBtn.click();
39
+ await page.waitForTimeout(3000);
40
+ }
41
+
42
+ // Take screenshot
43
+ await page.screenshot({ path: '/tmp/caresource-step1.png' });
44
+ console.log('Screenshot saved to /tmp/caresource-step1.png');
45
+
46
+ // Get current page text
47
+ const text = await page.evaluate(() => document.body.innerText);
48
+ console.log('\n--- PAGE CONTENT ---');
49
+ console.log(text);
50
+ })();
@@ -0,0 +1,34 @@
1
+ import { chromium } from 'playwright';
2
+
3
+ (async () => {
4
+ const browser = await chromium.launch({ headless: false });
5
+ const page = await browser.newPage();
6
+
7
+ console.log('🌐 Navigating to CareSource...');
8
+ await page.goto('https://caresource.wd1.myworkdayjobs.com/CareSource/job/Remote/AI-Developer_R10487/apply', { waitUntil: 'networkidle' });
9
+ await page.waitForTimeout(2000);
10
+
11
+ console.log('šŸ“‹ Clicking Apply Manually...');
12
+ await page.click('text=Apply Manually');
13
+ await page.waitForTimeout(3000);
14
+
15
+ console.log('šŸ“§ Filling email...');
16
+ const emailInput = await page.$('input[type="email"]');
17
+ if (emailInput) {
18
+ await emailInput.fill('osbornojure@gmail.com');
19
+ }
20
+
21
+ console.log('šŸ” Filling passwords...');
22
+ const passwordInputs = await page.$$('input[type="password"]');
23
+ if (passwordInputs.length >= 2) {
24
+ await passwordInputs[0].fill('workday2026!');
25
+ await passwordInputs[1].fill('workday2026!');
26
+ }
27
+
28
+ await page.waitForTimeout(1000);
29
+ await page.screenshot({ path: '/tmp/caresource-step1.png' });
30
+
31
+ console.log('āœ… Account form filled. Screenshot saved.');
32
+ console.log('\nšŸ“ Browser is now open. You can review before submitting.');
33
+ console.log('Keep this window open and ready to proceed.\n');
34
+ })();
package/dist/index.js CHANGED
@@ -818,7 +818,18 @@ async function main() {
818
818
  proactivePromptHistory = [];
819
819
  }
820
820
  // Helper to send data to frontend (with size limit handling)
821
- const MAX_MESSAGE_SIZE = 60000;
821
+ //
822
+ // WebRTC SCTP data channel max message size is ~256KB. Sending larger
823
+ // payloads corrupts the publisher transport, killing ALL subsequent sends.
824
+ // We enforce a soft limit (truncate text/content fields) and a hard limit
825
+ // (drop the message entirely with a warning) to prevent this.
826
+ // āš ļø These limits protect the LiveKit SCTP publisher peer connection.
827
+ // During session resume, 12 artifact requests arrive simultaneously and the agent
828
+ // sends responses back-to-back. If the cumulative payload exceeds the SCTP buffer
829
+ // (~50-100 KB in rapid fire), the publisher PC enters a zombie state and NEVER
830
+ // recovers — the user hears nothing for the rest of the connection. Keep these low.
831
+ const MAX_MESSAGE_SIZE = 30000; // 30KB soft limit — truncate text/content fields
832
+ const HARD_MAX_MESSAGE_SIZE = 50000; // 50KB hard limit — drop if still too large after truncation
822
833
  async function sendToFrontend(data) {
823
834
  if (!localParticipant) {
824
835
  console.log('āš ļø sendToFrontend: no localParticipant!');
@@ -827,18 +838,36 @@ async function main() {
827
838
  try {
828
839
  const encoder = new TextEncoder();
829
840
  let jsonData = JSON.stringify(data);
830
- // If message is too large, truncate the text content
841
+ // If message is too large, truncate the text or content field
831
842
  if (jsonData.length > MAX_MESSAGE_SIZE) {
832
843
  const truncatedData = { ...data };
844
+ // Try truncating .text first (assistant_response, claude_output, etc.)
833
845
  if (truncatedData.text && typeof truncatedData.text === 'string') {
834
846
  const overhead = JSON.stringify({ ...truncatedData, text: '' }).length;
835
847
  const maxTextLength = MAX_MESSAGE_SIZE - overhead - 100;
836
848
  truncatedData.text = truncatedData.text.substring(0, maxTextLength) + '\n\n[Message truncated due to size limit]';
837
849
  jsonData = JSON.stringify(truncatedData);
838
- console.log(`āš ļø Message truncated from ${data.text?.length} to ${truncatedData.text.length} chars`);
850
+ console.log(`āš ļø Message truncated .text from ${data.text?.length} to ${truncatedData.text.length} chars`);
851
+ }
852
+ // Also try truncating .content (research_artifact_content, plan_file_content)
853
+ if (jsonData.length > MAX_MESSAGE_SIZE && truncatedData.content && typeof truncatedData.content === 'string') {
854
+ const overhead = JSON.stringify({ ...truncatedData, content: '' }).length;
855
+ const maxContentLength = MAX_MESSAGE_SIZE - overhead - 100;
856
+ truncatedData.content = truncatedData.content.substring(0, maxContentLength) + '\n\n[Content truncated due to size limit]';
857
+ truncatedData.truncated = true;
858
+ truncatedData.originalSize = Buffer.byteLength(data.content, 'utf-8');
859
+ jsonData = JSON.stringify(truncatedData);
860
+ console.log(`āš ļø Message truncated .content from ${data.content?.length} to ${truncatedData.content.length} chars`);
839
861
  }
840
862
  }
863
+ // Hard cap — if still too large after truncation, drop entirely.
864
+ // This prevents a 480KB base64 image or similar from killing the
865
+ // WebRTC publisher transport (which is unrecoverable without reconnect).
841
866
  const payload = encoder.encode(jsonData);
867
+ if (payload.length > HARD_MAX_MESSAGE_SIZE) {
868
+ console.error(`āŒ sendToFrontend: dropping message (${(payload.length / 1024).toFixed(0)}KB > ${(HARD_MAX_MESSAGE_SIZE / 1024).toFixed(0)}KB hard limit) — type: ${data.type}`);
869
+ return;
870
+ }
842
871
  await localParticipant.publishData(payload, {
843
872
  reliable: true,
844
873
  topic: 'osborn-updates',
@@ -2677,13 +2706,42 @@ async function main() {
2677
2706
  const fileName = filePath.split('/').pop() || '';
2678
2707
  const ext = fileName.split('.').pop()?.toLowerCase() || '';
2679
2708
  const isImage = ['png', 'jpg', 'jpeg', 'gif', 'webp'].includes(ext);
2709
+ // WebRTC SCTP data channel max message size is ~256KB. Sending
2710
+ // larger payloads corrupts the publisher transport, killing ALL
2711
+ // subsequent sends (publishData, streamBytes, publishTranscription)
2712
+ // with "could not establish publisher connection: timeout". This
2713
+ // was the root cause of the career-ops session bug: a 480KB
2714
+ // evaluation report blew through the limit on resume.
2715
+ // āš ļø MUST be low — not just per-message but cumulative back-to-back pressure.
2716
+ // 12 artifact requests arrive simultaneously during session resume. Even if
2717
+ // each is individually "safe", flooding them kills the publisher PC. At 200KB
2718
+ // the search-index.txt (136KB) passed through and poisoned the connection.
2719
+ // 30KB catches search-index.txt (136KB), resume.pdf (233KB), and search-index-
2720
+ // meta.json (5.7KB passes). resume.html (14KB) also passes — acceptable.
2721
+ const MAX_DATA_CHANNEL_BYTES = 30_000; // 30KB max per artifact
2680
2722
  if (isImage) {
2681
- const base64 = fs.readFileSync(filePath, 'base64');
2682
- await sendToFrontend({ type: 'research_artifact_content', filePath, content: base64, fileName, isImage: true, mimeType: `image/${ext}` });
2723
+ const stats = fs.statSync(filePath);
2724
+ const base64Size = Math.ceil(stats.size * 4 / 3); // base64 inflates ~33%
2725
+ if (base64Size > MAX_DATA_CHANNEL_BYTES) {
2726
+ console.log(`āš ļø Artifact too large for data channel: ${fileName} (${(base64Size / 1024).toFixed(0)}KB base64) — sending truncation notice`);
2727
+ await sendToFrontend({ type: 'research_artifact_content', filePath, content: '', fileName, isImage: false, truncated: true, originalSize: stats.size });
2728
+ }
2729
+ else {
2730
+ const base64 = fs.readFileSync(filePath, 'base64');
2731
+ await sendToFrontend({ type: 'research_artifact_content', filePath, content: base64, fileName, isImage: true, mimeType: `image/${ext}` });
2732
+ }
2683
2733
  }
2684
2734
  else {
2685
2735
  const content = fs.readFileSync(filePath, 'utf-8');
2686
- await sendToFrontend({ type: 'research_artifact_content', filePath, content, fileName, isImage: false });
2736
+ if (Buffer.byteLength(content, 'utf-8') > MAX_DATA_CHANNEL_BYTES) {
2737
+ // Send a truncated preview + metadata so the frontend knows the file exists
2738
+ const truncated = content.substring(0, 5_000); // ~5KB preview (keep well under the 30KB limit)
2739
+ console.log(`āš ļø Artifact too large for data channel: ${fileName} (${(Buffer.byteLength(content, 'utf-8') / 1024).toFixed(0)}KB) — sending truncated preview`);
2740
+ await sendToFrontend({ type: 'research_artifact_content', filePath, content: truncated, fileName, isImage: false, truncated: true, originalSize: Buffer.byteLength(content, 'utf-8') });
2741
+ }
2742
+ else {
2743
+ await sendToFrontend({ type: 'research_artifact_content', filePath, content, fileName, isImage: false });
2744
+ }
2687
2745
  }
2688
2746
  }
2689
2747
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.8.14",
3
+ "version": "0.8.16",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {