The agent engine derives every tool's execution ctx from a blanket 60s
budget (toolExecutionTimeout), so MCP calls on services configured with a
longer advanced_config.timeout were silently cancelled mid-flight even
though the MCP transport layers honor that value (internal/mcp/client.go,
manager.go). The configuration was partially applied without any signal.
Re-derive the CallTool window from ApprovalCtx (the round-level parent
without the per-tool deadline) using the service timeout when it extends
the engine window. Widening-only by design: services without an explicit
longer timeout keep today's behavior, and shorter values stay enforced
where they already apply (HTTP transport timeout).
Fixes#3135
* feat(sandbox): add safe live file management to session sandboxes
* fix(sandbox): keep live files from waking paused sandboxes
Serialize directory entries as "directory", refuse Files-tab Connect on paused runtimes, and cap list/delete trees so oversized workspaces fail closed.
---------
Co-authored-by: wizardchen <wizardchen@tencent.com>
* feat(sandbox): use a classic colored user@host prompt in the PTY
Cube, Docker, and E2B now share user@host:last-dir with WeKnora brand green, overriding E2B's default `\w $ ` prompt.
* fix(sandbox): redraw the PTY prompt after connect instead of waiting for Enter
Reattach does not replay bash's already-printed prompt, and FitAddon can clear the first paint. Fit before flushing output, then Ctrl-L if the screen is still empty.
* feat(sandbox): add Debian-style ll/la/l aliases in the interactive PTY
Interactive bash now gets colored ls/grep shortcuts; non-interactive skill scripts still see the real commands.
* fix(sandbox): restore conventional ls colors so directories stay blue
Map ANSI blue/cyan back to Tango colors so directories and executables are distinguishable; keep brand green only for user@host.
* fix(sandbox): correct runtime env-var hint to this config and create time
The copy claimed workspace-wide injection. Scope it to sandboxes created from this config, point per-run secrets to the Sandbox secrets page, and keep the hint off the add-button row so Chinese no longer wraps mid-word.
* feat(sandbox): attach a running session sandbox when the terminal opens
Opening the panel used to always show the start overlay. Lookup first so a running sandbox reconnects, while paused or missing sandboxes still wait for an explicit click and Connect cannot silently resume.
* fix(sandbox): do not inject PTY input or auto-connect a non-running sandbox
Empty-screen reconnect now SIGWINCHes so a reattached busy PTY is not fed
Ctrl-L. Lookup-only opens require a click unless List confirms running, and
closing the panel drops the terminal mount so a Files-tab reopen cannot attach.
---------
Co-authored-by: hailongzhao <hailongzhao@tencent.com>
Co-authored-by: wizardchen <wizardchen@tencent.com>
* feat(stream): persist live-run markers and per-run steer queues
Mid-run sends need a cross-replica pointer to the generating
assistant and a control-plane list that never hits the SSE stream.
* feat(agent): inject queued user messages at ReAct round boundaries
The engine drains the steer sink after each round, persists accepted
rows, and delays Done until after a loop-end inject so the turn can continue.
* feat(session): expose /steer and hand leftover messages to a follow-up turn
Owner-scoped steer routes queue or inject into the live run, single-flight
AgentQA via the live-run marker, and history replay keeps multiple user rows
inside one turn.
* feat(frontend): queue mid-run messages while an agent turn streams
Stop and Send stay visible together, the overlay follows server delivery,
and history splits the transcript around injected user bubbles.
* fix(frontend): type pending and client_id on SteerQueueItem
vue-tsc failed because the overlay sets those fields while POST /steer is in flight.
* fix(session): harden steer persist, handoff, and new_run
Keep SetLiveRun ahead of SSE and roll back orphan turns, make inject
persist idempotent, append-then-delete on rebind, cap overrun rounds,
and stop aborting a live stream when /steer returns new_run.
* fix(session): satisfy pre-push lint on steer paths
Wrap long lines, check discarded Error/Emit results, and keep
context as the first argument so the fork push hook can pass.
---------
Co-authored-by: hailongzhao <hailongzhao@tencent.com>
Co-authored-by: wizardchen <wizardchen@tencent.com>
BREAKING CHANGE: Updates that explicitly include usage_instructions must supply nonblank text of at most 16000 characters. Omitting the field preserves existing instructions; legacy descriptions remain readable.
* feat(mcp)!: persist tool catalogs and add guided configuration
* fix(mcp): write caller-owned catalogs and require a real tool sync
OAuth viewers can now persist their own directory, and the setup wizard no longer treats an unsynced service as complete. Missing catalogs auto-fetch on the tools step; already-used MCP functions are restored across engine turns.
* fix(mcp): skip redundant list_servers and tighten catalog UI
When the advertised server directory already fits in discover_mcp_tools, models should list_tools or describe directly. Chat and settings now show server names, collapsed docs, and initialize debug metadata for empty optional fields.
* fix(mcp): surface catalog summaries and polish tool detail UI
List responses now include a saved catalog count without live MCP calls, so settings cards and @ mentions can show sync state. Tool details open in a popup instead of expanding the row.
* fix(mcp): drop incomplete Anthropic calls and gate shared catalog refresh
Unclosed streamed tool_use was emitted as {} and executed. list_tools refresh=true now re-lists upstream instead of returning the saved snapshot, and only Admin (or MCP-capable API keys) can overwrite tenant-wide static-auth catalogs.
* fix(mcp): wrap client refresh path to satisfy lll
The metadata refresh URL made doRequest exceed the 120-character limit in the client module CI lint.
* docs: add English and bilingual product tours to READMEs
* docs: center product tour cover buttons and language labels
* docs: highlight skills memory and integrations on tour covers
* docs: stabilize product screenshots in narrated tours
* docs: host HD product tours as GitHub attachments
* docs: embed product tours inline and simplify captions
* fix(graph): feed OCR chunks to graph extraction instead of image-link placeholders
KnowledgePostProcessService.Handle gathered "text-like" chunks through
chunkService.ListChunksByKnowledgeID, which is text-only by design, so the
image_ocr / image_caption chunks written by multimodal parsing were never
returned. For a scanned PDF the text chunks contain nothing but markdown
image links, so graph extraction ran on empty input: most calls failed
with "unexpected end of JSON input" and the rest echoed the few-shot
example from the extract config into the graph.
- Fetch the exact chunk types the post-process enriches via
chunkRepo.ListChunksByKnowledgeIDAndTypes (text, image_ocr,
image_caption); the service now receives interfaces.ChunkRepository
through dig like knowledgeService already does
- Build a dedicated graphChunks list for graph extraction: skip
image_caption chunks (they duplicate the OCR text) and chunks that
contain only image links (chunkHasExtractableText)
- graphChunkCount, the enqueue loop and the log line use graphChunks;
summary, wiki, auto-tag and question generation keep using textChunks
- Tests: fake ChunkRepository in the wiki-enqueue test, table test for
chunkHasExtractableText
* fix(graph): skip figure OCR and harden empty-chunk detection
Reuse the CommonMark image scanner plus extractRealText so titles with
parentheses and <image> wrappers are not treated as prose. Only enqueue
OCR for graph extraction when the parent text chunk has no extractable
text, and skip link-only chunks for question generation as well.
* style(graph): wrap long post-process extract log line
---------
Co-authored-by: wizardchen <wizardchen@tencent.com>
* fix(mcp): require schema discovery before calls and render tool results
* fix(mcp): keep proxy identity until describe and localize embed results
Listing still cached the target, so skipped describe showed as an mcp_* failure instead of the proxy error. Embed chat also lacked the new MCP strings.
* feat(sandbox): stream interactive PTYs from Cube and E2B
Give session sandboxes a provider-neutral terminal capability so a later
WebSocket layer can attach without knowing Cube vs E2B. Long-lived
streams use a timeout-free client; idle terminals keep the sandbox alive
by refreshing TTL, and a workspace can set how long to wait before
disconnect.
* feat(session): bridge browser terminals to the session sandbox
Mint a short-lived ticket over a normal authenticated POST so the browser
WebSocket never puts the access JWT in the query string. After upgrade,
recheck the bound token, membership, and session ownership so logout or
revocation tears the PTY down. Provisioning stays opt-in: a GET
handshake only creates a sandbox when the client sends provision=1.
* feat(chat): add a sandbox terminal panel beside the conversation
Open a side panel with an xterm attached to the session sandbox. Starting
the terminal is an explicit click so merely opening the panel does not
create or resume a microVM; reconnects stay lookup-only until the user
confirms create-and-start. Persist the remote shell PID in
sessionStorage so a refresh reattaches instead of spawning an orphan.
* feat(sandbox): let workspaces set terminal idle disconnect
Expose the idle-disconnect duration in the sandbox config editor so an
operator can close unused terminals and let the provider TTL pause the
sandbox, instead of leaving PTYs open for the built-in 15 minutes.
* feat(chat): show generated files in the sandbox side panel
Move the per-message artifact list into the first tab of the right-hand panel so generated files stay in view with the terminal. Keep the old drawer only for embedded chat.
* fix(sandbox-terminal): keep tickets out of logs and live PTYs across refresh
Gin was copying the handshake JWT into response_body logs, silent access-token
rotation dropped open terminals as AUTH_REVOKED, and first-use provisioning
missed shared agents. Local echo now skips password prompts.
---------
Co-authored-by: hailongzhao <hailongzhao@tencent.com>
Co-authored-by: wizardchen <wizardchen@tencent.com>
* refactor(resource-access): unify scoped authorization and transfer recovery
* fix(resource-access): close reparse and transfer review gaps
* fix: enforce explicit file bindings and recover move cleanup
* fix: align mutation admission and cleanup scope checks
The agent editor's dependency chain resolves storage engine info through
editorResources.ensureStorageEngine, which awaited the admin-only
tenants/kv/storage-engine-config call and the Viewer-safe status call in
one Promise.all. A Contributor opening the editor got 403 on the config
call (it carries integration secrets), which rejected the whole chain and
skipped every dropdown population — models, knowledge bases and web
search providers stayed empty, so Contributors could not create agents
at all (#2991).
Downgrade permission rejections on the config call to "no admin config"
(the only consumer of storageConfig already tolerates null and falls
back to the status list); every other failure stays fatal. The predicate
lives in a dependency-free module so it is unit-testable under
tsx --test like the other store helpers.
* fix(agent): improve web search and persistent page reading
* fix(agent): make web fetch snapshots waitable and retryable
Keep in-flight page fetches independent of a caller's short deadline so
content=true and continuations cannot cancel a shared download or miss a
snapshot that is still loading. Mark snapshot_expired as retryable, fetch
only the first three search hits in parallel, and stop forcing Brave's
country to US.
Second pass over the strings touched by the proofreading commit,
reverting over-corrections that read worse than the original:
- Progress messages go back to the 〜しています… sentence form; the
clipped 〜中… labels were unnatural in context
- Keep established katakana terms (プログレッシブエージェンティックRAG,
エージェンティックRAG, Wikiレビュー, ネガティブ例, 生の出力) instead
of kanji paraphrases; keep クエリ in the embed widget
- Remove commas placed inside noun phrases (〜のための、より〜) and fix
stacked-modifier sentences (共有解除の確認文, 権限不足の説明文,
並列実行数の説明)
- Restore sentence-form placeholders/toasts where the noun-only form
sounded cut off (モデルに直接質問できます, お待ちください…)
* feat(web-search): add Bocha AI Search provider
Add a Bocha (bochaai.com) web search provider following the Metaso
adapter pattern: hardcoded official endpoint (SSRF-safe), Bearer auth,
4MB response cap, error-detail extraction, and RFC3339 date parsing
(datePublished preferred, dateLastCrawled fallback).
Tenant-facing config fields:
- freshness: noLimit/oneDay/oneWeek/oneMonth/oneYear (default noLimit)
- summary: request long text summaries, preferred as snippets (default on)
Wiring: registry factory in container, provider type enum + /types
metadata (dynamic form fields), frontend provider union type, and
swagger docs enum entries.
* fix(web-search): match Bocha's real wire format for codes and error messages
Live endpoint probing showed errors return {"message": .., "code": "401"}
(string code, message field) instead of the documented {msg, code:int}.
Parse the code tolerantly (string or number) and extract the detail from
message/msg so auth and rate-limit failures surface their reason.
* fix(web-search): allow Bocha in provider CRUD validation
In-service integration testing caught a fourth wiring point the adapter,
registry, /types metadata and frontend covered but CRUD did not: the
service-layer whitelist (isValidProviderType) and parameter validation
switches rejected provider type 'bocha', so providers could be tested
via POST /web-search-providers/test but never created or updated.
Add Bocha to both switches (delegating to ValidateBochaParameters like
Metaso/Zhipu) with service-level test coverage.
* fix(web-search): correct Bocha legacy timestamp timezone
---------
Co-authored-by: vicfei <vicfei@users.noreply.github.com>
Co-authored-by: wizardchen <wizardchen@tencent.com>
StringArray is stored as a BLOB on SQLite, so the TEXT equality used by the directory listing never matched and every wiki folder listed empty on the SQLite build. Compare the text form there.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFSMrLZZ3oq1dKmJFAofz6
* fix(frontend): recover the chat stream from an expired access token
The SSE chat request runs on raw fetch, outside the axios instance, so the
401 refresh flow in utils/request.ts never applied to it. An expired token
left the user staring at an orphaned message bubble and a dead-end
"HTTP 401" toast, with no re-login prompt until some unrelated polling
request happened to hit 401 seconds later.
Extract the refresh into a shared, de-duplicated helper and have the stream
client use it: on a 401 handshake, refresh once and replay the request.
The replay cannot duplicate a message because auth rejects before the
handler runs, and nothing has been rendered yet. Embed visitors are
excluded since their token cannot be refreshed by the SPA.
* fix(frontend): keep a minted session after a post-refresh SSE 401
A second chat handshake 401 is not proof the session is gone — tenant
lookup also returns 401 — so wiping the just-refreshed tokens logged
users out unlike axios. Identify StreamAuthError by name, pin replay
to the original abort controller, and cover the shared refresh queue
with unit tests.
---------
Co-authored-by: wizardchen <wizardchen@tencent.com>
* feat(datasource): persist source document creation time and use content edit time for Feishu
Follow-up to 160c806b (Refs #3016), which started persisting the source
last-modified time as metadata.source_updated_at. Two gaps remained:
1. Creation time was never carried. FetchedItem gains CreatedAt and
ingestItem persists it as metadata.source_created_at (RFC3339, UTC)
with the same absent-means-unknown rule as source_updated_at.
2. The Feishu wiki connector filled FetchedItem.UpdatedAt from
NodeEditTime, which moves when a node is renamed or dragged in the
tree without any content change. The connector's own cursor already
prefers ObjEditTime for exactly this reason; use the same rule for the
persisted value (contentEditTime / contentCreateTime, node_* as
fallback), so source_updated_at reflects document edits.
Feishu Drive populates CreatedAt from created_time. GitLab stops
fabricating UpdatedAt = time.Now(): a fetch timestamp is not a document
timestamp, and an absent value is the documented "unknown" (the commit
time is not fetched today; leaving it unset is honest until it is).
Tests: ingestItem covers both keys present/absent; the wiki fetch test
asserts obj_* times win and node_* are the fallback.
* style(gitlab): break up the FetchedItem literal (lll)
---------
Co-authored-by: xiaoyaner0201 <>
* feat(ui): multi-stage Docker build without host npm
Build frontend assets inside the image with node:24-bookworm, then
copy into the existing digest-pinned nginx runtime so docker compose
and CI no longer require a host-side dist step.
* fix(ui): copy local xlsx package and raise Node heap in Docker build
npm ci needs packages/xlsx before install, and vite OOM'd under the
default Node heap during the multi-stage image build.
* fix(ui): native multi-arch frontend Docker build and layer cache
Keep Vite on BUILDPLATFORM so CI does not qemu-emulate Node, pin the
builder image, and move the commit ARG after npm ci so dependency
layers stay cacheable.
---------
Co-authored-by: wizardchen <wizardchen@tencent.com>
Follow-up to #3080. A full proofreading pass over the Japanese locale
against the en-US and zh-CN sources, fixing mistranslations, awkward
phrasing and inconsistent terminology:
- Meaning: "organization" contexts now consistently say 共有スペース
(not bare スペース); remove-from-space actions no longer read as
deleting the member/agent/KB; built-in model description says
"visible to" rather than "usable by" all workspaces; several
descriptions (parser features, xlsx header option, runtime capacity,
credentials invalidation) corrected against zh-CN
- Terminology: 改写 -> リライト, 机器人 -> ボット, 知识库 -> ナレッジベース
(not ナレッジ), ReRank -> リランク, サインイン -> ログイン,
制御プレーン -> コントロールプレーン, API-key capabilities -> 権限,
"Agentic RAG" kept in English, self-hosted -> セルフホスト
- Wording: fix broken particles/係り受け (JWTに署名, 〜に失敗), drop
unnatural potential forms (超えられません), align status chips
(〜済み), progress messages use the 〜中... form, placeholders follow
the English form, 未提供 -> なし, remove trailing ! on error toasts
- Conventions: keep ヘッダー / リンター with the long-vowel mark; remove
remaining Latin/Japanese spaces (チャンク#{index}, unix://を)
- Prompt template and agent preset labels get the same fixes
* feat(i18n): add Japanese (ja-JP) locale support
Add a complete Japanese UI locale for the admin frontend and the web
embed widget, and register it everywhere the supported-locale list is
enumerated.
- frontend/src/i18n/locales/ja-JP.ts: full bundle (5,600+ keys) aligned
with en-US/zh-CN key structure; verified by the locale key audit
- frontend/src/i18n/embed.ts: ja-JP block for the embed widget, plus
`ja*` normalisation in normalizeEmbedLocale
- Register ja-JP in i18n index, SUPPORTED_LOCALES, localeKeyAudit
bundles/order, TDesign locale map (ja_JP), login language picker,
general settings dropdown and embed default-locale options
- Add `language.jaJP` key to every locale so the settings dropdown can
use $t like its siblings
- Accept ja-JP as an embed channel default_locale on the backend and as
DEFAULT_LOCALE in the frontend docker entrypoint
- Extend locale-enumerating tests and update accepted-value lists in
.env.example, public/config.js, widget script and website-docs
* feat(i18n): add ja-JP names and descriptions for prompt templates
Add a ja-JP block to the i18n map of all 32 prompt templates
(system_prompt, context_template, rewrite, fallback, intent_prompts,
agent_system_prompt) so the template picker shows Japanese labels when
the UI locale is ja-JP instead of falling back to the Chinese names.
Prompt content is unchanged.
* feat(i18n): add ja-JP strings for the CLI integration page
main gained the CLI setup page (#3081) with 18 new integrations.cli.* /
integrations.tabs.cli keys; add their Japanese translations so the
locale key audit stays green after rebasing onto main.
* style(i18n): unify ja-JP spacing and long-vowel conventions
- Remove spaces between Latin tokens and adjacent Japanese text
(e.g. "MCP サービス" -> "MCPサービス"), matching the majority style
already used in the bundle
- Drop the trailing long-vowel mark on common technical katakana terms
(サーバ, ユーザ, フォルダ, プロバイダ, パラメータ, ヘッダ, クラスタ, ...)
so the whole locale follows one convention
* feat(i18n): add ja-JP labels for agent type presets
config/agent_type_presets.yaml carried default / zh-CN / zh-TW labels
only, so the agent editor showed English preset names in the Japanese
UI. Add a ja-JP label and description to all five presets.
* fix(chat): send one completion-token field per provider
Agent dual-wrote max_tokens and max_completion_tokens, which Volcengine Ark
rejects. Collapse them to one budget and pick the wire name the way Pi does.
Fixes#3014
* fix(chat): shrink max_tokens provider whitelist to documented hosts
Only DeepSeek, Zhipu, SiliconFlow, Moonshot, NVIDIA, and self-hosted
vLLM-class runtimes stay on the legacy field. Aliyun DashScope documents
max_completion_tokens for thinking models and must not be forced onto
max_tokens.
* fix(chat): send max_tokens for LKEAP completions
Tencent LKEAP documents max_tokens only. Keep Pi's default of
max_completion_tokens, and classify every AllProviders() host so a new
vendor cannot silently inherit the default.
A folder named 概念/实体/Concepts was stripped from category_path as model noise, so a page moved into it kept depth 0 and a root-level wiki_path and rendered at the root of the directory tree. Folder paths are authoritative and now map to category_path verbatim; the model-label cleaner still applies to pages without a folder.
Fixes#3053
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFSMrLZZ3oq1dKmJFAofz6
Eager remote images freeze large Markdown previews, but a scheme-based
allowlist also hid data:image URLs that document parsers emit. Keep lazy
loading on a preview-only sanitizer that matches DOMPurify's URI policy.
* fix(session): serve org-shared KB images through the message file proxy
The message-scoped file proxy only authorized resources whose tenant
matched the message's agent tenant, so replies produced by an agent of
the caller's own workspace over an org-shared knowledge base rendered
text but lost every image with 403 (#3022).
Fall back to the org-share relation for exactly that case: the
message's persisted retrieval references must contain the resource
handle (chunk evidence — an unrelated handle from the owner tenant
cannot ride along), the reference's knowledge base must belong to the
resource's tenant, and that KB must still be shared to the caller with
at least viewer permission. Messages written before agent_tenant_id
existed get the same fallback before the shared-agent relation is
demanded; the shared-agent path itself is unchanged.
* fix(session): prove shared-KB images from agent steps and image_info
Smart-reasoning turns never persist KnowledgeReferences, and OCR hits often
keep the resource handle only in image_info. Collect tool retrieval onto the
message and accept that evidence in the message-file proxy so org-shared KB
images stop 403ing.
* fix(session): keep shared-KB image evidence in the message-file proxy
Drop the agent-engine KnowledgeRefs collector so pre-push does not lint the
whole agent package. AgentSteps already persist knowledge_search hits, which
is enough for the proxy to authorize org-shared images.
---------
Co-authored-by: vicfei <vicfei@users.noreply.github.com>
Co-authored-by: wizardchen <wizardchen@tencent.com>
Wiki ingest makes several independent LLM calls per document and already
retries transient failures (408/429/5xx, timeouts, connection resets) with
exponential backoff, but some gateways report QPM/QPS throttling as HTTP
403 instead of 429 (e.g. DeepSeek MaaS: code 0x04030020, message
"调用频率(qpm)超限"). Those 403s were classified as permanent caller
errors and the wiki span failed immediately with EXTRACT_FAILED /
SUMMARY_FAILED, so a QPM burst aborted generation for the whole batch
(issue #2688).
isTransientLLMError now also matches "status 403" errors whose embedded
response body carries rate-limit wording (qpm/qps, rate limit, too many
requests, throttled, 调用频率/频率超限/请求过于频繁/繁忙, try again
later, slow down). Authorization-shaped 403s (invalid key, forbidden,
permission denied) and unrelated bodies stay permanent, so the change
cannot turn authz failures into retry loops.
Tests: 14-case table covering rate-limit 403 acceptance and
authorization-shaped 403 rejection, all existing isTransientLLMError
cases unchanged and passing.