Address Greptile review: add explicit "outside-workspace" case to
toSaveMediaSourceError so it returns "Media path is outside workspace
root" instead of the generic "Media path is not safe to read".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When editing a file outside the workspace root, SafeOpenError previously
used the "invalid-path" code with the message "path escapes root". This
was indistinguishable from other invalid-path errors (hardlinks, symlinks,
non-files) and consumers often fell back to a generic "not found" message,
which was misleading.
Add a new "outside-workspace" error code with the message "file is outside
workspace root" so consumers can surface a clear, accurate error message.
- fs-safe.ts: add "outside-workspace" to SafeOpenErrorCode, use it for
all path-escapes-root checks in openFileWithinRoot/writeFileWithinRoot
- pi-tools.read.ts: map "outside-workspace" to EACCES instead of rethrowing
- browser/paths.ts: return specific "File is outside {scopeLabel}" message
- media/server.ts: return 400 with descriptive message for outside-workspace
- fs-safe.test.ts: update traversal test expectations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(doctor): detect groupPolicy=allowlist with empty groupAllowFrom
The existing `detectEmptyAllowlistPolicy` check only covers
`dmPolicy="allowlist"` with empty `allowFrom`. After the .26 security
hardening (`resolveDmGroupAccessDecision` fails closed on empty
allowlists), `groupPolicy="allowlist"` without `groupAllowFrom` or
`allowFrom` silently drops all group/channel messages with only a
verbose-level log.
Add a parallel check: when `groupPolicy` is `"allowlist"` and neither
`groupAllowFrom` nor `allowFrom` has entries, surface a doctor warning
with remediation steps.
Closes#27552
* fix: align empty-array semantics with runtime resolveGroupAllowFromSources
The runtime treats groupAllowFrom: [] as unset and falls back to
allowFrom, but the doctor check used ?? which treats [] as authoritative.
This caused a false warning when groupAllowFrom was explicitly empty but
allowFrom had entries.
Match runtime behavior: treat empty groupAllowFrom arrays as unset
before falling back to allowFrom.
* fix: scope group allowlist check to sender-based channels only
* fix: align doctor group allowlist semantics (#28477) (thanks @tonydehnke)
---------
Co-authored-by: mukhtharcm <mukhtharcm@gmail.com>
* Gateway: allow Google Fonts stylesheet and font CDN in Control UI CSP
* Tests: assert Control UI CSP allows required Google Fonts origins
* Gateway: fix CSP comment for Google Fonts allowlist intent
* Tests: split dedicated Google Fonts CSP assertion
* fix(tts): use opus format and enable voice bubbles for feishu and whatsapp
Previously only Telegram received opus output and had `shouldVoice=true`.
Feishu and WhatsApp also support voice-bubble playback and require opus audio,
but were falling back to mp3 with `audioAsVoice=false`.
- Extract VOICE_BUBBLE_CHANNELS set (telegram, feishu, whatsapp)
- resolveOutputFormat: return TELEGRAM_OUTPUT (opus) for all voice-bubble channels
- shouldVoice: enable for all voice-bubble channels, not just telegram
- Update test to cover feishu and whatsapp cases
* Changelog: add TTS voice-bubble channel coverage note
---------
Co-authored-by: Ning Hu <ninghu@Nings-MacBook-Pro.local>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* feat(feishu): extract embedded video/media from post (rich text) messages
Previously, parsePostContent() only extracted embedded images (img tags)
from rich text posts, ignoring embedded video/audio (media tags). Users
sending post messages with embedded videos would not have the media
downloaded or forwarded to the agent.
Changes:
- Extend parsePostContent() to also collect media tags with file_key
- Return new mediaKeys array alongside existing imageKeys
- Update resolveFeishuMediaList() to download embedded media files
from post messages using the messageResource API
- Add appropriate logging for embedded media discovery and download
* Feishu: keep embedded post media payloads type-safe
* Feishu: format post parser after media tag extraction
---------
Co-authored-by: laopuhuluwa <laopuhuluwa@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
- Respect groupConfig.enabled flag (was parsed but never enforced)
- Fix misleading log: group allowlist rejection now logs group ID and
policy instead of sender open_id
* feat(feishu): parse post rich text as markdown
* chore: rerun ci
* Feishu: resolve post parser rebase conflicts and gate fixes
---------
Co-authored-by: Wilson Liu <wilson.liu@example.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
## Summary
- honor Feishu wildcard group policy fallback via `channels.feishu.groups["*"]` when no explicit group entry matches
- keep exact and case-insensitive explicit group matches higher precedence than wildcard fallback
- add changelog credit and TypeScript-safe test assertions
## Verification
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini
Co-authored-by: Wayne Pika <262095977+WaynePika@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Add an optional `header` parameter to `FeishuStreamingSession.start()`
so that streaming cards can display a colored title bar, matching the
appearance of non-streaming interactive cards.
The Card Kit API already supports `header` alongside `streaming_mode`,
but the current implementation omits it, producing headerless cards.
This change is fully backward-compatible: when `header` is not provided,
behavior is identical to before.
Closes#13267 (partial)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(feishu): pass proxy agent to WSClient for environments behind HTTPS proxy
The Lark SDK WSClient uses the `ws` library which does not automatically
respect https_proxy/HTTP_PROXY environment variables. This causes WebSocket
connection failures in proxy environments (e.g. WSL2 with a local proxy).
Detect proxy env vars and pass an HttpsProxyAgent to WSClient via the
existing `agent` constructor option.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(feishu): add generic type parameter to HttpsProxyAgent return type
Fix TS2314: `HttpsProxyAgent<Uri>` requires a type argument.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(feishu): wire ws proxy dependency and coverage
* chore(lockfile): resolve axios peer lock entry after rebase
---------
Co-authored-by: lirui <lirui@fxiaoke.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* fix(feishu): chunk large documents for write/append to avoid API 400 errors
The Feishu API limits documentBlockChildren.create to 50 blocks per
request and document.convert has content size limits for large markdown.
Previously, writeDoc and appendDoc would send the entire content in a
single API call, causing HTTP 400 errors for long documents.
This commit adds:
- splitMarkdownByHeadings(): splits markdown at # or ## headings
- chunkedConvertMarkdown(): converts each chunk independently
- chunkedInsertBlocks(): batches blocks into groups of ≤50
Both writeDoc and appendDoc now use the chunked helpers while
preserving backward compatibility for small documents. Image
processing correctly receives all inserted blocks across batches.
* fix(feishu): skip heading detection inside fenced code blocks
Addresses review feedback: splitMarkdownByHeadings() now tracks
fenced code blocks (``` or ~~~) and skips heading-based splitting
when inside one, preventing corruption of code block content.
* Feishu/Docx: add convert fallback chunking + tests
---------
Co-authored-by: lml2468 <lml2468@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>