Commit Graph
1482 Commits
Author SHA1 Message Date
viettranx 85ebbcfbd5 feat(tts): split DynamicParamForm into Basic and Advanced sections
Phase 3 of the TTS params/layout/agent-override plan.

Reads the `Group` field shipped in Phase 1 to partition each provider's
params into Basic (always visible) and Advanced (collapsed by default,
chevron toggle, count badge). Edge providers with no advanced params
hide the toggle entirely.

- `partitionSchema(schema)` splits params; unknown group values fall
  into Basic (forward-compat).
- `evaluateDependsOn` runs against the full shared form state, so
  cross-section dependencies (e.g. MiniMax `audio.bitrate` depends on
  basic-section `audio.format`) work without scoping.
- Count badge uses `t("tts.advanced.count", {count})` with i18next
  pluralization (`_one` for English).
- Desktop mirror with inline SVG chevron (no lucide-react dep).

i18n (web + desktop):
- New `tts.advanced.count` + `tts.advanced.count_one` keys in en/vi/zh.
- `tts.advanced.title` already present; verified.

TS contract: `ParamSchema.group?: string` added to web + desktop
`tts-capabilities.ts`. Plain TS interface — no Zod strict-validator
guard needed (verified via grep).

10 new unit tests cover partition order, unknown-group fallback, count
badge respecting DependsOn, and the cross-group MiniMax case.
189/189 web tests pass.
2026-04-20 06:28:17 +07:00
viettranx bd95022efe feat(tts): extract PortalVoicePicker and route Gemini through it
Phase 2 of the TTS params/layout/agent-override plan.

Web (`ui/web/src/components/voice-picker.tsx`):
- Extract `PortalVoicePicker` (search + VoiceRow + portal dropdown)
  from the `DynamicVoicePicker` inner body.
- Introduce `PortalVoice` shape + `mapCapVoiceToPortal` adapter so
  capability `VoiceOption` and `useVoices()` results both feed in.
- Dispatcher gains a Gemini route: static voices + provider==="gemini"
  → PortalVoicePicker with the static catalog. Other static providers
  (OpenAI) continue to use `StaticVoicePicker` (Radix Select).
- `DynamicVoicePicker` reduced to a thin fetch wrapper around
  `useVoices()` + `useRefreshVoices()`.

Desktop mirror (`ui/desktop/frontend/src/components/agents/voice-picker.tsx`):
- Same extraction + Gemini route. OpenAI keeps the Combobox-driven
  static path to preserve native UX.

Tests: 9 new cases covering Gemini→Portal dispatch, mapping shape,
absent `preview_url`/`labels`, plus the unchanged ElevenLabs/MiniMax/
OpenAI paths. 179/179 web tests pass.

No backend change. No new i18n keys (existing voice_picker.* reused).
2026-04-20 06:23:09 +07:00
viettranx ee57ec0fdc feat(tts): expand per-provider params + validation + golden fixtures
Phase 1 of the TTS params/layout/agent-override plan.

Capability schema:
- `ParamSchema.Group` field (`"basic"` default, `"advanced"` when set).
- Tag existing advanced params across openai/elevenlabs/minimax/gemini.

Gemini:
- Expose `temperature` (basic, 0.0–2.0, default 1.0, subtle-effect note),
  `seed`, `presencePenalty`, `frequencyPenalty` (advanced, experimental).
- Merge into `generationConfig` via explicit-presence resolvers so
  nil-params bodies stay byte-equivalent.

ElevenLabs:
- `output_format` enum (27 variants, default `mp3_44100_128`, advanced).
- `FormatMeta` lookup drives SynthResult MIME + extension.
- URL built via `net/url.Values.Encode()`; regex pre-validation for
  `output_format` (`^[a-z0-9_]+$`) and `language_code`
  (`^[a-z]{2,3}(-[A-Z]{2})?$`) blocks query-string injection.
- Telegram opus contract preserved: `opts.Format=="opus"` forces
  `audio/ogg; codecs=opus` regardless of user-set `output_format`.

MiniMax:
- `language_boost` (basic enum), `subtitle_enable` (basic bool),
  `pronunciation_dict` (advanced text, 8KB cap, wrapped as
  `{"tone":[...]}`). Parse failure logs length only + omits.

Validation:
- `audio.ValidateParams` enforces Min/Max/Enum + rejects unknown keys.
- Wired into `/v1/tts/synthesize` and `/v1/tts/config` write paths.
- `loadParamsBlob` capped at 16KB.
- i18n keys `MsgTtsParamOutOfRange`, `MsgTtsParamInvalidJSON`,
  `MsgTtsParamUnknownKey` added to en/vi/zh catalogs.

Tests:
- Golden `testdata/default_body.golden.json` per provider (gemini,
  elevenlabs, minimax, openai) checked in; invariant tests diff
  against file instead of self-referential capture.
- Round-trip tests for each new param + Telegram opus contract +
  URL-injection attempts.
2026-04-20 00:20:50 +07:00
viettranx 613b6e38d7 feat(tts): provider capabilities schema + Gemini TTS + dynamic param forms
Baseline groundwork for TTS expansion plan. Introduces a capabilities
system that the follow-up plan (phase-01..04) builds on.

Backend:
- `audio.ParamSchema` / `ProviderCapabilities` types + per-provider
  capabilities.go for edge, elevenlabs, minimax, openai, gemini.
- Gemini TTS provider (client, models, voices, wav encoder,
  multi-speaker via SpeakerVoice, audio-tag aware prompts).
- TTSOptions gains `Params map[string]any` (read-only) + `Speakers`.
- `VoiceListProvider` interface decouples HTTP voice handler from
  provider-specific impls.
- `nested_keys.go` resolves dot-separated param paths for nested
  provider bodies (voice_settings.stability etc.).
- Characterization + defaults-invariant tests per provider lock
  nil-params byte-equivalence before new params land.
- `/v1/tts/capabilities` HTTP endpoint + integration coverage.
- Dual-read tests (PG + SQLite) for tts_config.

Frontend (web + desktop):
- `DynamicParamForm` with depends-on evaluation, split into
  fields/logic modules. Slider primitive added.
- `AudioTagPicker` + `MultiSpeakerEditor` for Gemini.
- `voice-picker` refactored toward portal UX; combobox tightened.
- tts-capabilities API client + typed hooks.
- i18n catalogs (en/vi/zh) expanded; parity tests guard key drift.
- TTS page reorganised (voice-model-section removed; playground +
  credentials + provider-setup split cleanly).

Docs: codebase-summary, project-changelog, tts-provider-capabilities.
2026-04-20 00:05:19 +07:00
viettranx 7639a8c013 feat(pruning): enable context pruning by default with cache-ttl mode
- Change default mode from "off" to "cache-ttl" (enabled by default)
- Increase softTrimRatio from 0.25 to 0.3 to match TS defaults
- Update tests to reflect new default-enabled behavior
2026-04-19 21:00:13 +07:00
viettranx 5bc73d557c Merge remote-tracking branch 'origin/main' into dev 2026-04-19 16:20:30 +07:00
viettranx 7f7cf99da1 docs: final consistency sweep and normalization
- Fix prose body citation in 15-core-skills-system (removed :410 line ref)
- Normalize File Reference schema in 08-scheduling-cron and 10-tracing-observability to Module/Path/Purpose format
- Add missing hint line to 03-tools-system File Reference section
- Clean Go method-call symbols from 01-agent-loop Mermaid diagrams (Router + Resolver)
2026-04-19 16:12:57 +07:00
viettranx b6a7e7d754 docs: condense agent-loop and agent-teams body text
- 01-agent-loop: replace Go function names in sanitize step details and
  mermaid labels with behavioral descriptions; clean resolver resolved
  properties, router cache/run-tracking, and team workspace context
  variables of symbol references
- 11-agent-teams: compress mailbox 3-action table + Use Cases into
  3 narrative paragraphs; replace WorkspaceDir Go code block with
  prose description
2026-04-19 15:41:44 +07:00
viettranx cd1b3f267b docs: condense security, rpc, auth, evolution body text
- 09-security: compress 9-bullet edge-case list to 6-row Attack Surface/Mitigation table
- 20-api-keys-auth: replace auth.go:tokenMatch + router.go:handleConnect citations with behavior descriptions; remove RoleFromScopes() file ref
- 21-agent-evolution: remove resolver.go:346 code block + loop.go/loop_history.go line citations, replace with behavioral descriptions
- 19-websocket-rpc: already clean (no body-text .go:line refs)
2026-04-19 15:41:16 +07:00
viettranx 463d3d1c5d docs: replace file-reference lists with module tables (batch B) 2026-04-19 15:38:26 +07:00
viettranx 653df6fb52 docs: replace file-reference lists with module tables (batch A)
Replace trailing File Reference sections in 7 docs with 3-4 row
module-level tables. Column schema: Module | Path | Purpose.
Adds 1-line grep hint at end of each section. Also fixes one
body .go:line citation in 05-channels-messaging.md.
2026-04-19 15:37:59 +07:00
viettranx d7e2883926 docs: consolidate HTTP API into single v3-only reference
Merge docs/22-v3-http-endpoints.md into docs/18-http-api.md, adding
episodic memory, evolution metrics, vault, orchestration, and v3 flags
sections. Drop File Reference table in favour of 4-row module table.
Add missing live routes (TTS, backup/restore, CLI agent grants, cancel-
summon, system-prompt-preview, vault full CRUD, github-releases,
shell-deny-groups). Delete docs/22-v3-http-endpoints.md and update
cross-references in doc 21.
2026-04-19 15:33:24 +07:00
viettranx 985de60d93 docs: condense tools-system to concept-focused reference
Rewrote docs/03-tools-system.md from 987 lines to 519 lines.
Removed Go type/const definitions, function citations, 47-file
path listing, and static regex patterns. Replaced with concept
tables, complete tool inventory cross-checked against live
internal/tools/ registrations, and 3-row module table.
2026-04-19 15:32:39 +07:00
viettranx d6ae866321 docs: condense ws-team-events to base+delta format 2026-04-19 15:31:58 +07:00
viettranx adc2a1fafe docs: delete stale internal changelog, redundant with root CHANGELOG.md
Root CHANGELOG.md is the user-facing single source of truth. docs/17-changelog.md
was an internal verbose log duplicating git history.

Also removed docs/multi-tenant-architecture.bk.md (gitignored, pre-v3 backup
superseded by 23-multi-tenant-architecture.md).

Phase 01 of plans/260419-1344-docs-audit-and-condense.
2026-04-19 15:21:01 +07:00
Plateau Nguyenandviettranx 6d7389539a fix(vault): prevent vault_read id-namespace collision (#959)
* fix(vault): prevent vault_read id-namespace collision

vault_search was leaking KG/episodic entity ids into result sets even when
narrow `types` were requested, and callers then passed those ids to
vault_read which returned a generic "document not found". The cause was
threefold:

1. `types` filter was only applied to the vault fan-out; KG and episodic
   ran unconditionally. Now gated by shouldFanout(types, key).
2. vault_search output lacked a per-source tool hint. Each result now ends
   with " → use <tool>" naming the correct follow-up (vault_read,
   knowledge_graph_search, or memory_search).
3. vault_read miss returned "document not found" without checking whether
   the id belonged to a foreign namespace. It now probes KG then episodic
   and returns a namespace-specific redirect error. Stores are injected
   via SetKGStore/SetEpisodicStore, nil-safe, tenant-scoped.

Adds red→green characterization tests plus an end-to-end integration
scenario seeding a vault doc + KG entity with identical basenames.

* test(agent): bump none-mode prompt size budget to 3100

vault_read wiring (#948) added ~95 chars to read_file tool summary,
pushing none-mode prompt from <3000 to 3075 chars. Bump budget to
3100 (~775 tokens) to match the intentional addition.

* test(integration): ensure data_migrations table exists in reset helper

The reset helper runs before RunPendingHooks, but RunPendingHooks is
what normally creates data_migrations. On a fresh CI database the
DELETE fails with 'relation does not exist'. Create the table
defensively so reset works regardless of execution order.

* refactor(vault): per-source id fields + wire episodic into search

Align vault_search output fields with downstream tool input params:
doc_id (vault_read), entity_id (knowledge_graph_search), episodic_id
(memory_expand). Prevents LLMs from pattern-matching a generic `id:`
and misrouting a foreign-namespace uuid into vault_read. Fallback
redirect in vault_read now quotes id + names the correct param so
the LLM can self-correct in one turn.

Also wire stores.Episodic into VaultSearchService (stale comment
claimed pending-impl; PGEpisodicStore has existed and been in use
since v3). Unifies search fan-out with vault_read namespace probe.

---------

Co-authored-by: viettranx <viettranx@gmail.com>
2026-04-19 15:00:07 +07:00
Viet Tran 7b83e890aa feat(ui): a11y + toggle + viewport-close for voice picker (#964)
- aria-haspopup/aria-expanded/aria-controls on trigger; role="listbox"
  + aria-label on dropdown; role="option" + aria-selected on rows. Listbox
  id from useId() so multiple pickers don't collide.
- Clicking the trigger while open now closes it (toggle), not a no-op.
- Close on window scroll/resize: the fixed-position dropdown doesn't
  reflow with the viewport, so pinning it in the wrong place is worse
  than closing.
2026-04-19 14:29:20 +07:00
David Mizrahi 597e59ac04 fix(tts): make ElevenLabs voice picker selectable with portaled dropdown (#960) 2026-04-19 14:25:59 +07:00
Viet Tran fe90f6fed9 fix(tools): clean up Windows exec merge leftovers + expand credentialed env (#963)
- shell.go: remove commented-out git conflict markers; restore load-bearing
  rationale for plain exec.Command vs CommandContext (process-group kill).
- credentialed_exec.go: pass through SYSTEMROOT, TEMP, APPDATA and other
  Windows runtime vars that native CLIs (gh, az, aws, npm) require. Missing
  SYSTEMROOT breaks networking/registry APIs in most Win32 programs. Switch
  to USERNAME (Windows) vs USER (POSIX) per platform convention.
2026-04-19 14:23:26 +07:00
steelstring 2bc8e909f1 Fix/desktop lite edition cmd exec failure (#961)
* This fixes a data integrity issue affecting fresh installations of Desktop Lite and any environment where agent.other_config and serveral columns are not pre-initialized. This will stop Desktop Lite edtion from saving any agent setting.

The root cause is missing default initialization of JSON field in update flow, leading to NOT NULL violations in both SQLite and Postgres.

Fix ensures consistent behavior across Lite and Standard deployments.

* Fix: Destop Lite Windows edition exec failure
The executing of local commands use sh wrongly.
2026-04-19 14:20:21 +07:00
viettranx b967055560 feat(channels): shared WriterLabel helper with prompt-injection sanitization 2026-04-18 22:49:51 +07:00
viettranx 4bc7ca1320 fix(store): tenant-scope GetContactsBySenderIDs to prevent cross-tenant leak 2026-04-18 22:49:48 +07:00
viettranx 9333fee5b6 feat(agent): add affirmative writer hint + sender identity to group permissions prompt 2026-04-18 22:43:01 +07:00
viettranx 6e935b3314 fix(agent): fallback 'User <id>' in group writer prompt when metadata empty 2026-04-18 22:38:16 +07:00
viettranx d4a08c818f fix(agent): broaden NO_REPLY detection to cover decorative variants
Models occasionally emit NO_REPLY with trailing underscores, quotes,
markdown emphasis, or short explanations (e.g. `NO_REPLY_`,
`**NO_REPLY**`, `NO_REPLY: offline`). Previous word-boundary check
treated `_` as a word char so `NO_REPLY_` slipped through and was
delivered to end users.

Rewrite IsSilentReply to strip decorative wrappers, then prefix-match
case-insensitively. Token is only rejected when glued to another
word character (e.g. `NO_REPLYING`). Intentional divergence from
upstream #19537 — documented in the doc comment.
2026-04-18 21:46:26 +07:00
viettranx 59a8c25349 revert(http): remove admin backfill endpoint — lazy heal on /writers is enough 2026-04-18 21:43:19 +07:00
viettranx 78e2882335 feat(ui): unknown writer label fallback + i18n (en/vi/zh) 2026-04-18 21:35:54 +07:00
viettranx f1c1a466f0 feat(http): admin backfill endpoint for legacy writer metadata 2026-04-18 21:35:50 +07:00
viettranx d9c453cd49 feat(telegram): lazy self-heal writer metadata on /writers + fallback label 2026-04-18 21:35:48 +07:00
viettranx e66badb803 fix(permissions): auto-enrich file_writer metadata on grant from Web UI 2026-04-18 21:35:46 +07:00
viettranx cadca23cab feat(channels): add MemberResolver interface + Telegram getChatMember impl 2026-04-18 21:35:44 +07:00
viettranx 9f68f3ee0d feat(pancake): auto-react allow/deny scope filter + UI toggle
Extends #919 with per-channel scope control for Facebook auto-react.

- AutoReactOptions pointer struct (allow/deny post & user IDs)
- filterAutoReact + containsString helpers; deny overrides allow
- UI surfaces features.auto_react toggle + 4 tags fields
- Nil options = react-all (backward compatible)
2026-04-18 17:55:11 +07:00
viettranx 2abb317e02 docs: update tools system and changelog for web_search refactor
Update docs/03-tools-system.md to document:
- Per-tenant web_search provider chain resolution
- Tenant settings schema (provider_order, per-provider disable)
- Cache TTL and event-driven invalidation
- Config secrets lookup for API keys

Update docs/17-changelog.md with web_search tenant-scoping feature.
2026-04-18 17:47:39 +07:00
viettranx 18e48cd0fc test(integration): add web_search tenant isolation and migration hook tests
Add comprehensive integration tests:
- web_search_tenant_isolation_test: verify per-tenant provider chains, cache
  hit/miss behavior, event-driven invalidation
- web_search_migrate_hook_test: verify migration of inline keys from
  config.json5 and builtin_tool_tenant_configs.settings to config_secrets

Tests cover multi-tenant scenarios, cache expiry, concurrent execution.
2026-04-18 17:47:36 +07:00
viettranx 96b75f0413 refactor(ui): remove web_search config section
Remove ToolsWebSection from config page and clean up i18n keys for web
search provider configuration (duckduckgo, exa, tavily, brave). Web search
now configured entirely via builtin_tool_tenant_configs REST API.

Delete tools-web-section.tsx component and update locale catalogs (en/vi/zh).
2026-04-18 17:47:33 +07:00
viettranx ff1ab33fbc refactor(gateway): wire web_search with tenant-scoped stores
Move web_search tool registration from gateway_setup (where it received
hardcoded config) into wireExtraTools with access to ConfigSecretsStore
and MessageBus for runtime per-tenant resolution.

Update gateway_setup.go to remove web_search-specific config loading.
2026-04-18 17:47:29 +07:00
viettranx 1af7c2bb2a refactor(config): remove web_search from static config
Remove Tools.Web and related WebToolsConfig, DuckDuckGoConfig from config
struct and Default(). Web search providers now resolved dynamically from
config_secrets (tenant-scoped) and builtin_tool_tenant_configs instead of
hardcoded config.json5.

Update config_load_test to match removed config paths. Separate config_secrets
and config_channels loading to clarify schema evolution.
2026-04-18 17:47:25 +07:00
viettranx 31a6d1a60c refactor(tools): convert web_search to tenant-scoped provider resolution
Refactor WebSearchTool constructor to accept ConfigSecretsStore and
MessageBus instead of hardcoded WebSearchConfig. Implement runtime
per-tenant provider chain resolution via tenantChainCache.

Constructor now:
- Subscribes to cache invalidation events on config_secrets changes
- Returns nil-safe tool instance (no early return on empty providers)
- Passes secrets store and bus for dynamic resolution per Execute() call

Add resolve unit tests; delete legacy config_test.go (functionality
subsumed by cache tests and integration tests).
2026-04-18 17:47:21 +07:00
viettranx d7fae64974 feat(tools): add per-tenant web_search provider chain cache
Implement tenantChainCache with 60-second TTL to amortize DB reads for
per-tenant provider chain resolution. Cache is TTL-based with RWMutex
protection and event-driven invalidation on config_secrets changes.

Chain resolution rules:
- Parse tenant settings from builtin_tool_tenant_configs
- Apply NormalizeWebSearchProviderOrder for user-specified order
- Append DDG last (force-enabled, no API key required)
- Skip disabled providers or providers without API keys

Include comprehensive unit tests for cache hit/miss, expiry, and
concurrent access patterns.
2026-04-18 17:47:17 +07:00
viettranx 7a8f4c6446 feat(upgrade): add data migration hook for web_search inline API keys
Implement 055_web_search_legacy_keys_to_config_secrets migration hook that:
- Extracts inline API keys from config.json5 (tools.web.{exa,tavily,brave})
- Migrates inline api_key fields from builtin_tool_tenant_configs settings JSON
- Inserts all keys into config_secrets under appropriate tenant scope
- Strips inline fields from settings blobs to prevent re-migration

Hook is idempotent, tracked in data_migrations table, PostgreSQL-only.
2026-04-18 17:47:13 +07:00
viettranx afee8b4682 test(tools,agent): cover cross-target guard scenarios
Guard unit coverage:
- MessageTargetEnforced helper table (10 session shapes)
- TestMessageToolCrossTargetGuard — 9 scenarios: DM/group same-target
  pass, cross-target block, forward opt-in pass with notice; cron,
  heartbeat, subagent, team FREE
- TestMessageToolCrossTargetGuard_TraceReplay — anchors the 019d9fcf
  production trace against regression
- NoticeFallbackSender — msgBus-less deployment still gets the audit
  breadcrumb via t.sender
- NoNoticeOnSendFailure — failed forward does not post a phantom notice

System prompt coverage:
- <current_reply_target> emitted for direct + group, omitted when
  ChatID is empty

Pre-existing TestSelfSendGuard/text_to_different_chat_allowed updated
to pass forward=true — the test now correctly isolates the self-send
guard from the new cross-target guard.
2026-04-18 17:45:28 +07:00
viettranx 3715d260a5 feat(agent): inject <current_reply_target> into system prompt
Give the LLM an unambiguous reference for the currently bound chat so
it can compare against any target it plans to pass. Block lists channel,
chat_id and kind (direct/group) with a one-line policy: omit target to
reply here, only set target when forwarding per explicit user request.

Threads ChatID through SystemPromptConfig, IdentityData,
BridgePromptBuilder, and buildMessages so the pipeline populates the
block from pipeline.RunInput.ChatID.
2026-04-18 17:45:14 +07:00
viettranx 4658014ac8 feat(tools): fail-closed cross-target guard in message tool
Production trace 019d9fcf: agent in DM session called
message(target="-1003787954683:topic:1") and leaked a user's image to
the wrong Telegram group topic. No guardrail caught it.

Add explicit forward opt-in: in ENFORCED sessions, cross-target sends
require forward=true + non-empty forward_reason. Otherwise blocked with
a self-explanatory error that tells the LLM how to retry correctly.

Successful forwards emit slog.Warn("message.cross_target_forward") and
post a user-facing notice back to the origin chat via noticeOnSuccess
wrapper — notice only fires when the send itself succeeds, so failed
forwards no longer announce phantom deliveries.

Schema gains forward and forward_reason fields; description explains
when to set target vs omit.
2026-04-18 17:45:08 +07:00
viettranx 3fa8a02d4b feat(tools): add MessageTargetEnforced session guard helper
Classifies session kinds into FREE (cron, heartbeat, subagent, team) vs
ENFORCED (DM, group, ws, default, unknown, empty). FREE kinds compose
targets per job spec; everything else must match the ctx-bound origin.

postCrossTargetNotice posts the audit breadcrumb back to the origin
chat when an explicit forward succeeds: msgBus preferred, falls back to
direct sender, else slog.Warn so the skip is traceable.
2026-04-18 17:44:57 +07:00
viettranx 5b2d856896 feat(i18n): add message cross-target forwarded key
User-facing notice posted back to origin chat when the agent forwards a
message to a different target per explicit user request. Translations
added for en/vi/zh.
2026-04-18 17:44:50 +07:00
viettranx feca0a25e4 fix(docker): swap browser sidecar to chromedp/headless-shell
zenika/alpine-chrome is stuck at v124 — upstream stopped publishing
newer tags — and v124 with default headless mode doesn't support
--load-extension, so users couldn't load Chrome extensions during
automation.

Switch to chromedp/headless-shell:latest (Chrome 147+ at time of
commit, auto-tracks Chrome for Testing) and add --headless=new so
extension loading works. Rod connects to the same raw-CDP
ws://chrome:9222 endpoint — no client code change.

The image's entrypoint already configures the debugging port and
fronts it with socat, so our explicit --remote-debugging-*
flags have to be removed (otherwise Chrome fails with
"bind() failed: Address already in use").

Healthcheck switched from wget (absent in the minimal image) to a
bash /dev/tcp probe (bash is present and sufficient for a liveness
check on port 9222).

Verified: image pulls, Chrome starts, /json/version returns, health
probe succeeds.

Closes #722
2026-04-18 16:55:08 +07:00
viettranx 935b58b7a3 fix(migrate): RFC 8089-compliant file:// URL for Windows paths
golang-migrate's file source driver parsed "file://F:\\project\\..."
with "F" as host and ":\\..." as port → "invalid port" error, so
upgrade/migrate commands crashed on Windows before applying any SQL.

Format the source URL per RFC 8089: forward-slash separators and a
leading "/" before the drive letter → "file:///F:/project/...".
POSIX paths pass through unchanged (leading "/" already present).

Shape-driven, not OS-driven: no runtime.GOOS branch needed, which
also lets the unit test cover both shapes on a single runner.

Closes #872
2026-04-18 16:55:08 +07:00
viettranx 4988f57a9c fix(agent): revert path-normalizing dedup and symlink-resolved storage
Two pieces of the review-hardening turned out to be risky in production:

1. deduplicateMedia used filepath.Abs on each MediaResult.Path to
   normalize the dedup key. filepath.Abs depends on the process CWD,
   which varies across deployment environments. In containers with a
   non-standard CWD, relative paths from parseMediaResult were being
   rewritten into keys that could collide with or differ from
   absolute paths from tools — leading to legitimate media entries
   being silently eaten.

2. extractMediaFromContent overwrote the stored path with the
   EvalSymlinks-resolved path. When the workspace is a bind-mount or
   contains ancestor symlinks, the stored path diverged from what the
   tool originally wrote and queued into rs.mediaResults, so dedup
   against tool-origin entries failed and the same file could be
   delivered twice (or fail altogether if the resolved path wasn't
   reachable from the downstream reader's FS view).

Revert both to exact-string behavior. Keep EvalSymlinks for the
containment check only — the symlink-escape P0 remains closed, but
the stored path now matches what the tool wrote.
2026-04-18 16:19:23 +07:00
viettranx e03a11031f fix(team): propagate origin_sender_id for deferred task dispatches
When tasks are dispatched via ticker (deferred/unblocked), the context
has no real sender — only synthetic "ticker:system". This caused
workers to fail file writes in group chats with "permission denied:
system context cannot write files".

- team_tool_dispatch.go: fallback to task.Metadata["origin_sender_id"]
  when context sender is empty or synthetic
- task_ticker.go: propagate origin_sender_id and origin_role from task
  metadata when building ticker dispatch message

Fixes #915 deferred dispatch flow.
2026-04-18 15:53:40 +07:00
viettranx bfedf8e1d4 fix(telegram): bump timeout for media uploads to 3 min
Photo/video/audio/document uploads were hitting "context deadline
exceeded" mid-flight on slow or mobile networks. Two layers capped
the budget at 60s:

  - sendOverallTimeout (wraps the whole retry sequence)
  - http.Client.Timeout (caps a single HTTP request)

Both are fine for text sends but too tight for multi-MB uploads
(sketchnote images, PDFs, long voice clips).

Split the retry budget by method: text sends keep 60s, media sends
(sendPhoto/Video/Audio/Voice/Document) get a new sendMediaOverallTimeout
of 3 min. Bump the shared http.Client.Timeout to 3 min so a single
upload attempt can actually consume that budget — getUpdates long-poll
is 25s, so the extra headroom doesn't affect polling.

Closes #628
2026-04-18 15:53:25 +07:00