深度解析:多模态帧管线、推按说话语音回路与 LLM 提示面)
Rowboat 视频通话模式Video Mode / Calls深度解析多模态帧管线、推按说话语音回路与 LLM 提示面【免费下载链接】rowboatOpen-source AI coworker, with memory项目地址: https://gitcode.com/GitHub_Trending/rowb/rowboat导读本文深入剖析 Rowboat开源 AI 协作者具备记忆能力的本地桌面应用中通话Calls / Video Mode功能的完整技术实现用户在与助手对话时助手可以看见用户网络摄像头与看见用户的屏幕屏幕共享。全文从产品交互流、帧捕获管线、消息 Schema 与模型编码、推按说话Push-to-Talk语音回路、伴随窗口companion window机制、macOS 权限体系、LLM 提示词目录到共享屏幕指向screen-pointer与通话内驱动 App 的底层原理逐一展开并给出精确的源码指针。读完本文你将掌握 Rowboat 视频通话模式单一引擎、四种预设的设计哲学、1fps 帧采样与容量限制的参数化配置、基于按键释放的零端点化语音回路以及如何通过# Video Mode/# Practice Session提示片段约束多模态模型行为。本文关联文档为 apps/x/VIDEO_MODE.md文中所有路径均以仓库根目录为基准。一、产品流一个通话引擎四个预设入口Rowboat 的通话功能遵循ONE call engine单一通话引擎原则只有一套底层通话机制——按住说话Push-to-Talk下称 PTT语音输入、强制全文朗读read-aloud的 TTS 输出、帧捕获frame capture——但通过四个只在起始设备上有差异的预设preset向用户呈现不同的体验。预设起始设备首次出现的界面voice— 主按钮点击、⌥⇧Space、托盘 Quick Ask、卡片的收纳手柄、Home 的 Skipper、发现性 toast摄像头关、屏幕共享关若用户选择过粘性共享则自动重放Skipper 卡片悬停模式share— Share screen共享屏幕屏幕共享开、摄像头关Skipper 卡片——即同样的悬停召唤只是从一开始就带着屏幕共享video— Video call视频通话摄像头开浮动小药丸摄像头在小药丸内展开进入全屏practice— Practice session练习会话摄像头开外加教练coaching人格全屏通话入口控件位于输入区composer的通话拆分按钮实现于 chat-input-with-mentions.tsx主点击即悬停伴生模式voice预设chevron 菜单里才是其余三种偏离项。通话进行中按钮变为红色点击即结束通话。单一悬停流ONE hover flow文档反复强调ONE hover flow所有悬停入口最终都汇聚到startHoverCall()定义于 App.tsx和弦键chord、托盘项、收纳手柄、toast 走主进程路径relaySummon()→quick-ask:tuck→ 应用通话按钮与 Home 的 Skipper 直接调用startHoverCall()。startHoverCall()会先应答中继quickAsk:tuckAck启动voice预设然后才发起粘性的或被share预设强制的屏幕共享——且是 fire-and-forget即发即忘in-flight 守卫in-flight guard在通话引擎落定的那一刻即释放绝不会在设备获取期间被长时间占用。一个启动失败的会话会回退到文本卡片quickAsk:show因此召唤永远不会是静默的空操作若未配置语音则从一开始就以文本卡片作为答案。单一表面规则callSurfaceApp.tsx中的callSurface派生值规定了通话的呈现方式核心铁律是全屏与屏幕共享互斥——全屏通话覆盖了整个屏幕此时共享屏幕就等于把通话本身共享出去。具体规则共享中 → 始终浮动弹窗popout小药丸即工作态未共享 → 全屏除非callMinimized全屏即面对面展开小药丸会自动停止任何共享最小化全屏通话会自动开启一个共享药丸的存在意义就是边通话边协作全屏下做演示presenting同样折叠回药丸摄像头开关永不改变表面从药丸打开摄像头只是把视频放进药丸里展开是另一个显式动作。屏幕共享的三层同意机制任一共享开始的瞬间弹出 toastYour screen is being shared… [Stop sharing]药丸上常驻 Sharing screen 徽章macOS 的紫色录制指示器purple recording indicator。若自动共享失败未授予屏幕录制权限通话仍会以纯语音通话形式启动并弹出一个指向系统设置的 toast。练习/教练模式始终是显式选择——展开全屏绝不会自动开启教练。通话内控制与状态机通话内控制条在两个表面上完全一致按住说话按钮按住说 / 点按锁定免提——与 Right ⌘ 键行为完全镜像、麦克风静音、摄像头开关关闭时显示剪影头像不捕获任何摄像头帧、屏幕共享开关、吉祥物 ⇄ R 字母头像、结束通话。状态提示条status chip引导用户使用 PTT空闲时Hold right ⌘ to talk · tap to go hands-free捕获中Listening — release to send免提锁定时Hands-free — tap ⌘ to send。弹窗内嵌的是真实的聊天输入区ChatInputWithMentions-提及、附件、单轮配置在通话中全部可用消息像普通输入区消息一样落入会话帧随之携带。最小化表面上不渲染任何转写文本——回复以语音朗读想看文字就展开。静音是完整的输入暂停而不只是静音麦克风音频停止到达 DeepgramuseVoiceMode.setPaused与思考/说话自动暂停按 OR 合并摄像头/屏幕帧捕获同样停止useVideoMode.setCapturePaused静音期间collectFrames()返回空所以静音期间的打字消息也不携带任何帧——用户可以转身和房间里的人说话而不被助手偷听设备保持占用以便即时取消静音摄像头指示灯与 macOS 共享指示器保持开启——药丸上的共享徽章会切换为 Sharing paused状态提示条显示 Muted 而非 Listening助手输出不受影响进行中的语音继续播放用 Stop 处理静音状态在通话开始/结束时重置为关闭。当助手思考或说话时吉祥物磁贴mascot tile上会出现红色Stop按钮立即静音 TTS、跳过排队中的语音片段若 run 仍在生成则中止该 run从任何位置停止 run——包括输入区——也会静音 TTS。进行中话语的字幕与助手朗读的句子沿底部滚动。输入区在通话中仍可打字帧也会随打字消息一起携带。通话之外唯一保留的语音入口通话之外输入区只保留一个语音控件麦克风按钮按住说话的听写保持不变。语音回复只存在于通话内部强制全文朗读挂断即关闭。旧的视频下拉菜单、talking-head 开关、朗读耳机开关、摘要/全文 TTS 下拉菜单均已退役——计划用助手消息上的per-message read aloud动作来替代文本进/语音出。通话按钮在语音输入Deepgram与语音输出TTS均已配置时才可用。call_started携带preset事件被 PostHog 采集作为该功能的采纳指标。弹窗机制Popout mechanics浮动小药丸就是伴随窗口companion window——即 ⌥⇧Space 召唤的那个始终置顶窗口——的药丸布局开启时的摄像头磁贴 吉祥物磁贴、实时字幕、控制条、输入区重新定位到右上角。它浮在每个应用之上包括 Rowboat 自己。控制条动作通过video:popoutAction→ 主进程 →video:popout-action→ 应用窗口往返应用窗口拥有麦克风/摄像头/捕获expand还会将焦点重新还给应用窗口在 main 中处理。二、帧管线流 → 离屏 video → canvas JPEG → 环形缓冲核心实现在 useVideoMode.ts。useVideoMode为每个数据源各跑一条捕获管线CapturePipe管道为MediaStream → 离屏 video → canvas JPEG → 环形缓冲。捕获节奏与帧容量参数useVideoMode.ts顶部常量均可按需调整参数值说明CAPTURE_INTERVAL_MS1000每秒抓一帧足以捕捉表情与姿态变化同时控制请求体积MAX_BUFFERED_FRAMES120环形缓冲上限 ≈ 2 分钟只覆盖两次发送之间的间隙MAX_CAMERA_FRAMES_PER_MESSAGE12每条消息最多携带 12 帧摄像头画面MAX_SCREEN_FRAMES_PER_MESSAGE4屏幕帧约 4 倍分辨率与 token且最新一帧远比轨迹重要故上限更小CAMERA_FRAME_WIDTH512摄像头帧缩到 512px 宽单帧约 20–40KB每条消息内联十几帧成本可控SCREEN_FRAME_WIDTH1280屏幕帧保留 1280px保证屏幕上文字对模型可读CAMERA_JPEG_QUALITY0.65摄像头帧 JPEG 质量SCREEN_JPEG_QUALITY0.7屏幕帧 JPEG 质量attachPipeSource在数据源首次送达时立即抓取首帧loadeddata事件随后按setInterval稳定节拍抓取capturePipeFrame对近空 data URL100 字节直接丢弃以跳过源尚未温热的空帧。collectFrames()的采样策略drainPipe排空自上次发送以来缓冲的帧均匀采样evenly sampled至上限始终保留最新帧并在发送瞬间额外抓取一帧当快速连续发送消息rapid-fire没有新帧累积时回退到仅最新一帧——保证源预热后每条消息至少带一帧。帧按来源标记摄像头帧在前屏幕帧在后。一个值得注意的细节useVideoMode.ts实现了对 macOS 屏幕录制权限的经验性检测——screenFrameLooksBlank()。因为 macOS 在未授权时仍会让捕获成功只是交付全黑帧所以代码将捕获到的 160×90 缩略图做亮度均值与方差分析均值 8 且方差 4 判定为内容被屏蔽真实的桌面——哪怕是深色——也有菜单栏、窗口等结构。该函数先等 700ms 让捕获稳定避免把合法的启动黑帧误判为权限问题。静音暂停捕获setCapturePaused(true)时两条管道的抓取函数直接 returncollectFrames()也返回空数组连静音前的缓冲帧都不带杜绝静音时段被后来的消息携带。在 App.tsx 的handlePromptSubmit中当通话进行时排空的帧会作为UserImagePart附加到外发消息上摄像头或屏幕任一激活时设置composition.videoMode练习会话期间额外设置composition.coachMode。帧同时也成为isVideoFrame显示附件转写面板里的胶片条——见 chat-message-attachments.tsx历史会话回填在 lib/run-to-conversation.ts。三、消息 Schema 与模型编码UserImagePart真正的多模态图像部分定义于 packages/shared/src/message.tstype: image、data无data:前缀的 base64 字节、mediaType如image/jpegsource: camera | screen可选——标记帧来自摄像头还是屏幕capturedAt可选 ISO 时间戳。关键区别与文件附件不同附件是路径引用、通过LLMParse工具读取图像部分是以真实的多模态图像部分real multimodal image parts直接发给模型的。convertFromMessages上下文行 分组标签编码逻辑位于 packages/core/src/runtime/assembly/message-encoding.ts 的convertFromMessages先发出一个上下文行[Video mode: N live webcam frames … and M frames of the users shared screen …]实测文本为[Video mode: … attached below, each group oldest to newest, recorded while they composed this message.]见该文件 L201摄像头图像前加Webcam frames (oldest to newest):文本部分屏幕图像前加Screen-share frames (oldest to newest):文本部分。这样模型永远不会把用户本人与其屏幕混淆。历史中的帧刻意保持内联不裁剪裁剪会每次轮转都破坏提供方的前缀缓存prefix caching成本反而高于保留。而自动权限分类器auto-permission classifier会把每条消息内容字符串化并截断到约 3KB因此内联 base64 不会撑爆其提示词。四、推按说话Push-to-Talk语音回路Rowboat 语音回路的核心哲学用户的按键手势就是端点endpoint——没有静音检测silence detection没有端点化启发式endpointing heuristics且助手的 TTS永远不会被转写回给自己麦克风门只在用户刻意说话时打开。手势语义Right ⌘或任一表面的屏幕说话按钮按住≥350ms按住期间麦克风门打开松开即提交话语submit the utterance。快速点按350ms助手正在说话时点按 STOP——完全打断音频 排队回复 生成麦克风保持关闭下一次点按恢复正常行为静默时点按锁定免提捕获hands-free下一次点按提交。免提锁定期间仍无自动提交——闭合的点按才是端点。和弦Right ⌘ 按住时按其他键/点击此次按压是键盘快捷键而非说话手势——进行中的按住被取消锁定中的捕获吞掉对应的松开。Escape 同样取消。在助手思考/说话时按下静音其音频并开始监听——但 run 与其回复依然存活意外或空按压永远不会白费答案未说出的语音段冻结并在松开时恢复。只有真正提交的话语才会中止上一轮并丢弃其未说出的积压。Stop 按钮仍是硬中止。边沿触发的 PTT 状态机App.tsx中的handlePttDown/handlePttUp/handlePttChord构成一个边沿触发edge-triggered的状态机聚合两个输入源全局按键钩子apps/main/src/ptt.ts基于 uiohook-napi系统级监听 Right ⌘ 的 down/up/chord通过voice:ptt-key推送给应用窗口。仅当通话或其他消费方激活时运行用voice:setCallActive引用计数。需要 macOS 的输入监控权限ptt:getStatus中的eventsSeen是活性信号钩子已运行但事件数为 0 权限未生效——应用会在首次通话约 4 秒后显示一次性权限对话框。DOM 监听器应用窗口聚焦时监听e.code MetaRight的 keydown/keyup——无需输入监控权限的兜底方案。两个源在应用聚焦时重叠80ms 内的相同边沿合并为一次PTT_EDGE_ECHO_MS。useVoiceMode 的会话 API核心实现在 useVoiceMode.tsstartPtt(onUtterance)为整个通话获取麦克风 Deepgram socket按键按下即可瞬时捕获音频通过setPaused(true)门控关闭每 5s 一次的 KeepAlive 保持空闲 socket 存活Deepgram 无音频约 10s 会关闭连接见KEEPALIVE_INTERVAL_MS。pttBegin()清空转写缓冲并打开麦克风门。pttEnd()冲刷缓冲音频发送 DeepgramFinalize读取最终转写 尾部 interim关闭门触发onUtterance。实现中先flushBufferedAudio()再finalizeDeepgramStream()且先读转写再setPaused(true)setPaused(true)会清空转写缓冲注释明确警告顺序问题。pttCancel()关闭门并丢弃已听到的一切。通话中 socket 掉线 1s 后重连ws.onclose中若pttCbRef.current仍存在则重连离线音频积压有上限约 500 块 × 64ms ≈ 30s。Deepgram 会话参数DEEPGRAM_PARAMSmodelnova-3、encodinglinear16、sample_rate16000、channels1、interim_resultstrue、smart_formattrue、punctuatetrue、languageen、endpointing100、no_delaytrue。波形由 ScriptProcessor 以 1024 采样帧16kHz 下约 64ms产出约 16 次/秒的振幅更新自动增益基于运行峰值PEAK_DECAY0.97、MIN_PEAK0.02缓冲上限MAX_AUDIO_LEVELS4800。通话生命周期App.tsx的startCall(preset)/endCall()进入通话会保存/强制 TTS 设置、取消任何输入区听写、启动 PTT 会话结束则恢复一切。通话持有麦克风期间输入区听写被禁用。静音完全阻断 PTT静音时按键无操作捕获中静音则丢弃已听内容。五、伴随窗口悬停表面一个始终置顶的窗口主进程 apps/main/src/quick-ask.ts渲染端 components/quick-ask-bar.tsxhash#quick-ask通过quick-ask:modepinned | hidden推送只有一种可见角色Skipper自包含卡片顶部条logo · 目标 · 窗口操作 · 分享 · 说话/停止 · 小的 ✕ 关闭下面是真实输入区药丸活跃摄像头需要自视self-view时。吉祥物只存在于摄像头药丸中卡片改用信号表达麦克风门打开时输入区切换为应用的录制条波形 interim 转写接入通话的 PTT 状态机↑ 发ptt-up✕ 丢弃发ptt-cancel进行中的轮次显示为顶部 logo 的辉光 响应面板的 spinner-and-shimmer 活动行输入区占位符按平台命名说话键hold right ⌘ / right Ctrl to speak平台感知逻辑在 packages/shared/src/ptt-key.ts。没有独立的 popout 窗口也没有第二个 ask bar 角色。NSPanel 细节窗口是type: panel的 NSPanel关键配置setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true, skipTransformProcessType: true })浮于每个 Space 之上包括其他应用的全屏 SpaceskipTransformProcessType保住 Dock 图标否则visibleOnFullScreen会在窗口存在期间把应用变成 macOS agent 应用——看起来像 Rowboat 消失了fullscreenable: false否则在激活 Space 为全屏时创建的窗口可能直接以全屏窗口打开药丸吞掉整个屏幕。钉住pinned当且仅当派生的callSurface popoutApp.tsx 中的 effect。渲染端请求video:setPopout {show}main 重新定位伴随窗口Skipper 卡片在其锚点角落摄像头通话用旧的 popout 几何位置右上角并显示有待处理的召唤时聚焦否则showInactive()以免抢焦点。Blur 不产生任何行为旁边工作的伴随窗口不能因为点击别处就消失Esc 只是收起文本而非关闭⌥⇧Space 折叠/展开文本面板。揭示协议Reveal protocol每次quick-ask:mode推送携带seq渲染端在提交该呈现两帧后即已绘制通过quickAsk:modeApplied应答。main 先把窗口以 opacity 0 排序显示以便渲染端绘制收到 ack 后才置 opacity 1聚焦——绝不让半成布局上屏。折叠同样先推送、收到 ack 后缩小窗口开放卡片永远不会被挤进吉祥物大小的边界哪怕一帧。超时600ms页面仍加载时为 6s防止卡死的渲染端阻塞。渲染端在知道自身角色前什么都不绘制hidden 时完全不绘制index.html从第一帧绘制起就给#quick-ask/#screen-pointer透明背景。通话状态流通过video:popout-state推送通道main 在 quick-ask.ts 缓存最后载荷在窗口加载和每次钉住时重放。渲染端离开 pinned 角色即丢弃其镜像通话结束时应用推送显式 idle 状态缓存跨全屏 ⇄ 弹窗抖动存活所以摄像头通话回来仍是药丸而非变形的卡片。无应用窗口用户关闭了或仍在加载召唤会以隐藏态重建它main.ts中initQuickAsk({ ensureAppWindow })并在应用的quickAsk:appReady握手时重发中继未应答的中继由看门狗重发一次有应用窗口时 1.5s启动期间 8s之后记录日志绝不用其他表面应答。通话中关闭应用窗口会取消伴随窗口的钉住onAppWindowClosed。药丸捕获自己的摄像头预览MediaStream 无法跨窗口并合成吉祥物嘴部幅度该窗口无音频。而卡片的录制条波形是真实的应用窗口通过video:popoutLevels→video:popout-levels中继语音钩子的逐帧振幅约 16/s批量条随应用输入区自己的节拍跟踪真实语音。Levels 绝不在 main 中缓存——波形只有实时才有意义。磁贴显示实时像素控件显示能力。纯语音通话摄像头关、无共享渲染没有 You 磁贴的药丸吉祥物 响应 输入区 控件——展开一个语音通话绝不会被读成用户没开始的视频通话。切换摄像头/共享会在原位变换磁贴/徽章。video:popoutAction中继控制条动作到应用窗口只按真实应用窗口 URL 匹配——getAllWindows()也包含伴随窗口与隐藏工具窗口PDF 导出这些绝不能显示或发消息。药丸聚焦时按 Right ⌘ 同样作为 ptt-down/ptt-up 动作中继该场景无需输入监控权限。收起态迷你通话药丸voice-to-voice钉住表面可折叠为迷你通话药丸quickAsk:setPinnedCollapsed呈现状态随quick-ask:mode推送logo点击展开· 状态通道 · 共享 · 说话/停止 · 结束药丸主体作为拖拽手柄左侧有 « 展开手柄作为回到文本输入的可见路径镜像卡片的 » 收纳手柄⌥⇧Space 和 logo 也可。状态通道在折叠时持续叙述——说话时抖动波形、思考时 spinnershimmer 活动、朗读回复时连贯滚动说话波、静止时说话键提示Hold right ⌘ / Hold right Ctrl平台感知——除提示外动态即全部故事药丸双向都不显示转写用户把文本收起来了展开去读。活跃共享在点亮的共享按钮上保持同意徽章。卡片的 » 收纳手柄、Esc、点击卡片旁舞台均进入此状态⌥⇧Space 切换之。收起/展开从不结束会话——只有 End close 才结束。六、权限体系摄像头voice:ensureCameraAccess在getUserMedia之前解决 macOS TCC 提示与麦克风同一模式——否则首次点击会在原生提示弹出期间静默失败。NSCameraUsageDescription在 forge.config.cjs 的extendInfo中。屏幕getDisplayMedia由 main.ts 的setDisplayMediaRequestHandler以主屏幕自动批准无选择器meeting:checkScreenPermission在首次使用时把应用注册进 macOS屏幕录制设置列表。由于权限被拒或提示未应答时getDisplayMedia可能永远挂起useVideoMode.startScreenShare将整个获取过程限时 10sSCREEN_SHARE_START_TIMEOUT_MS并干净失败——历史上一次挂起的共享会把screenState卡在starting直到会话结束见 useVideoMode.ts L321-L384 注释。输入监控全局 PTT 键钩子启动 uiohook 事件 tap 会在首次使用时触发 macOS 同意提示但缺失授权不会报错——只是事件永不到达eventsSeen保持 false。授权前创建的 tap 永远失效ptt:retryHook重新创建它见 ptt.ts 的retryPttHook以及openInputMonitoringSettings直接打开x-apple.systempreferences:...Privacy_ListenEvent面板。拒绝永不沉默components/permission-dialog.tsx 是麦克风/摄像头/输入监控失败背后唯一的对话框——解释缺失的权限并深链到精确的系统设置面板app:openPrivacySettings。屏幕共享失败保持 toast通话进行中模态框会挡路。七、LLM 提示词目录提示位置# Video Mode (Live Camera)系统片段——如何使用摄像头帧、教练指引、屏幕共享规则把屏幕当作主要主题、最后一帧屏幕帧即当前状态、礼仪绝不评论外貌packages/core/src/runtime/assembly/capabilities/modes.tsvideo-mode能力的VIDEO_MODE片段由runtime/assembly/compose-instructions.ts组合# Practice Session (Coach Mode)系统片段——教练人格每次 take 后给出具体、可行动的反馈流程中一句句插话收尾时结构化复盘同上modes.tsCOACH_MODE片段紧随视频能力之后视频模式片段中的 Driving the app 段落——通话中优先用应用导航的 read-view/open-item边说边展示而非描述或眯眼看帧同一# Video Mode片段完整动作文档在app-navigation技能runtime/assembly/skills/app-navigation/skill.ts每消息帧上下文行[Video mode: N live webcam frames … and M frames of the users shared screen …] 分组标签packages/core/src/runtime/assembly/message-encoding.tsconvertFromMessagesvideoMode/coachMode合成覆盖会话粘性翻转会打破前缀缓存packages/core/src/runtime/turns/bridges/real-agent-resolver.tsCompositionOverrides由App.tsx的sendConfig设置语音输入/输出提示片段# Voice Input、# Voice Output原样复用——通话按话语设置voiceInput并强制voiceOutput: full。从modes.ts源码可提取VIDEO_MODE片段的完整指令语义帧的使用用于视觉感知真正增值之处——表情、肢体语言、姿态、手势、与镜头的眼神接触、举到镜头前的东西、相关环境跨帧比较以发现消息内的变化对表演性练习pitch、演示、面试、讲话给出基于实际所见的具体、可行动反馈最后几帧你低头移开了视线而非泛泛建议若用户向镜头展示物体/文档/白板读出来并回应。驱动应用优先read-view 读取数据 屏幕上展示 简短口述而非眯眼看屏幕帧——读视图数据是精确的。屏幕共享屏幕帧在独立分组屏幕是主要主题最后一帧最当前帧是降采样捕获小字可能难读需要时明确要求放大错误信息而非猜。礼仪不要每轮都叙述所见绝不评论外貌帧是周期性快照而非连续视频不得断言看不见的连贯运动。COACH_MODE片段则要求观察节奏/填充词/结构/从帧看姿态眼神表情手势能量每次 take 后给 2-3 条具体观察并引用事实你说 ask 时低下了头、开头的 um so basically 削弱了你的钩子流程中最多一句插话或保持沉默开局若不明显就问练习目标投资人 pitch面试会议演讲收尾给结构化复盘做得好的、Top 3 改进、一个具体练习/重构建议鼓励但诚实绝不评论外貌。八、指向共享屏幕Pointing at the shared screen屏幕共享进行中助手可以指向用户真实显示器上的位置screen-pointer内建工具由app-navigation技能附加接受分数坐标x/y 取值 0–1从最新屏幕共享帧估算加可选的小标签main 在那里绘制动画激光点 ping 环。这个低谷就是周末从此带着一根手指落在图表上。工具定义packages/core/src/runtime/tools/domains/screen-pointer.ts——动作pointx、y、label?≤60 字符、durationMs?默认自动隐藏 8s、范围 1000–30000ms和hide。通过 DI 接缝IScreenPointerService像浏览器控制一样注册于 main.ts直接在 main 中执行——无渲染端往返——无共享进行时以解释性错误硬失败No screen share is live — pointing only works while the user shares their screen…。共享门App.tsx 的一个 effect 把video.screenState live通过screenPointer:setShareActive上报共享结束立即拆除指针。覆盖层apps/main/src/screen-pointer.ts 创建一个透明、点击穿透setIgnoreMouseEvents、不可聚焦、屏保级别的 NSPanel 覆盖主显示器共享始终捕获主显示器加载带#screen-pointer的渲染端 → components/screen-pointer-overlay.tsx。状态通过screen-pointer:state推送加载时重放nonce在指向同一位置两次时重启 ping。窗口只在有点击目标时存在——隐藏即销毁。提示面# Video Mode屏幕共享片段中的 You can POINT at their screen 要点modes.tsapp-navigation技能中带工作示例的 Pointing at the users shared screen 小节。点击/键入曾被探索并移除文档记录了一段重要的设计史一个screen-control工具点击帧坐标 向聚焦字段键入曾短暂上线且机制上可用但被移除从 1280px 宽的帧瞄准小目标会落空模型在动作之间是失明的帧只随用户消息到达导致它键入错误的焦点却报告成功缺失的拼图是工具结果中的动作后验证帧post-action verification frame。若重建文档留下硬性教训现代 macOS 上 System Events 的click at不点击也返回成功——应通过osascript -l JavaScript ObjC 桥发送真实 CGEvents无需原生模块、仅 Accessibility、无需 Automation 同意未受信进程的 CGEventPost 会静默丢事件所以前置的isTrustedAccessibilityClient自检是唯一可靠的门TCC 键授予的是代码签名ad-hoc 构建每次重建都会失去授权Developer ID 签名可解决。Web 任务从来不需要它——内嵌浏览器browser-control以元素级精确操作并逐动作返回页面状态。完整实现在分支历史feat/screen-pointer-controlpre-removal。九、通话中驱动应用Driving the app on a call助手可以通过扩展的app-navigation内建工具app driver驱动 Rowboat UI 本身open-view打开任意主视图read-view返回该视图渲染的数据邮件 / 后台代理 / 会话历史——渲染端同时导航过去用户眼睁睁看着它发生open-item深度链接打开特定邮件线程、笔记、后台代理或过往会话。数据来自与 UI 的 IPC 处理器相同的核心函数listImportantThreads/searchThreads、后台任务的listTasks、sessions 容器——没有对屏幕帧做 OCR。渲染端通过 App.tsx 的applyAppNavigation应用结果同时从两条事件路径取数遗留的runs:eventsref 轮询以及会话聊天对话上的 watcher轮次运行时不发出遗留 run 事件——漏掉这条导航会在工具报告成功时静默空转。会话切换会播种 watcher因此重放历史从不触发导航。通话中可见导航还会把全屏通话折叠成药丸并聚焦应用窗口app:focusMainWindow让用户真正看到屏幕变化。卡片标签在 lib/chat-conversation.ts。通话提示与app-navigation技能共同教授边说边展示show-while-telling模式read-view → 口述要点 → 用户选中后 open-item。十、延迟工程语音到语音的四点优化语音到语音延迟用户停止说话 → 助手出声在四个点被工程化call_turn_latencyPostHog 事件度量真实分布utterance → submit → first speak → audio playingPTT 端点useVoiceMode.ts的pttEnd按键释放就是端点——完全没有静音检测。释放后的提交延迟只剩 DeepgramFinalize往返典型值远低于旧式 600–1800ms 的端点化等待且误触发说到一半被截断、TTS 混入在结构上不可能。流式 TTSvoice:synthesizeStreamStart→voice:tts-chunk→ useVoiceTTS.ts 中的 MediaSource 播放空闲队列的首个片段从第一个 MP3 块就开始播放而非等完整正文ElevenLabs/streamflash 模型。后续片段保持无缝的全正文预取路径。任何失败回退到非流式。早句语音turn-view.tsx 的applyOverlay仍在打开的voice块 ≥60 字符时立即发出其最后一个完整子句——句子其余部分还在生成时语音已经开始。确认提示音lib/call-sounds.ts话语被接受的瞬间播放轻柔 blip——感知延迟与实测延迟同样重要。十一、在 Skipper 上打字Skipper 卡片承载真实输入区ChatInputWithMentions知识笔记 -提及、附件、模型/精力选择器、搜索/代码/权限开关。提交通过 main 中继完整载荷quickAsk:submit→quick-ask:submit→handleHoverSubmit进入伴随窗口自己的会话——绝不是应用窗口的会话——回复通过语音回复的同一调用镜像返回video:popoutState→video:popout-state因此没有第二条答案通道。语音跟随问题的模态打字提问静默渲染语音提问朗读出来外加卡片上的显式扬声器静音。输入区上方的条带承载目标 chip此会话继续哪个会话带最近切换器Command Center 优先、新会话、历史窥视、扬声器静音、Open-in-Rowboat。设备控制麦克风、共享、结束在吉祥物的别针上打开与收起两种状态皆有。已退役2026-08summoned角色——一个独立 Spotlight 式 ask bar带自己的答案面板quickAsk:state、本地听写quick-ask:summoned按住说话、Stop 中继quickAsk:stop、关闭quickAsk:hide/blur以及 voice-out 无通话共享开关quickAsk:setOptions/quickAsk:optionsState。这些通道及其 App 侧管道speakTurnRef、quickAskActiveRef、quickAskOptionsRef已删除。⌥⇧Space 现在只有一个结果没有第二个布局会闪烁、竞争或卡死。无法成为会话的召唤语音未配置或引擎启动失败会在应用窗口中解释——前台化并带打开设置的 toast——绝不通过显示不同的浮动表面。十二、成本说明与已知限制Token 成本基准帧内联多模态摄像头帧每帧约 250–350 tokens≤12 帧/消息 ≈ 3–4k屏幕帧每帧约 1.5–2k tokens≤4 帧/消息 ≈ 6–8k。历史保持帧内联长会话增长但保持前缀缓存。成本吃紧时的第一根杠杆除非屏幕变化每条消息只发一帧屏幕帧。已知限制以当前仓库为准无开放式麦克风插话open-mic barge-in——但在助手说话时按 PTT 会静音它并开始监听新话语提交后该 run 被中止所以打断从不依赖 Stop 按钮。全局 PTT从其他应用按 Right ⌘需要 macOS 输入监控权限没有它 PTT 仅在应用窗口聚焦时可用DOM 兜底。帧采样而非视频帧间运动不可见提示明确要求模型不得断言相反。声乐交付反馈有限Deepgram 把语音降为文本能量教练依赖视觉线索。屏幕共享始终捕获主显示器尚无窗口/显示器选择器。全屏通话遮住会话无通话中转写抽屉。不发起通话但给打字消息附加摄像头帧的组合旧 videochat 模式在通话模型简化中被移除若分析显示有需求它应以附件 chip 而非模式回归。结语Rowboat 的视频通话模式是单一引擎、四种预设设计哲学的完整落地一套 PTT 强制朗读 TTS 帧捕获引擎通过起始设备差异呈现 voice / share / video / practice 四种体验callSurface规则保证全屏与共享互斥collectFrames的均匀采样与容量上限把多模态请求控制在可预测的 token 预算内convertFromMessages的分组标签让模型从不混淆用户与屏幕PTT 以按键释放为端点消灭了端点化延迟伴随窗口与揭示协议保证了悬停体验的即时与稳定。对想要复用或扩展这套能力的人来说VIDEO_MODE.md 与本文给出的源码指针useVideoMode.ts、useVoiceMode.ts、modes.ts、ptt.ts是继续深入的最佳起点。【免费下载链接】rowboatOpen-source AI coworker, with memory项目地址: https://gitcode.com/GitHub_Trending/rowb/rowboat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考