Commit Graph

83 Commits

Author SHA1 Message Date
matevip
da332f2fd8 feat(goal): checklist UI — progress ring, hover checklist card, criteria SSE 2026-06-03 21:19:27 +08:00
matevip
9073c94de1 fix(ui): intercept generated-file downloads so failures never wedge the SPA (#243) 2026-06-01 20:21:21 +08:00
matevip
0ef735588a fix(wiki): 3 bugs caught by browser-driven chat e2e + 12-round trace 2026-05-28 10:55:30 +08:00
matevip
0e01b2b526 feat(wiki): chat-rendered wikilinks navigate via cross-KB lookup 2026-05-28 09:25:58 +08:00
matevip
66d3d90ea9 feat(wiki): slug-first wikilink resolution and safe DOM postprocess 2026-05-28 08:16:56 +08:00
matevip
5f571e86a2 feat(agent,ui): multi-level subagent delegation tree 2026-05-22 13:44:01 +08:00
matevip
8bd8a02cd0 feat(agent,ui): nested subagent timeline + always-on plan panel 2026-05-22 09:48:06 +08:00
matevip
34175ab94d fix(goal,ui): guard evaluating flag against the message_complete race 2026-05-21 22:27:25 +08:00
matevip
495a5c126d fix(goal,ui): wire evaluating halo, followup ↻ mark, dedupe system-line icon 2026-05-21 22:26:48 +08:00
matevip
c34e8290ac feat(goal,ui): inline set-goal prompt, terminal system-line, sidebar dot 2026-05-21 22:26:40 +08:00
matevip
9c5ad29d42 chore(llm): drop unused imports, add XIAOMI_MIMO cross-turn cache tests 2026-05-21 17:24:53 +08:00
matevip
c9e54e820f fix(goal,ui): live ring update after agent-triggered setGoal / addGoalCriterion 2026-05-21 14:43:59 +08:00
matevip
3a0595d5bd feat(goal,ui): Jobs-cut frontend for persistent goal 2026-05-21 14:43:28 +08:00
matevip
51e6542a5a feat(channel/qq): add scan-to-bind onboarding via QQ Open Platform Lite portal 2026-05-20 21:47:49 +08:00
matevip
d53d66abe3 feat(chat): per-conversation model selection (#150) 2026-05-18 16:27:27 +08:00
matevip
001cb1a2ed refactor(chat): extract conversation sidebar and shared UI primitives 2026-05-17 11:49:44 +08:00
matevip
82594878a0 refactor(ui): fold the live runtime view into the Employees page 2026-05-16 14:50:58 +08:00
matevip
0800c03cd3 fix(ui): preserve snowflake ID precision through form round-trips (#133) 2026-05-15 15:51:33 +08:00
matevip
e10dd9d0a0 refactor(ui): swap ElMessage for project-native mcToast, finish mcConfirm migration 2026-05-15 10:19:24 +08:00
matevip
777e2ecee2 feat(ui,router): gate routes and sidebar by workspace capability 2026-05-15 10:18:38 +08:00
matevip
aa27853712 feat(ui,workspace): consume backend access endpoint for capability state 2026-05-15 10:18:30 +08:00
matevip
eb6badeb61 feat(ui): surface pending approvals + stuck agents as sidebar badges 2026-05-15 10:17:30 +08:00
matevip
d1be02985c fix(agent): collapse model-predicted tool results that the actual tool replaced 2026-05-14 15:44:03 +08:00
matevip
0192a62f21 feat(ui/mcp): redesign MCP connections page with componentized layout 2026-05-14 14:09:44 +08:00
matevip
faf7f98358 feat(context): consume compact_status on the frontend; load history from latest boundary; backfill summaryId 2026-05-13 08:50:35 +08:00
matevip
f18626304e feat(agent,ui): recovery affordance card for non-transient LLM errors 2026-05-10 19:15:57 +08:00
matevip
97e2b12f71 feat(chat): show reply model attribution in assistant message bubbles 2026-05-09 16:41:18 +08:00
matevip
0cbe271f6f fix(ui/chat): mermaid streaming flicker + copy/download buttons 2026-05-08 17:09:22 +08:00
matevip
de89df6900 feat(ui/workflows): Monaco editor + schema validation + compile-error markers 2026-05-08 15:08:50 +08:00
matevip
dae74cf26c feat(channel,trigger): wire channel_message + content_match event sources 2026-05-08 15:07:11 +08:00
matevip
dac774f15e feat(workflow): trigger workspace isolation + step property editor + custom dialogs 2026-05-08 15:06:16 +08:00
matevip
f8499eefa7 feat(ui/workflows): vue-flow canvas + component dialogs + dark / mobile polish 2026-05-08 15:05:45 +08:00
matevip
32fe582b82 fix: avoid auto-scroll while reading history (#61) 2026-05-05 11:12:38 +08:00
matevip
f5d845ff31 feat(backstage): tool chip, triage sort, bento metadata tiles 2026-05-04 14:00:39 +08:00
matevip
7708e26851 feat(backstage): avatar status ring, hero focus panel, brand-tone time dial 2026-05-04 14:00:25 +08:00
matevip
7d02841d1d fix(chat): per-event SSE ids for safe reconnect dedup + queue race recovery 2026-05-03 19:57:06 +08:00
matevip
66f09a968a feat(chat-stream): streaming UX overhaul + multi-agent stability layer 2026-05-03 17:15:02 +08:00
matevip
5f125364e9 fix(skill): parseIconValue accepts site-absolute paths + restore ckjia-shopping nameZh/nameEn/tags 2026-05-03 17:14:43 +08:00
matevip
28c3b0e62f feat(model3d): Tencent Hunyuan 3D provider — Pro/Rapid action routing + <model-viewer> preview 2026-05-01 22:39:13 +08:00
matevip
e3ab06d57c feat(generative): unified async pipeline + live SSE delivery for music/video/image 2026-05-01 20:15:20 +08:00
matevip
5e6764ecf1 fix(skill): tool-gate whitelist + markdown link host normalization
When an agent had any skill bound, the runtime tool gate was silently
hiding @Tool beans that aren't declared in any skill manifest, even
though the global system prompts (SOUL.md / "Web Search Capability" /
"File Reading Guidelines") explicitly tell the LLM these tools are
available. Result: the model would call search / renderDocx / read_file
/ etc., hit "Tool not found", then either give up or fall back to
unhelpful behaviour (e.g. dumping markdown text instead of producing a
.docx download).

This commit:

- Adds universally-promised, agent-wide tools to SYSTEM_LEVEL_TOOLS so
  they bypass the manifest restriction: document/media generation
  (renderDocx*, image_generate, music_generate, video_generate),
  global capability tools the system prompt mentions (search,
  browser_use, read_file / write_file / edit_file /
  execute_shell_command, detect_file_type, extract_*_text,
  readMateClawDoc), skill discovery siblings (listSkillFiles,
  listAvailableSkills), and the delegate triplet (delegateToAgent,
  delegateParallel, listAvailableAgents).

- Fixes 5 entries in the prior whitelist whose names did not match
  any real @Tool bean and were therefore silently dead:
    read_workspace_file   -> read_workspace_memory_file
    write_workspace_file  -> write_workspace_memory_file
    list_workspace_files  -> list_workspace_memory_files
    delegate_agent        -> delegateToAgent
    datetime              -> getCurrentDate / getCurrentDateTime / getCurrentTime
  Also adds the missing edit_workspace_memory_file.

- In the chat markdown renderer, strips any hallucinated
  https?://<host> prefix from /api/v1/files/generated/<id> download
  links before building the <a href>. Multiple LLMs have been
  observed prepending bogus hosts when echoing tool-returned download
  URLs back to the user, breaking the click. One-line defensive
  normalization independent of which model is in use.

Verified end-to-end on a previously-broken agent: search / browser_use
/ execute_shell_command / renderDocx all dispatch correctly now and
the final markdown link is a clean same-origin path. 36 whitelist
entries cross-checked against real @Tool method names.
AgentBindingServiceTest green.
2026-05-01 12:24:25 +08:00
matevip
8ce7e7d26d feat(ui): agent integration of SkillIcon + SkillIconPicker 2026-05-01 10:58:15 +08:00
matevip
efb91796fb feat(skill): pixelarticons icon library + picker component 2026-05-01 09:52:12 +08:00
matevip
b4aef56c89 fix(channels): close QR loading dead-window for feishu and dingtalk register flows
Reported issue: click 'scan to create' -> button momentarily flickers
loading -> button re-enables but no QR shows up -> blank for 1-2 seconds
-> QR suddenly appears. Looks broken even though it works.

Root cause: loading.value flipped back to false the moment the begin HTTP
call returned (sessionId in hand), but the actual QR image only arrives on
the first status poll, which the existing code waited a full 2 seconds
for. Between begin completing and the first poll firing the UI was a
disabled button + nothing.

Three coordinated changes:
- useFeishuAppRegister and useDingTalkAppRegister: keep loading.value true
  through begin AND across the polls, only flip false when the QR image
  is actually populated (or a terminal failure status arrives). Also run
  an immediate first poll right after begin instead of waiting for the
  setInterval tick — usually the first poll already has the rendered QR
  for dingtalk, and pushes the feishu user roughly 2 seconds closer.
- ChannelEditModal: same-sized loading placeholder (min-height 240px,
  matching the QR card) that renders when loading is true and no QR is
  in hand. CSS spinner ring tinted with the channel brand color (feishu
  indigo, dingtalk blue) and a new
  channels.{feishu,dingtalk}Register.qrcodeLoading hint. The placeholder
  swaps to the real image with no layout shift.
- i18n: new qrcodeLoading key in zh-CN and en-US for both flows.

Net effect: click to spinner-visible is ~50ms; the user is never staring
at a frozen button-without-content again.
2026-04-28 11:14:14 +08:00
matevip
acf6eccb3a feat(dingtalk): one-click bot creation via OAuth device flow
Mirrors the feishu one-click flow: scan a QR with the DingTalk app,
approve, and the bot's client_id / client_secret get auto-filled instead
of forcing the user through the open-dev console. Saves about seven
manual steps per channel setup.

Backend
- Bump dingtalk-stream from 1.3.5 to 1.3.12. Diff against the classes we
  depend on (OpenDingTalkStreamClient, ChatbotMessage, MessageContent,
  GenericEventListener) is empty — pure point-release bumps, no API churn.
- New DingTalkAppRegistrationService: synchronously runs init + begin
  against /app/registration/{init,begin} on oapi.dingtalk.com to obtain
  the device_code and verification URL, then spawns a daemon worker that
  polls /app/registration/poll every 5s until SUCCESS / FAIL / EXPIRED is
  returned. Sessions evict after 7 minutes, worker has a 6-minute hard
  runtime cap, transient HTTP errors do not terminate the loop. Same
  shape as the feishu service, but written from scratch because the
  dingtalk-stream SDK doesn't wrap this OAuth device flow.
- Two new endpoints under /api/v1/channels/webhook:
  POST /dingtalk/register/begin returns session_id;
  GET  /dingtalk/register/status returns status + qrcode_img (data URI
  PNG, ZXing-encoded from the verification URL, matching the feishu and
  weixin flows). Status surface: waiting / confirmed / expired / denied.

Frontend
- channelApi.dingtalkRegisterBegin / dingtalkRegisterStatus.
- New useDingTalkAppRegister composable, structurally identical to
  useFeishuAppRegister minus the domain argument. Stops polling on
  terminal status, fires onConfirmed with {clientId, clientSecret}.
- ChannelEditModal: dingtalk-register-card rendered when channelType is
  dingtalk, scoped DingTalk blue (#1f79ff) to differentiate from feishu's
  indigo. onConfirmed writes channelConfig.client_id / client_secret so
  the existing form fields update reactively.
- i18n: channels.dingtalkRegister.* keys for title / hint / button states
  / scan / confirmed / expired / denied / startFailed.
2026-04-28 11:12:22 +08:00
matevip
a27898507c feat(feishu): one-click app creation via official SDK device-flow registration
Saves the user the entire 'go to the open platform -> create an enterprise
app -> copy App ID and Secret' detour. Click a button in the channel form,
scan the QR code, confirm authorization, credentials are auto-filled.

Backend
- Bump com.larksuite.oapi:oapi-sdk from 2.5.3 to 2.6.1, which adds the
  scene/registration package wrapping the device-code flow.
- New FeishuAppRegistrationService: each begin() creates a sessionId,
  spawns a worker thread, runs the SDK's blocking RegisterApp.register
  with onQRCode and onStatusChange wired into a per-session state machine
  (PENDING -> WAITING -> CONFIRMED / EXPIRED / DENIED / ERROR). The
  session caches the QR data URI so ZXing only encodes once per attempt.
  Sessions evict after 5 minutes so closed browsers don't leak the map.
- Two new webhook endpoints under /api/v1/channels/webhook/feishu:
  POST /register/begin returns session_id, GET /register/status returns
  status + qrcode_img (data URI base64 PNG, ZXing-encoded from the SDK's
  verification URL — the raw URL would render as a broken image, so the
  encoding step matches the WeCom flow).
- SDK detail caught the hard way: don't pass .domain() or .larkDomain().
  The SDK defaults are accounts.feishu.cn / accounts.larksuite.com (the
  registration endpoints). open.feishu.cn is the open-API endpoint, a
  completely different service. Passing the wrong one makes the SDK parse
  HTML as JSON and emit invalid_response.

Frontend
- channelApi: feishuRegisterBegin / feishuRegisterStatus.
- New useFeishuAppRegister composable: state machine that begins the
  session, polls status every 2s, prefers qrcode_img over qrcode_url for
  the <img> src, stops on terminal status, fires onConfirmed with
  {appId, appSecret}.
- ChannelEditModal: a new feishu-register-card above the wecom one. The
  composable's onConfirmed writes channelConfig.app_id / app_secret, so
  the existing form fields update reactively.
- i18n: channels.feishuRegister.* keys for title / hint / button states /
  scan / confirmed / expired / denied / error.
2026-04-28 11:11:36 +08:00
matevip
22894ac4b1 perf(channels): split Channels.vue, lazy-load modal, async locales, keep-alive route
- Extract create/edit modal into ChannelEditModal.vue (defineAsyncComponent),
  shrinking Channels.vue from 1438 to 370 lines and dropping ~30KB from the
  initial route chunk.
- Move side-effect logic into composables: useWeixinQrcodePoll (QR + 2s status
  poll, auto-cleanup) and useWecomBotAuth (lazy SDK script with module-level
  promise dedupe). Pure config-JSON helpers move to utils/channelConfigJson.ts.
- Switch i18n locales from static imports to dynamic import keyed by current
  locale; applyLocale becomes async to avoid first-render flicker.
- /channels route opts into keep-alive (meta.keepAlive=true). Channels.vue
  pauses status polling in onDeactivated and resumes in onActivated, with an
  isActive guard to prevent late-resolving timers from leaking after navigation.
- Initial load goes from serial 3-RTT to Promise.all + 4-card el-skeleton.
2026-04-28 11:08:36 +08:00
matevip
4e22b85557 fix(chat): surface SSE error in retry card and stop poll from wiping local-only failed turn
When SSE setup fails (e.g. workspace permission denied for shared channel
conversations opened from the web console), the failed turn is never
persisted on the backend. Two issues made the failure invisible to the user:

- The fallback errorInfo dropped data.message, so the inline retry card fell
  back to the generic "请求过程中遇到了意外问题" template instead of the
  actual reason. Carry rawMessage through, and lower the MessageBubble
  display threshold from >8 to >3 chars so short-but-informative messages
  (7-char Chinese / "Forbidden") aren't filtered out.

- The status-poll loop in useChat overwrote the local-only failed turn
  with the server's "no message" view, erasing the inline retry card.
  Skip the merge for turns that exist only locally and are in error state,
  so the user can still see the failure and retry.
2026-04-28 00:16:14 +08:00
matevip
e64752a830 fix(approval): unify tool-approval state machine across DB / message metadata / memory
- Reconcile approval status atomically: DB row, message metadata, in-memory store
- Approve and deny both flip the tool-call card + timeline segment to a terminal
  state on the gate message — no more orange spinner stuck after a decision
- Frontend hydrate matches by pendingId and reverse-converges to expired so a
  refresh after server-side timeout / consume clears the banner without restart
- Stop sweep, GC timeout, and JVM restart all close the loop with consistent
  state
- Remove the dead REST /approve endpoint + matching frontend client export so
  there is only one resolve path to maintain
2026-04-27 22:25:27 +08:00
matevip
03c8584910 fix(channels): resolve issue #19 — non-admin member errors on channel page
Three bugs surfaced when a non-admin workspace member opened the channel
admin page:

- vue-i18n "Invalid linked format" when '@' appeared in message strings
  without the linked-format escape. Replaced literal '@' with vue-i18n v9
  literal interpolation {'@'} in both zh-CN.ts and en-US.ts (6 strings:
  QQ guide step3, accessControl requireMention/Tooltip).

- 403 from WorkspaceAccessInterceptor was being treated as 401 by the
  axios interceptor and the chat SSE handler, clearing the token and
  redirecting to /login. Split the two:
    * 401 = authentication failure  -> handleAuthFailure (logout)
    * 403 = authorization failure   -> keep session, surface to caller
  Now a member who lacks workspace permission sees a toast instead of
  being silently logged out.

- Two backend exception sites threw with the default code=500 for what
  is semantically an auth/authz event, contradicting the codes returned
  elsewhere for the same business event:
    * AuthService.login() bad credentials  500 -> 401
    * WorkspaceService.requirePermission() 500 -> 403
  This aligns service-layer denials with SecurityConfig (401 for missing
  JWT) and WorkspaceAccessInterceptor (403 for permission denied), so
  the same business event always produces the same code.
2026-04-27 19:31:46 +08:00