Commit Graph

1237 Commits

Author SHA1 Message Date
claude_code
8b8b05e005 Merge remote-tracking branch 'gitea/develop' into feat/ai-chat-collapse-on-focus 2026-06-21 01:33:47 +03:00
claude_code
4f5a08cba0 Merge pull request 'fix(ai-chat): resolve current page for agent context (#43, hardness #1)' (#47) from fix/ai-chat-current-page into develop
Some checks failed
Develop / test (push) Has been cancelled
Develop / build (push) Has been cancelled
2026-06-21 01:33:28 +03:00
claude_code
3695dbdf7f Merge remote-tracking branch 'gitea/develop' into fix/ai-chat-current-page 2026-06-21 01:29:37 +03:00
claude_code
ab51239cab Merge pull request 'feat(share): public-share AI chat reuses internal chat presentation (#41)' (#51) from feat/share-chat-reuse-internal into develop
Some checks failed
Develop / test (push) Has been cancelled
Develop / build (push) Has been cancelled
2026-06-21 01:29:17 +03:00
claude_code
4fa8882c58 Merge remote-tracking branch 'gitea/develop' into feat/share-chat-reuse-internal 2026-06-21 01:28:14 +03:00
claude_code
eae68ba11f Merge pull request 'fix(mcp): security review follow-ups (#24)' (#48) from fix/mcp-security-followups into develop
Some checks failed
Develop / test (push) Has been cancelled
Develop / build (push) Has been cancelled
2026-06-21 01:28:10 +03:00
claude_code
730486ad12 test(mcp): keep real mcp-auth.helpers in gate spec mock (forward-compat with #49)
Some checks failed
Test / test (pull_request) Has been cancelled
After develop merged, mcp.service.ts calls decideBasicGate from mcp-auth.helpers.
The gate spec mocked the whole module returning only FailedLoginLimiter, so the
merged code crashed with 'decideBasicGate is not a function' (7/7 failing).
Spread jest.requireActual('./mcp-auth.helpers') so the real helpers are kept and
the gate exercises real logic; keep only FailedLoginLimiter stubbed so its
constructor runs without a real sweep timer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:25:36 +03:00
claude_code
5f3a3d3ec0 Merge remote-tracking branch 'gitea/develop' into fix/mcp-security-followups 2026-06-21 01:21:57 +03:00
claude_code
f63719a21c fix(share): neutralize own-origin absolute links in public-share AI chat
isExternalHttpUrl treated any http(s):// URL as external, so an absolute link
back to the app's own host (e.g. https://self/p/{uuid}, /settings/members)
emitted by the assistant stayed clickable on the anonymous share, leaking
internal UUIDs/structure and pointing at auth-gated routes. Classify a link as
external only when its host differs from window.location.host; unparseable URLs
are treated as internal (fail-closed). Tests cover own-origin absolute (flag
on -> inert), external host (kept with safe rel/target), dangerous schemes, and
no behavior change for the internal chat (flag off).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:20:11 +03:00
claude_code
877806e0ce Merge pull request 'ci: gate develop & release image builds on the test suite' (#59) from ci/gate-build-on-tests into develop
Some checks failed
Develop / test (push) Has been cancelled
Develop / build (push) Has been cancelled
2026-06-21 01:17:58 +03:00
claude_code
0caceb614b ci: gate develop & release image builds on the test suite
Some checks failed
Test / test (pull_request) Has been cancelled
The Docker-image builds ran independently of the Test workflow, so a
failing test would not block publishing the :develop image (or a
release). GitHub Actions `needs:` only works within one workflow, so the
two separate workflows didn't depend on each other.

Make test.yml a reusable workflow (workflow_call) and call it from
develop.yml and release.yml as a `test` job that `build` depends on
(`needs: test`); release's `release` job already needs `build`, so it
waits transitively. test.yml keeps its pull_request trigger for PR
gating; its redundant push:develop trigger is dropped (develop.yml now
calls it on push).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:17:27 +03:00
claude_code
987a4fd32e Merge pull request 'ci: run test suites on push/PR + quarantine broken stock scaffolds' (#58) from ci/test-job into develop
Some checks failed
Develop / build (push) Has been cancelled
Test / test (push) Has been cancelled
2026-06-21 00:44:49 +03:00
claude_code
d96f94a80a ci: run the test suites on push/PR + quarantine broken stock scaffolds
Some checks failed
Test / test (pull_request) Has been cancelled
Add .github/workflows/test.yml (pnpm + Node 22): on pull_request and push
to develop it installs, builds @docmost/editor-ext and runs `pnpm -r test`
across all packages (server Jest, client Vitest, editor-ext Vitest,
packages/mcp node:test). So tests now run automatically in CI, not just
on demand.

To make the run green, quarantine the 16 pre-existing stock NestJS
`should be defined` scaffold specs via jest `testPathIgnorePatterns` —
they never compiled (missing DI providers / lib0 ESM) and assert nothing
useful. Tracked for a proper fix/removal in issue #56. Verified each
pattern drops only its scaffold (46 of 62 suites still collected) and the
full `pnpm -r test` is green: server 587, client 185, editor-ext 56,
mcp 247.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:44:21 +03:00
claude_code
8414114dc8 Merge pull request 'docs(backlog): extract non-test findings to issues #52-#56' (#57) from docs/extract-findings-to-issues into develop
Some checks failed
Develop / build (push) Has been cancelled
2026-06-21 00:25:30 +03:00
claude_code
41efacbe3d docs(backlog): move non-test findings out to issues #52-#56
Keep the backlog focused on deferred TESTS; the related non-test gaps
(model-allow-list, restriction-cache invalidation, server embed-recursion
guard, collectPageEmbeds cycle guard, jest DI/lib0-ESM debt) are now
tracked as issues #52-#56 and only linked from the backlog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:25:05 +03:00
claude_code
4348608ee4 Merge pull request 'test: cover features since 053a9c0d + repair test tooling' (#49) from test/feature-coverage into develop
Some checks failed
Develop / build (push) Has been cancelled
2026-06-21 00:20:15 +03:00
claude_code
bd377ca4a8 docs(backlog): record deferred tests + non-test gaps from the coverage PR
Captures what PR #49 intentionally left out: DB-integration tests (need a
test Postgres), the public-share XFF e2e + real-Redis Lua check (need an
HTTP/Redis harness), the full AiChatService.stream integration (R1-stream
seam), and the related non-test findings (no server-side model allow-list,
unreferenced restriction-cache invalidation, client-only embed recursion
cap, missing cycle guard, and the pre-existing jest DI/lib0-ESM debt).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:19:39 +03:00
claude code agent 227
e0aac5aa04 feat(share): public-share AI chat reuses the internal chat's presentation (#41)
The public-share widget was a separate minimal impl: plain-text answer, static
'Thinking…', no markdown, no tool-cards. Now it renders through the internal
chat's debugged presentational layer (MessageList/MessageItem/TypingIndicator/
ToolCallCard), so a share gets the same incremental streaming, animated typing
indicator, markdown, and tool-call cards. The share keeps its anonymous
transport (useChat + DefaultChatTransport '/api/shares/ai/stream',
credentials:'omit').

The shared components were already prop-driven (UIMessage[] + isStreaming) with
no transport/auth coupling; made the new props additive optionals (emptyState,
showCitations, neutralizeInternalLinks) all defaulting to current behavior, so
the internal chat is unchanged.

Security (review-caught): rendering assistant markdown on the ANONYMOUS share
made internal links (/p/{id}, /settings/...) clickable, which the old plain-text
render didn't. renderChatMarkdown gains neutralizeInternalLinks (true only on
the share): a one-shot DOMPurify afterSanitizeAttributes hook (added/removed by
reference around a single sanitize) strips href from internal/relative/non-http(s)
links (rendered inert) and keeps external http(s) links with
rel=noopener noreferrer nofollow target=_blank. Tests cover both the link
neutralization and the absence of any global-hook leak into internal renders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:04:18 +03:00
claude code agent 227
f6e216cb87 feat(ai-chat): auto-collapse the chat window on page focus, expand on header (#42)
The floating chat window covered page content; you could only collapse it
manually. Now it auto-collapses to its header (visual collapse only — ChatThread
stays mounted so an in-flight stream isn't interrupted) when you interact with
the page, and expands again from the header.

- document mousedown listener in the CAPTURE phase, armed only when
  windowOpen && !minimized; collapses on a pointer-down outside the window.
  Guards: ignore clicks inside the window and inside any Mantine [data-portal]
  (the chat-list kebab menu + delete-confirm modal render in portals).
- Header click expands: startDrag distinguishes click vs drag by a 4px
  threshold (minimizedRef avoids a stale closure); an expand-click doesn't
  persist geometry.
- Reset minimized=false when the window opens (no sticky collapsed state).
- a11y: when minimized, the title is the keyboard expand affordance
  (role=button, tabIndex, aria-label Expand, Enter/Space) — kept off the
  dragBar container so no role=button wraps the Minimize/Close buttons.
- Pure helpers shouldCollapseOnOutsidePointer + isHeaderClick with vitest tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:45:43 +03:00
claude_code
90d3fab483 test: cover features since 053a9c0d + repair test tooling
Add ~330 tests across server (Jest), client (Vitest), editor-ext (Vitest)
and packages/mcp (node:test) for the gitmost features added since
053a9c0d: AI chat, AI agent roles, public-share assistant, MCP per-user
auth, HTML embed, page templates/embed, realtime tree, tree
expand/collapse, and the AI-settings UI.

Test-tooling fixes (prerequisite, were silently hiding coverage):
- Repair 3 page-template specs broken by the 11-arg TransclusionService
  constructor; they never compiled, so template access-control / content
  -leak / unsync-strip coverage was fictitious.
- Build @docmost/editor-ext before server tests via a `pretest` hook;
  the stale dist omitted the new HtmlEmbed/PageEmbed exports (TS2305).
- Let jest resolve the .tsx email templates: add `tsx` to
  moduleFileExtensions and widen the ts-jest transform to (t|j)sx?.

Behaviour-preserving "extract pure core" refactors that the tests drive:
- server: resolveShareAssistantRequest + uiMessageTextLength
  (public-share controller), decideBasicGate + mapAuthResultToResponse
  (mcp), buildErrorAssistantRecord (ai-chat), jsonbObject export (roles).
- client: render-raw-html + shouldExecute/canEdit, decide-embed-state,
  page-embed picker utils, tree-socket reducers, open/close branch maps,
  isEndpointConfigured/resolveKeyField; buildTreeWithChildren now treats
  a permission-trimmed orphan as a root instead of crashing.

Deferred (need a test DB or HTTP harness, documented in the specs):
repo-level Postgres integration tests and the public-share XFF E2E.
Pre-existing DI/lib0-ESM suite failures are untouched and out of scope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:40:40 +03:00
claude code agent 227
1f457b060c fix(mcp): security review follow-ups (#24)
Post-merge hardening from the #13 security review:
- isInitializeRequestBody now delegates to the SDK isInitializeRequest (same
  predicate as packages/mcp/http.ts), so a bare {method:'initialize'} with no
  id/params no longer triggers the side-effecting login() (audit-spam /
  user_sessions growth) before http.ts 400s it.
- Bind the Bearer path to the instance workspace: verifyBearerAccess rejects a
  token whose payload.workspaceId != the instance workspace (resolved via
  workspaceRepo.findFirst, consistent with the Basic path); optional param so
  it's a no-op when unset.
- Close the user-enumeration timing oracle in verifyUserCredentials: the
  missing/disabled branch now runs a bcrypt compare against a module-level dummy
  hash whose cost (12) matches production saltRounds, so both paths take one
  equal-cost bcrypt compare; the exact CREDENTIALS_MISMATCH_MESSAGE is preserved.
- Document the trusted-proxy requirement for the spoofable per-IP brute-force
  limiter in .env.example (trustProxy is on; deploy behind a trusted proxy).
- Add real-execution coverage for enforceBasicLoginGate (SSO enforced / EE-MFA
  bundled vs not / user-MFA / workspace-enforced-MFA) instead of stubbing the gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:36:53 +03:00
claude_code
692c0abe13 Merge pull request 'feat(editor): footnotes (reference + definitions, collab-safe)' (#18) from feat/footnotes into develop
Some checks failed
Develop / build (push) Has been cancelled
2026-06-20 22:21:35 +03:00
claude_code
c5f44a6eee Merge branch 'develop' into feat/footnotes
Resolve conflicts at shared registration points by unioning both features
(footnotes + the already-merged html-embed / page-embed work):
- slash-menu/menu-items.ts, editor extensions.ts: keep both imports + configures
- collaboration.util.ts: register footnote nodes and pageEmbed
- editor-ext marked.utils.ts: register footnote + html-embed markdown extensions
- editor-ext package.json/tsconfig.json/vitest.config.ts: union of test config
  (jsdom env for footnote DOM tests + combined test/spec include glob)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:21:07 +03:00
claude code agent 227
a6ba19f0dc feat(ai-chat): add get_current_page tool for proxy-robust page context (#43, hardness #2)
The current page id was only injected as text in the system prompt, which a
proxy (CLIProxyAPI) can rewrite/truncate, so the agent could lose track of 'this
page'. Add a getCurrentPage tool the model can call to read the open page (id +
title) from the server-side request context (forUser now takes openedPage,
threaded from body.openPage — the same value used for the system prompt). The
inline system-prompt line is kept as belt-and-suspenders. Reads/writes still go
through the CASL-enforced page tools by id, so this is strictly not worse than
the existing prompt hint — just delivered over a channel the proxy can't mangle.

User-approved on the issue. Completes #43 together with the hardness-1 fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:19:40 +03:00
claude code agent 227
ada1dce739 fix(ai-chat): resolve the current page for agent context (#43, hardness #1)
AiChatWindow derived the open page via useParams(), but it's mounted in a
pathless parent layout route where :pageSlug isn't matched, so useParams()
returned {} and openPage was ALWAYS null — the agent never received current-page
context (couldn't resolve 'this page'/'the current page'). Derive pageSlug from
useMatch('/s/:spaceSlug/p/:pageSlug') against the full pathname instead, so it
resolves regardless of where the component sits in the route tree. No-match
behavior is unchanged (undefined -> query disabled -> openPage null).

Addresses Hardness #1 of #43. Hardness #2 (proxy resilience: a get_current_page
tool / hidden user-message context so identity doesn't depend on the system
prompt surviving CLIProxyAPI) remains open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:57:03 +03:00
claude code agent 227
a85dd607bd fix(footnotes): tighten the gap between a definition's number and text (#44)
The footnote definition number ('1.') sat ~19px from its text because two
spacings stacked: the 1.5em (24px) marker min-width box (wider than the ~15px
glyph) plus a 10px flex gap. Reduce the flex gap to 0.4em (about one space) and
right-align the number within the 1.5em column so the period sits next to the
text and multi-digit numbers (10, 11, ...) stay aligned. Reads like '1. text'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:29:02 +03:00
claude_code
b53b0c651e docs(footnotes): delete footnotes design plan
Some checks failed
Develop / build (push) Has been cancelled
The detailed footnotes implementation plan has been removed from the repository now that the design is finalized and tracked elsewhere.
2026-06-20 21:03:50 +03:00
claude_code
be17391e18 docs: remove admin-only HTML embed documentation
Some checks failed
Develop / build (push) Has been cancelled
2026-06-20 21:03:31 +03:00
claude_code
19ae6a0efa Merge pull request 'feat(editor): page templates — live whole-page embed (MVP)' (#17) from feat/page-templates into develop
Some checks failed
Develop / build (push) Has been cancelled
2026-06-20 20:34:44 +03:00
claude_code
7a03321d43 Merge pull request 'feat(editor): admin-only raw HTML/CSS/JS embed (variant C)' (#16) from feat/html-embed-admin into develop
Some checks failed
Develop / build (push) Has been cancelled
2026-06-20 20:19:06 +03:00
claude_code
2b3fc926cc Merge remote-tracking branch 'gitea/develop' into feat/html-embed-admin
# Conflicts:
#	apps/server/src/core/workspace/services/workspace.service.ts
2026-06-20 20:18:44 +03:00
claude_code
e9e9f74ec6 Merge remote-tracking branch 'gitea/develop' into feat/page-templates
# Conflicts:
#	apps/server/src/integrations/throttle/throttle.module.ts
#	apps/server/src/integrations/throttle/throttler-names.ts
2026-06-20 20:18:42 +03:00
claude code agent 227
52efd37fd9 fix(page-templates): import ThrottleModule into collab app so it boots
PageTemplateController (added on this branch) guards its lookup/toggle routes
with UserThrottlerGuard, which depends on the throttler options provided by
ThrottleModule. CollaborationModule -> TransclusionModule registers that
controller, and the collab server bootstraps CollabAppModule, which did not
import ThrottleModule. The API server's AppModule does, so :3000 booted, but
the collab server (:3001) crashed at startup with
'Nest can't resolve dependencies of the UserThrottlerGuard ... THROTTLER:MODULE_OPTIONS'.
Without collab the editor can't sync, so live editing was broken on this branch.

Import ThrottleModule into CollabAppModule, mirroring AppModule, so the guard
resolves in the collab process too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:10:09 +03:00
claude_code
d80a419963 ci(develop): build the :develop image on push to develop, not main
Some checks failed
Develop / build (push) Has been cancelled
The "Develop" workflow builds the :develop image but was triggered on
push to main (the stable/default branch, released via v* tags). Switch
the trigger to the develop branch so pushes to develop build the image.
2026-06-20 20:05:44 +03:00
claude_code
6128920264 Merge pull request 'feat(public-share): selectable agent-role identity + fix floating-icon overlap' (#25) from feat/share-assistant-identity-and-branding into develop 2026-06-20 19:59:48 +03:00
claude_code
cf29a0fc11 0.93.0 2026-06-20 19:57:37 +03:00
claude_code
4fe42ead56 feat(public-share): selectable agent-role identity + fix floating-icon overlap
Anonymous public-share AI assistant:
- Add a workspace setting `publicShareAssistantRoleId` so an admin can pick which
  agent role (identity/persona) the anonymous assistant adopts. The role's
  instructions REPLACE the built-in persona while the immutable safety framework
  is still always appended; the role's optional model override takes precedence
  over the cheap publicShareChatModel. Resolved server-authoritatively
  (workspace-scoped, soft-delete aware; disabled/missing roles fall back to the
  built-in persona, so the tool scope remains the real security boundary).
- Plumb the field through the update DTO, ai-settings service, the workspace.repo
  ALLOWED whitelist, resolve()/getMasked(), stream-time role resolution and the
  prompt/model, plus the settings UI: a new "Assistant identity" Select listing
  enabled roles (and surfacing a saved-but-disabled role explicitly).

Public-share branding / floating icon:
- Fix the AI assistant FAB overlapping the "Powered by ..." button (both were
  Affixed bottom-right): stack the FAB above the bottom-right branding.
- Rename "Powered by Docmost" -> "Powered by Gitmost" and point the link at the
  gitmost repo.

Tests: extend public-share-chat.spec (role persona replacement still appends the
safety framework, resolveShareRole edge cases, model-override precedence).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:54:45 +03:00
claude code agent 227
41f3944e79 fix(html-embed): execute embeds on public shares; toggle is server-side kill switch
The html-embed feature toggle was enforced CLIENT-side in the NodeView (reads
settings.htmlEmbed from the logged-in workspace), so an anonymous public-share
viewer — who has no workspace context — always saw it as OFF and got a
placeholder instead of the executing embed. That broke the whole point (a
tracker must run for anonymous visitors).

Make it server-authoritative:
- share.service prepareContentForShare (the single path both share-content
  flows use) strips htmlEmbed from served content when the workspace toggle is
  OFF; both callers (updatePublicAttachments host page + lookupTransclusionForShare)
  resolve the toggle once and pass it. Fail-closed: missing workspace -> OFF ->
  stripped.
- NodeView executes whatever it was served in read-only/share mode
  (shouldExecute = !editor.isEditable || htmlEmbedEnabled); the disabled
  placeholder now only shows in the editable editor when OFF.

Net: anonymous share + toggle ON -> server serves the (admin-authored) embed ->
it executes for everyone; toggle OFF -> stripped server-side from every
share-content path (true kill switch); a non-admin embed can never be served
(save-path strip). No XSS regression in the editable editor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:49:57 +03:00
claude_code
46688074d8 Merge pull request 'feat(tree): server-authoritative realtime tree updates' (#15) from feat/realtime-tree-server into develop 2026-06-20 19:48:36 +03:00
vvzvlad
f650d2591b fix(tree): address realtime-tree-server review findings
- make addTreeNode receivers idempotent (invalidateOnCreatePage guard +
  buildTree dedup) so the author's self-echo no longer duplicates the node
- broadcast realtime tree updates for bulk copy/duplicate and import via a
  root refetch: PAGE_CREATED now carries spaceId and the WS listener falls
  back to refetchRootTreeNodeEvent when no per-node snapshot is present
- remove the now-dead client-relay inbound path (isTreeEvent/handleTreeEvent)
  that remained a stale-restriction-cache attack surface
- honest string|null cast for a root move's parent id
- add tests: buildTree dedup; onPageCreated per-node vs refetch branching

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:48:06 +03:00
claude_code
f72e44c9b7 Merge pull request 'feat(mcp): per-user auth for /mcp (HTTP Basic, server-validated)' (#13) from feat/mcp-per-user-auth into develop 2026-06-20 19:32:02 +03:00
claude code agent 227
8fcce6a674 feat(html-embed): per-workspace feature toggle, default OFF
The admin-only raw HTML/JS embed is a deliberate stored-XSS surface, so gate the
whole feature behind a workspace toggle that is OFF by default; it only works
when a workspace admin explicitly enables it.

- settings.htmlEmbed (boolean, default false) + workspace-update field htmlEmbed,
  persisted via WorkspaceRepo.updateSetting with an audit diff. Flipping it is
  admin-only (same Manage Settings CASL as other workspace toggles).
- New gate htmlEmbedAllowed(featureEnabled, role) = featureEnabled && admin/owner.
  All 7 server write paths (create, duplicate, collab onStoreDocument, REST/MCP/AI
  updatePageContent, single + zip import, transclusion unsync) now read the
  workspace's settings.htmlEmbed and strip unless (toggle ON AND admin). OFF
  (default, or a failed/empty workspace lookup) strips htmlEmbed for EVERYONE
  including admins -> existing embeds are cleaned up on next save, none persist.
- Client (defense-in-depth): the /html slash item is hidden unless toggle ON +
  admin; the NodeView executes nothing and shows a 'disabled in this workspace'
  placeholder when OFF; an admin Switch in Workspace Settings -> General with a
  description of the behavior.
- docs/html-embed-admin.md documents the toggle + admin-only + fail-closed
  coedit (a non-admin save strips an admin's embed) + execution semantics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:28:39 +03:00
claude_code
c718b2a6de Merge pull request 'feat(ai): anonymous AI assistant on public shares' (#14) from feat/public-share-assistant into develop 2026-06-20 18:41:17 +03:00
vvzvlad
0c46f60ddf Merge gitea/develop into feat/public-share-assistant
Resolve conflicts with the independently-merged ai-agent-roles feature:
- ai-chat.module.ts: keep BOTH AiAgentRolesModule and the public-share
  wiring (Share/Search modules, PublicShareChatController, services).
- ai.service.ts: take develop's getChatModel ChatModelOverride superset,
  which already covers the public-share model-id-only override.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:40:58 +03:00
vvzvlad
90e9b0a3f4 docs(public-share): document trusted-proxy XFF requirement + cost cap
The anonymous public-share AI assistant's per-IP rate limit is only
effective behind a trusted reverse proxy that overwrites X-Forwarded-For
with the real client IP (the app runs with trustProxy). Document this
deployment requirement and the per-workspace cost backstop env var
(SHARE_AI_WORKSPACE_MAX_PER_HOUR, default 300) in .env.example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:34:16 +03:00
claude_code
4c1d1aa2ee Merge pull request 'feat(ai-chat): agent roles (admin persona + optional model)' (#11) from feat/ai-agent-roles into develop 2026-06-20 18:31:10 +03:00
vvzvlad
4b31128e24 fix(ai-roles): harden model override, role-name uniqueness, id validation, list least-privilege
Follow-up fixes on the agent-roles feature:

- ai.service: a cross-driver override to the ollama driver (when the
  workspace driver is not ollama) now fails with an explicit 503 instead
  of silently reusing the workspace base URL, which belongs to a different
  provider. Same-driver ollama and openai/gemini overrides are unchanged.
- migration: add a partial unique index on (workspace_id, name) WHERE
  deleted_at IS NULL so role names are unique per workspace without
  soft-deleted rows blocking re-creation; map Postgres 23505 to a 409
  ConflictException on create/update.
- dto: validate the role id as @IsUUID instead of @IsString.
- roles list: do not expose instructions/modelConfig to non-admin members.
  The list endpoint now returns a picker view (id/name/emoji/description/
  enabled) to members and the full view only to admins (same gate as the
  CRUD endpoints). Client IAiRole fields made optional accordingly.

Adds tests for the cross-driver-ollama throw, the 23505->409 mapping, and
the non-admin picker-view security invariant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:30:33 +03:00
claude_code
127d26c057 Merge pull request 'test(ai-chat): crypto/SSRF/assistant-parts coverage + a11y + refactors' (#10) from feat/ai-chat-review-followups into develop 2026-06-20 18:10:33 +03:00
vvzvlad
45cf4140eb Merge branch 'develop' into feat/ai-chat-review-followups
Integrate the already-merged step-limit work from develop. Only conflict was
ai-chat.service.spec.ts: both sides appended a describe block and edited the
import line. Resolved as a union — keep compactToolOutput + the assistantParts/
serializeSteps/rowToUiMessage suites (this branch) AND the prepareAgentStep
suite (develop), importing all symbols from ai-chat.service.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:09:17 +03:00
claude code agent 227
ec128d54b4 test(ssrf): add IP-level bypass-vector cases (ported from GLM branch)
Adds explicit isIpAllowed cases for the CGNAT, ULA (fd00::/8) and IPv4-mapped
IPv6 loopback (::ffff:127.0.0.1) sample addresses from the parallel
safety-coverage branch. The mapped-loopback case is genuinely new (the existing
table only covered the mapped *private* variant); CGNAT and ULA ranges were
already covered with other samples and are kept here as explicit regression
guards for these specific addresses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:00:43 +03:00