bloby-bot 0.70.12 → 0.71.0

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.
Files changed (65) hide show
  1. package/bin/cli.js +234 -48
  2. package/dist-bloby/assets/{bloby-DSNB0g4w.js → bloby-es6cZJzs.js} +6 -6
  3. package/dist-bloby/assets/globals-DBqwNiJV.css +2 -0
  4. package/dist-bloby/assets/{globals-B3cTbITX.js → globals-DN3F0CQE.js} +1 -1
  5. package/dist-bloby/assets/{highlighted-body-OFNGDK62-BLforpkr.js → highlighted-body-OFNGDK62-8PiOHw9p.js} +1 -1
  6. package/dist-bloby/assets/mermaid-GHXKKRXX-BJWX8urU.js +1 -0
  7. package/dist-bloby/assets/{onboard-Dn2Ws_G2.js → onboard-BKgy17OU.js} +1 -1
  8. package/dist-bloby/bloby.html +3 -3
  9. package/dist-bloby/onboard.html +3 -3
  10. package/package.json +3 -4
  11. package/scripts/install +156 -41
  12. package/scripts/install.ps1 +146 -29
  13. package/scripts/install.sh +156 -41
  14. package/shared/config.ts +37 -2
  15. package/shared/relay.ts +3 -1
  16. package/supervisor/channels/manager.ts +84 -44
  17. package/supervisor/channels/telegram.ts +57 -16
  18. package/supervisor/channels/types.ts +4 -1
  19. package/supervisor/channels/whatsapp.ts +57 -10
  20. package/supervisor/chat/OnboardWizard.tsx +0 -15
  21. package/supervisor/chat/src/components/Chat/AudioBubble.tsx +1 -1
  22. package/supervisor/chat/src/components/Chat/AuthedImage.tsx +16 -3
  23. package/supervisor/chat/src/components/Chat/BlobyImageCard.tsx +2 -2
  24. package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +25 -8
  25. package/supervisor/chat/src/components/Chat/InputBar.tsx +62 -7
  26. package/supervisor/chat/src/components/Chat/MessageBubble.tsx +37 -18
  27. package/supervisor/chat/src/components/Chat/MessageList.tsx +3 -3
  28. package/supervisor/chat/src/hooks/useChat.ts +52 -0
  29. package/supervisor/chat/src/lib/authedFile.ts +24 -12
  30. package/supervisor/file-saver.ts +92 -19
  31. package/supervisor/harnesses/attachment-policy.ts +111 -0
  32. package/supervisor/harnesses/claude.ts +62 -15
  33. package/supervisor/harnesses/codex.ts +69 -43
  34. package/supervisor/harnesses/pi/index.ts +367 -112
  35. package/supervisor/harnesses/pi/providers/humanize-error.ts +27 -2
  36. package/supervisor/harnesses/pi/providers/retry.ts +31 -0
  37. package/supervisor/harnesses/pi/providers/stream-anthropic.ts +31 -3
  38. package/supervisor/harnesses/pi/providers/stream-google.ts +26 -3
  39. package/supervisor/harnesses/pi/providers/stream-openai-completions.ts +32 -9
  40. package/supervisor/harnesses/pi/providers/types.ts +29 -1
  41. package/supervisor/harnesses/pi/session.ts +143 -3
  42. package/supervisor/harnesses/pi/test-completion.ts +56 -0
  43. package/supervisor/harnesses/pi/tools/bash.ts +198 -22
  44. package/supervisor/harnesses/pi/tools/glob.ts +79 -0
  45. package/supervisor/harnesses/pi/tools/grep.ts +0 -0
  46. package/supervisor/harnesses/pi/tools/registry.ts +18 -6
  47. package/supervisor/harnesses/pi/tools/todo-write.ts +45 -0
  48. package/supervisor/harnesses/pi/tools/web-fetch.ts +129 -0
  49. package/supervisor/index.ts +93 -18
  50. package/supervisor/widget.js +19 -5
  51. package/worker/db.ts +2 -0
  52. package/worker/index.ts +18 -1
  53. package/worker/prompts/bloby-system-prompt-codex.txt +1 -1
  54. package/worker/prompts/bloby-system-prompt-pi.txt +6 -24
  55. package/worker/prompts/bloby-system-prompt.txt +1 -1
  56. package/workspace/client/src/components/Dashboard/DashboardPage.tsx +4 -117
  57. package/workspace/client/src/components/Dashboard/deleteme_placeholders.tsx +194 -0
  58. package/workspace/client/src/components/Layout/Sidebar.tsx +52 -30
  59. package/workspace/client/src/components/deleteme_onboarding/WorkspaceTour.tsx +25 -15
  60. package/workspace/client/src/components/deleteme_onboarding/tour-theme.css +24 -0
  61. package/workspace/skills/mac/SKILL.md +13 -4
  62. package/dist-bloby/assets/globals-DyeW509Y.css +0 -2
  63. package/dist-bloby/assets/mermaid-GHXKKRXX-C1H_fSCU.js +0 -1
  64. package/supervisor/public/headphones_spritesheet.webp +0 -0
  65. package/supervisor/public/spritesheet.webp +0 -0
@@ -44,6 +44,15 @@ import type { SavedFile } from '../file-saver.js';
44
44
  import { getCodexAccessToken } from '../../worker/codex-auth.js';
45
45
  import { assembleSystemPrompt } from '../../worker/prompts/prompt-assembler.js';
46
46
  import { mirrorSkillsInto } from './skills.js';
47
+ import {
48
+ routeAttachment,
49
+ normalizeImageMediaType,
50
+ approxBase64Bytes,
51
+ buildSavedFilesNote,
52
+ INLINE_TEXT_PER_FILE_CHARS,
53
+ INLINE_TEXT_TOTAL_CHARS,
54
+ MAX_INLINE_IMAGE_BYTES,
55
+ } from './attachment-policy.js';
47
56
  import type { OnAgentMessage, RecentMessage, AgentAttachment, AgentQueryRequest, AgentQueryResult } from './types.js';
48
57
  export type { RecentMessage, AgentAttachment };
49
58
 
@@ -555,42 +564,24 @@ function emitDone(conv: CodexConversation): void {
555
564
 
556
565
  /* ── Input building ────────────────────────────────────────────────────── */
557
566
 
558
- /** mediaTypes whose content we inline into the prompt — codex has no document
559
- * input type (verified against 0.138 UserInput), so this is the closest we get
560
- * to claude's native base64 document ingestion for text-like files. */
561
- const INLINE_TEXT_MEDIA = /^(text\/|application\/(json|xml|yaml|x-yaml|toml|csv|javascript|typescript))/;
562
- const INLINE_TEXT_MAX_BYTES = 48_000;
563
- const INLINE_TEXT_TOTAL_BUDGET = 96_000;
564
-
567
+ /**
568
+ * Build codex `UserInput` blocks from the user text + saved files + raw
569
+ * attachments. Routing is delegated to the shared attachment-policy so codex
570
+ * stays byte-for-byte consistent with the Claude/PI harnesses. Codex's UserInput
571
+ * has NO native document type (verified against 0.138), so canNativeDocument is
572
+ * FALSE: PDFs/binaries become a disk-pointer note and the agent opens them with
573
+ * its file tools.
574
+ *
575
+ * Block order is MEDIA-first then TEXT (matching Claude/PI): the inline-text
576
+ * notes and the saved-files pointer are folded into the trailing text block.
577
+ */
565
578
  function buildUserInput(text: string, savedFiles?: SavedFile[], attachments?: AgentAttachment[]): Array<Record<string, any>> {
566
579
  const input: Array<Record<string, any>> = [];
567
580
 
568
- let promptText = text || '(attached files)';
569
- if (savedFiles?.length) {
570
- const lines = savedFiles.map((f) => `- ${f.name} -> ${f.relPath}`);
571
- promptText += `\n\n[Attached files saved to disk]\n${lines.join('\n')}\nYou can read or reference these files using the paths above (relative to your cwd).`;
572
- }
573
-
574
- // Inline text-like attachments (size-capped) so the model can answer about
575
- // their contents immediately instead of shelling out to read them from disk.
576
- if (attachments?.length) {
577
- let budget = INLINE_TEXT_TOTAL_BUDGET;
578
- for (const att of attachments) {
579
- if (att.type !== 'file' || !INLINE_TEXT_MEDIA.test(att.mediaType || '')) continue;
580
- const approxBytes = Math.floor((att.data?.length || 0) * 0.75);
581
- if (approxBytes === 0 || approxBytes > INLINE_TEXT_MAX_BYTES || approxBytes > budget) continue;
582
- try {
583
- const decoded = Buffer.from(att.data, 'base64').toString('utf-8');
584
- budget -= approxBytes;
585
- promptText += `\n\n[Attached file content: ${att.name}]\n\`\`\`\n${decoded}\n\`\`\``;
586
- } catch {}
587
- }
588
- }
589
-
590
- input.push({ type: 'text', text: promptText });
591
-
592
581
  // Codex understands `localImage` (path on disk) — Bloby's file-saver already
593
- // wrote attachments to disk, so we just point at the absolute path.
582
+ // wrote image attachments to disk, so we point at the absolute path. Track a
583
+ // per-name COUNT (not presence): WhatsApp multi-image pushes share one
584
+ // attachment name and each saved file covers exactly one of them.
594
585
  const savedImageCounts = new Map<string, number>();
595
586
  if (savedFiles?.length) {
596
587
  for (const f of savedFiles) {
@@ -601,23 +592,58 @@ function buildUserInput(text: string, savedFiles?: SavedFile[], attachments?: Ag
601
592
  }
602
593
  }
603
594
 
604
- // Fallback for image attachments that never made it to disk (file-saver
605
- // failure): inline as a data URL so the agent still SEES the image — claude
606
- // always gets the base64 inline, codex shouldn't silently go blind.
607
- // Match by COUNT, not name presence: WhatsApp multi-image pushes share one
608
- // attachment name, and each saved file covers exactly one of them.
595
+ // Route every attachment through the shared policy. Inline-text notes are
596
+ // accumulated into `inlineNotes` (appended to the trailing text block);
597
+ // images become data-URL blocks (with the localImage path already covering
598
+ // disk-saved copies); everything else falls back to the saved-files pointer.
599
+ let promptText = text || '(attached files)';
600
+ const inlineNotes: string[] = [];
601
+ let inlineBudget = INLINE_TEXT_TOTAL_CHARS;
609
602
  if (attachments?.length) {
610
603
  for (const att of attachments) {
611
- if (att.type !== 'image' || !att.data) continue;
612
- const remaining = savedImageCounts.get(att.name) || 0;
613
- if (remaining > 0) {
614
- savedImageCounts.set(att.name, remaining - 1);
615
- continue;
604
+ switch (routeAttachment(att, { canNativeDocument: false })) {
605
+ case 'image': {
606
+ if (!att.data) break;
607
+ // Skip data-URL inlining when a disk copy exists (localImage already
608
+ // points codex at it) or when the payload is too big to resend on
609
+ // every stateless turn — the saved-files pointer covers it instead.
610
+ const remaining = savedImageCounts.get(att.name) || 0;
611
+ if (remaining > 0) {
612
+ savedImageCounts.set(att.name, remaining - 1);
613
+ break;
614
+ }
615
+ if (approxBase64Bytes(att.data) > MAX_INLINE_IMAGE_BYTES) break;
616
+ const mediaType = normalizeImageMediaType(att.mediaType);
617
+ input.push({ type: 'image', url: `data:${mediaType};base64,${att.data}` });
618
+ break;
619
+ }
620
+ case 'inline-text': {
621
+ if (!att.data) break;
622
+ try {
623
+ let decoded = Buffer.from(att.data, 'base64').toString('utf-8');
624
+ if (decoded.length > INLINE_TEXT_PER_FILE_CHARS) decoded = decoded.slice(0, INLINE_TEXT_PER_FILE_CHARS);
625
+ if (decoded.length > inlineBudget) decoded = decoded.slice(0, inlineBudget);
626
+ if (!decoded.length) break;
627
+ inlineBudget -= decoded.length;
628
+ inlineNotes.push(`\n\n[Attached file content: ${att.name}]\n\`\`\`\n${decoded}\n\`\`\``);
629
+ } catch {}
630
+ break;
631
+ }
632
+ // 'native-document' cannot occur (canNativeDocument:false); it and
633
+ // 'reference-only' both rely on the saved-files disk pointer below.
634
+ default:
635
+ break;
616
636
  }
617
- input.push({ type: 'image', url: `data:${att.mediaType};base64,${att.data}` });
618
637
  }
619
638
  }
620
639
 
640
+ for (const note of inlineNotes) promptText += note;
641
+
642
+ const savedNote = buildSavedFilesNote(savedFiles || []);
643
+ if (savedNote) promptText += `\n\n${savedNote}`;
644
+
645
+ input.push({ type: 'text', text: promptText });
646
+
621
647
  return input;
622
648
  }
623
649