npm run dev:docs / build:docs serve the design specs + live component demos (@rspress/plugin-preview) with the app's vite aliases and style.css mirrored into the rspack config; stubs/url-stub.ts shims Node's url module for the browser bundle. doc_build/ output gitignored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tool overwrites .coaligneignore with its own default template, so tracking it means the rewrite lands in everyone's tree — keep it per-developer alongside .coaligne/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The .coaligne/ dir holds per-developer project bindings that the tool regenerates (it re-binds and rewrites them on its own), so it should never have been tracked — untrack the two committed state files and ignore the dir.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update GalleryApp wiring and the Button/Color/ConfirmDialog/Feedback/Illustration/Modal/Responsive/Typography showcases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The old .theme-green steps sat at hue ~157 (teal) while --brand-500 is 142, so the main color jumped out of its own ramp. Light steps keep the old saturation/brightness envelope with the hue rotated onto the 500 family (-2/step, mirroring the blue lights); dark steps mirror the blue ramp's dark-side HSV geometry. --brand-500/main unchanged; surface-active-alt re-synced to --brand-50. Values updated in BRAND-THEME-HANDOFF.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The code-interpreter tool description said nothing about which Python packages
are available, so the model guessed — reaching for pdfminer.six to read PDFs (a
common default) which is NOT installed in the backend env, producing spurious
"No module named 'pdfminer'" output (it fell back to fitz, but the noise leaked
into the run).
Declare the actually-installed libraries (verified in the backend env: pandas,
numpy, matplotlib, openpyxl / XlsxWriter, python-docx, Pillow, reportlab, PyMuPDF)
and steer PDF reads to `fitz`, away from pdfminer / pdfplumber / PyPDF2. Also tell
the model not to `pip install` (shared, offline env). LocalExecutor only — e2b is
a different sandbox with a different image, not asserted here. Adds a
description-contract regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On an L3/L4 abort (tool-loop breaker / recursion ceiling), the partial-result
salvage falls back to `_last_assistant_text`, produced by
`_extract_last_message_text`. That helper returned `messages[-1]` WITHOUT
checking the role, so when a run was cut off mid tool-loop the trailing message
— typically a raw `bisheng_code_interpreter` ToolMessage such as
`{"exitcode":0,"log":"\nError: No module named 'pdfminer'\n=== PDF文本内容(fitz…"}`
— was surfaced verbatim after the apology preamble as "已完成的分析内容".
Walk backward to the last AIMessage carrying text instead; skip Tool/Human/
System messages. When the model never produced any text, return None so the
caller degrades to a friendly failure rather than dumping tool JSON. Split out
`_is_assistant_message` / `_message_text` helpers and cover with unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The task-mode report md preview (PreviewBody → shared react-markdown) had two
independent rendering failures:
1. Relative image refs `` never resolved — the renderer has no
relative→absolute rewrite, so the browser resolved them against the SPA route
and got the index.html fallback (200 text/html) → broken image. Fix: a custom
async <img> renderer (MarkdownImage) resolves relative refs against the session
file_list (matchArtifactByRelPath → file_url → resolveArtifactUrl presigned
URL). Enabled only when a resolver is injected, so chat bubbles are unchanged.
2. Empty raw-HTML placeholder boxes (`<div style=...></div>` comment/figure
scaffolding) leaked as literal text because rehype-raw is deliberately off
(XSS guard). Fix: stripEmptyHtmlPlaceholders removes only *empty* paired block
tags in the md preview (preview-only; stored .md is untouched so HTML/PDF
derivation is unaffected).
Also nudge generation: the task-mode system prompt now tells the model to use
markdown image syntax and avoid raw HTML in the canonical md deliverable.
fileList is threaded through WorkspacePanel / FilePreviewPanel (via ExecutionFlow).
15 unit tests cover matching (path-suffix / basename / URL-encoded CN names) and
empty-box stripping. HTML (iframe srcDoc) preview has the same image gap — not
covered here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the now-redundant opacity-90 on EmptyStateIllustration (the grey/mid-tone was darkened at the source, so the dimming hack is no longer needed) and normalize sizing to size-[120px] mb-4 across channel/subscription/knowledge/apps empty states.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Task-mode results claimed a deliverable (e.g. a generated PDF) that never
appeared in the workspace panel. Root cause: the model wrote the file to an
ABSOLUTE path (/output/report.pdf, /scratch/*.png) which resolves to the
container filesystem root -- outside the per-task working dir the LocalExecutor
harvests. The file was never uploaded (file_list=[]) nor synced, so
get_final_result_file found no deliverable and the panel fell back to a
synthesized fallback report. The shared LocalExecutor cannot safely rescue
container-root files (cross-task leak), so the fix steers the model to relative
paths:
- Harden the code-interpreter tool description (local + e2b) to require the
RELATIVE output/ (scratch/) dir and forbid absolute /output|/scratch, noting
files outside the working dir are discarded.
- Add a deterministic, non-blocking corrective notice: when a run's code wrote
to an absolute /output|/scratch path, append a system notice to the tool
result so the model self-corrects on the next step
(BaseExecutor.absolute_path_advisory + wiring in LocalExecutor.run).
- Tests: test/linsight/test_code_interpreter_output_path.py (18 cases).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DEV-only gallery: register the new Color/Scrollbar/Illustration sections in GalleryApp, add a progress/ tracker (per-component migration status), and refresh the Button/ConfirmDialog/Modal/Overview/Responsive/Typography showcases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
illus-grey --illus-300 #FFFFFF→#E5E5E5 (grey mode only; green/blue keep the mid tone) — pure white made e.g. the CrawlingIllustration magnifier halo disappear on white backgrounds. New gallery IllustrationSection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per 基础-滚动条规范.md: remove the global :not(.scrollbar-os) forced-slim ::-webkit-scrollbar rule plus the .excel-scroll / .scrollbar-transparent overrides so OS auto-hide/always-on is respected; drop the now-dead excel-scroll class from ExcelPreview. New gallery ScrollbarSection. The .scrollbar-os opt-out is now a no-op, cleaned up as files get touched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to the None≡[] skill gate: with skills now strictly opt-in, the
daily task-mode path (ChatView → useAiChat → unified /chat/completions →
_to_linsight_submit) exposed a latent gap — it never carried the picked
skills, so the stored SV had skills=None and NOTHING was materialized. The
picker looked broken ("workspace has no such skill file"). Previously this
was masked because None loaded every enabled skill.
Thread the selection end-to-end:
- APIChatCompletion gains `skills` (Track H); _to_linsight_submit maps it
onto the linsight submit schema (None/[] = no skills, opt-in list = those).
- useAiChat sends the picked skill names (taskModeSkillsState('new')) on
task-mode turns only; the daily chain ignores the field.
- tests: _to_linsight_submit forwards a selection / stays None when absent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live case (session b28d0dc6, model deepseek-v4-flash, input "skill test"):
the task-mode ask_user clarify card rendered a raw-JSON blob as its single
question title with zero options. Root cause is model-side — deepseek-v4-flash
did NOT escape the inner quotes in a question text (你想要的"skill test"是指什么?),
so its OpenAI-compatible function-call serializer corrupted the whole `questions`
array. The arg parser then produced a WELL-FORMED outer list/dict but crammed the
entire 3-question array into the FIRST dict's `question` VALUE (dropping the
opening `[{"question"` while keeping the `: "` separator). The trigger is
intermittent: it only fires when a question's text itself contains quotes (here
echoed from the literal user input) — which is why prior DeepSeek clarifications
without quotes rendered fine.
The existing recovery (854d45974) only re-parsed malformed STRING list-elements,
so this dict-value shape fell through unchanged. Extend the recovery to:
- re-expand a dict whose `question` value is itself a serialized questions array
(gated by a quoted-JSON-key signature so ordinary prose is never mangled);
- reconstruct a blob that RETAINED the `: "` separator via `[{"question"` so the
first question comes back clean (no leading `: "` noise);
- run the same crammed-array recovery on a malformed TOP-LEVEL string (still
degrades to [] for arbitrary prose — reason-only park).
Frontend needs no change (one clarify tool_call per recovered question already
renders the multi-page card). 22 unit tests green, incl. the exact live blob as
a fixture and regression guards for prose/placeholder questions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
extendTailwindMerge teaches cn() the custom text-caption/body/h1... tokens as a font-size group (v1.x config shape) so a size + a color in the same cn() call no longer drop the size; gallery Modal/ConfirmDialog sections + kit updates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Task mode was running skills the user never picked. Root cause: the submit
persisted `skills=NULL` (the field was absent — a stale/cached client or a
non-UI caller), and `materialize_session_skills` treated `None` as
"copy every governance-enabled skill" — the exact opposite of an empty
selection. Every enabled skill was then materialized into the workspace
`/skills/` subtree, advertised to the model, and executed, silently
defeating the picker.
- skill_provisioning: `None ≡ [] ≡ "no skills this run"` (guard on `not
selected`); skills are strictly opt-in via an explicit non-empty list,
still intersected with the tenant's governance-enabled set.
- useSubmitMessage (client): the daily-chat task-mode entry now sends an
explicit `skills: []` instead of omitting the field, so the contract no
longer depends on the downstream `|| []` guard.
- test + docstrings updated; `test_none_selection_copies_nothing` is a
regression guard against the footgun.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Button active states now use coarse-pointer:active (hover-capable devices keep the hover color on press, no click flash); theme-green btn-brand-primary :active wrapped in a touch-only media query to match; new gallery ResponsiveSection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump the green theme's --brand-500/main, --illus-500 (+ lighter 100/300), --primary and the btn-brand-primary green ramp from the darker #187C54/#19B476 to #169C47; TaskModeToggle active state uses the brand blue-500 token. Update the values in BRAND-THEME-HANDOFF.md.
(cherry picked from commit 1d2757797 on hotfix/2.6.0-beta4-cofco; style.css hunk re-placed into beta4's .theme-green block since beta4 defaults to the blue theme.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New color×variant×size Button API (solid/outlined/filled/text/link) with legacy shadcn variants auto-mapped; btn-* semantic tokens + touch hit-area in style.css/tailwind; app-wide hoverOnlyWhenSupported; gallery ButtonSection; migrate KnowledgeSpaceSidebar create button to the new API.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two-layer type tokens (primitive --font-size-N + semantic --text-*) in style.css with a mobile remap, wired to Tailwind fontSize; system font stack; new gallery TypographySection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sync_information_article iterated channel_info_source (a tenant-aware table)
with no tenant context set, so on a multi-tenant deploy the first SELECT raised
NoTenantContextError and the daily article sync never ran — subscribed sources'
content never updated.
Mirror reconcile_all_tenants: enumerate active tenants (root + active children,
or the default tenant when multi-tenancy is off) and run the sync under each
tenant's context, isolating per-tenant failures. Add a sync counterpart
TenantDao.get_children_ids_active for the sync worker path, and fix a latent
missing `import asyncio` in the knowledge-space dispatch hook.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to 1208fa158: instead of only relaxing the pattern to allow the
empty string, remove it entirely to align with feat/2.6.0 (72fd1e8f0
"fix: unused pattern"). The whitelist could not stop XSS (it allowed <>/"'&)
yet rejected the empty default plus emoji / non-CJK text; real escaping is
done by the frontend (React text nodes, no dangerouslySetInnerHTML). Keeping
all three branches identical avoids a future merge conflict on these lines.
Update regression tests accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
applicationCenterWelcomeMessage / applicationCenterDescription were declared
with default="" but a pattern requiring >=1 char (^[...]+$). Any tenant whose
stored workstation config left these fields empty or unset (the default) made
WorkstationConfig(**raw) raise a pydantic ValidationError, so GET
/api/v1/workstation/config returned 500 and the client rendered its full-screen
"system maintenance" overlay — /workspace/c/new appeared broken.
Relax both patterns from + to * so the empty string is accepted while keeping
the character whitelist for non-empty input. Add regression tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The task result panel reads its liked verdict from the linsight store, which is
seeded once on hydration. An optimistic like updated only the button's local
state + backend, not the store — so switching away and back re-mounted the panel
with the stale store value and the highlight vanished (a full page reload fixed
it, since that re-hydrates the store from the server).
Sync the verdict back to the store on click via a new onLikedChange callback
(TaskTurnPanel + ExecutionFlow pass updateLinsight), so the store stays current
and the highlight survives switch-away/switch-back.
In task mode the result panel's like/dislike showed as soon as the run hit
'completed', but the task turn still carried its streaming placeholder id
(userMessageId + '_') — the real persisted category="task" ChatMessage id only
arrived on a page reload. A like clicked the moment the panel appeared wrote to
a non-existent row and silently vanished (occasional, timing-dependent).
Source the feedback id from the linsight store instead of the conversation
message, and populate the store's real message_id at completion by reusing the
existing session-version-list enrichment (no new endpoint/field):
- Websocket final_result: fetch the version list, copy this version's real
message_id + liked into the store
- TaskTurnPanel: ResultPanel reads linsight.message_id (history hydration
already seeds it via the same endpoint); add allowFeedback prop for the
share-view opt-out
- AiMessageBubble: pass allowFeedback instead of encoding it via messageId
The button now appears only once the real id is in the store, so show-time and
id-availability are aligned and the placeholder can never reach /liked.
update_workbench_llm persisted the incoming WorkbenchModelConfig wholesale, so a
body without `models` (Pydantic defaults it to None) nulled the entire configured
dialogue-model list — the way a stale admin page wiped the Root config in prod.
Treat a missing/None `models` as "no change" and keep the stored list; an explicit
`models: []` still clears. asr/tts/chat_title keep their None-clears semantics.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A blank dialogue-model row ({id:''}) saved from the workbench model config
flows into the workspace model pickers as <SelectItem value="">, which Radix
forbids — it throws and crashes the whole /workspace/c/new page.
- client: drop empty/invalid-id options in AiModelSelect and the Linsight
ModelSelector so a stale/blank model never renders an empty-string
SelectItem value (defense that doesn't rely on backend sanitizing).
- platform: reject saving a blank model row in WorkbenchModel — validation
only checked models.length, letting {id:''} through and overwriting the
configured dialogue models with an empty row.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Channel article chat yielded the 'end' event BEFORE persisting the answer
ChatMessage, so the client never got the real id — the streamed answer kept its
temporary placeholder id and a like clicked before switching away wrote to a
non-existent row and vanished (same class of bug as the knowledge-space fix).
Persist the answer first, then emit the end event with its message_id so the
client can swap the placeholder out immediately (frontend useChannelChat already
consumes it).
A TTS provider failure (e.g. empty audio) raised a bare exception that
propagated as HTTP 500, which the client's global interceptor treats as a
service outage and redirects to the maintenance overlay — a poor experience
for what is just 'this one utterance failed to synthesize'.
- backend: wrap the synthesis call, raise a dedicated TtsSynthesisFailedError
(code 10026) instead of letting it bubble up as 500
- frontend: textToSpeech opts into the interceptor's translate-and-toast path
(skip403Redirect) instead of the 500 escape hatch; TextToSpeechButton no
longer masks the real error or double-toasts once the interceptor already
showed the localized message
- add api_errors.10026 to zh-Hans/en/ja
Knowledge-space chat (single-file / folder) rendered the streaming answer under
a temporary client-side placeholder id (userMessageId + '_') and never swapped
it for the real persisted ChatMessage id, so a like clicked before the first
page reload wrote to a row that never existed — it silently vanished. Reload
loaded the real id from history and worked fine, masking the bug as 'only
fails right after sending, fixed by refresh'.
- backend: the 'end' stream event now carries the persisted answer message_id
- frontend: useStreamChatSSE forwards it through onFinal; useFolderChat /
useFileChat / useChannelChat swap their placeholder messageId for it so
like/dislike (and any other per-message action) targets the right row
immediately, not just after a reload
Add a shared CommentDialog (optional-comment / reason dialog) + gallery FeedbackSection; migrate the message 点赞/点踩 feedback (dropping the bespoke MessageFeedbackForm) and the menu-unavailable apply-reason dialog onto it; feedback comment is now optional (com_feedback_placeholder, en/zh/ja).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The linsight task result is already persisted as a category="task" ChatMessage
in the unified daily conversation, so rate it through the shared chatmessage
feedback (/liked + /chat/comment) instead of a linsight_session_version-specific
column/endpoint. All four AI Q&A surfaces now share one storage + rollup path.
Backend:
- chat_helpers: agent history formatter returns liked + remark (fixes daily +
in-conversation task-turn highlight on reload)
- workstation_schema: WorkstationMessage carries liked + remark (channel surface)
- linsight utils: get_task_feedback_by_version maps session_version -> its task
ChatMessage {message_id, liked}
- linsight endpoint: session-version-list enriches each version with message_id +
liked for the standalone linsight page
Frontend:
- ResultPanel: rate by messageId via likeChatApi / disLikeCommentApi
- TaskTurnPanel forwards messageId + liked; AiMessageBubble passes the task row's
message.messageId (hidden on the read-only share view); ExecutionFlow reads them
from the enriched session-version store field
- drop the now-unused likeLinsightVersion / commentLinsightVersion
The PRD doc is no longer needed; the 点赞/点踩 feature itself (98b48c93a) is
unaffected — only its now-redundant design doc is removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tighten padding (px-6 -> px-5), add a focus ring (border + 2px shadow) on the tag input, disable autocomplete, and normalize placeholder / no-tags text to #999.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Swap the touch-mobile: variant for max-md: so the edit-tags dialog uses the standard breakpoint-based mobile full-screen sheet (rounded corners only from md up).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace OGDialog/OGDialogTemplate delete confirmations in SharedLinks and the standalone guest-convo item with useConfirm; touch up the ConfirmDialog + Modal gallery sections.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Root-list loading no longer depends on currentFolderId, so selecting a
folder (click or route change) never rebuilds this or any other space's
tree; switching the active space leaves background trees untouched.
- Clicking a folder row (or a space row) only selects/navigates; expanding
and collapsing is exclusively the chevron's job.
- Deep links (direct URL into a nested folder) still auto-expand the
ancestor chain, fetching only levels not already cached, gated on the
root list being genuinely loaded (fixes the reload race that left the
tree half-expanded).
Product PRD (pyramid structure) for adding thumbs up/down feedback to the
four AI Q&A surfaces (workstation daily, linsight task mode, knowledge-space
知源, channel subscription). Feedback persists on the answer row and rolls up
into message_session for single-table, per-surface analytics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>