Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
f6bfa9c
fix(skill): reduce watcher inotify usage
May 22, 2026
bff62db
fix(workflow): isolate LLM provider from shared singleton (#316)
xiami762 May 25, 2026
5144011
feat(web2cli,agent): remove agent-browser from web2cli, add planner a…
xiami762 May 25, 2026
39580cd
fix(webui): narrow uploaded document attachment type
May 25, 2026
1d60c17
Merge pull request #318 from AgentFlocks/fix/sessionchat-uploaded-doc…
stephamie7 May 25, 2026
ab3b702
feat(skills): migrate browser workflows to flocks browser (#320)
xiami762 May 25, 2026
23ac8f6
fix(storage): prevent and recover from SQLite "file is not a database…
xiami762 May 25, 2026
18162dc
fix(mcp): accept legacy env alias for local server config (#317)
xiami762 May 25, 2026
782749d
feat(compaction): overhaul context compaction with hermes-style pre-p…
duguwanglong May 25, 2026
1b097a2
fix(channel): reliably parse and dispatch slash commands from group-m…
duguwanglong May 25, 2026
8630224
Merge branch 'dev' of github.com:AgentFlocks/flocks into feat/context…
duguwanglong May 25, 2026
74aad64
chore: remove docs/design directory
duguwanglong May 25, 2026
bb43e79
Merge pull request #310 from AgentFlocks/fix/reduce-skill-watcher-ino…
xiami762 May 26, 2026
db32305
feat(tool): make read tool output limits configurable via flocks.json
duguwanglong May 26, 2026
52bf110
fix(device): support host+port providers in connectivity test
duguwanglong May 26, 2026
87dcc72
fix(tool): restore tools when their API service is re-enabled
duguwanglong May 26, 2026
27fc4bd
fix(mcp): connect on first enable when server is absent from runtime
duguwanglong May 26, 2026
cde8046
fix(device-startup): skip api-type integrations during _sync_all
duguwanglong May 26, 2026
f077980
test(tool): cover bi-directional sync and api-type exclusion
duguwanglong May 26, 2026
bb15a9e
fix(pr321): address three pre-merge issues from code review
duguwanglong May 26, 2026
28af9f9
fix(pr321): follow-up cleanup after self-review
duguwanglong May 26, 2026
11b4d71
test(pr321): cover the three review fixes with focused unit tests
duguwanglong May 26, 2026
47ffd57
Merge pull request #321 from AgentFlocks/feat/context-compaction-v2
xiami762 May 26, 2026
0a4ac64
fix(mcp): reconnect after previous FAILED/DISCONNECTED state too
duguwanglong May 26, 2026
87a36e3
test(device): cover host+port fallback for connectivity probe
duguwanglong May 26, 2026
b3fc90c
docs(tool): document the pair-with-apply contract for sync helper
duguwanglong May 26, 2026
049173e
fix(skills): require device_context lookup before calling device tools
duguwanglong May 26, 2026
5b4ef42
Merge pull request #323 from AgentFlocks/fix/device-mcp-tool-visibility
xiami762 May 26, 2026
78d2c48
perf(webui,session): code-split routes/modals; per-message parts pers…
xiami762 May 26, 2026
fb5cbb1
Merge branch 'dev' of github.com:AgentFlocks/flocks into fix/device-r…
duguwanglong May 26, 2026
7f4add6
Merge pull request #325 from AgentFlocks/fix/device-resolution-in-use…
xiami762 May 26, 2026
4a92675
feat(web2cli,browser): improve cookie scoping and payload handling (#…
xiami762 May 26, 2026
ff1ffe3
feat(web2cli,session): multipart payloads, manual auth headers, tool-…
xiami762 May 26, 2026
f301bb8
refactor(session): use offline chars/4 token estimate (#327)
xiami762 May 27, 2026
53d0da2
fix(session): persist text placeholder on start to preserve part orde…
xiami762 May 27, 2026
8787a6b
feat(tdp_alert_triage): replace HTTP analysis nodes with unified 5-st…
duguwanglong May 27, 2026
4f4301e
feat(device): improve tool targeting, discovery, and WebUI source lab…
xiami762 May 27, 2026
874a46d
Merge pull request #330 from AgentFlocks/feat/sync-tdp-http-prompt
xiami762 May 27, 2026
21ee000
Update version to v2026.5.27 in pyproject.toml and uv.lock (#332)
stephamie7 May 27, 2026
4973f1f
fix(session): reject tool-call title payloads
May 27, 2026
6b497ef
feat(agent,webui): device-inspector agent and workflow session naviga…
xiami762 May 28, 2026
edc0a0c
fix(webui): enforce workflow session cap on all localStorage writes (…
xiami762 May 28, 2026
c97aa59
Merge pull request #335 from AgentFlocks/fix/session-title-tool-call-…
xiami762 May 28, 2026
876dadd
fix(device): per-device tool enable/disable isolation
duguwanglong May 28, 2026
fddc836
Merge pull request #339 from AgentFlocks/fix/device-tool-isolation
xiami762 May 28, 2026
965f133
feat(webui): add agent picker and mentions
chenjie-booker May 28, 2026
1ec9895
fix(tool): force-close HTTP tool sockets to prevent CLOSE_WAIT buildu…
xiami762 Jun 1, 2026
d44bbbd
fix(channel): archive previous session on IM /new command (#344)
xiami762 Jun 1, 2026
e70d598
fix(logs): bound local log growth (#331)
JohnYin-hub Jun 1, 2026
7674a8e
feat(workflow): add Kafka ingest consumer with WebUI integration (#342)
xiami762 Jun 1, 2026
ba116a4
fix(webui): remove stale workflow detail session sync (#346)
xiami762 Jun 1, 2026
1f8e794
feat(session): add non-blocking prompt queue (#334)
stephamie7 Jun 1, 2026
1306920
Squashed commit of the following:
chenjie-booker May 22, 2026
41baa23
removed github push artifacts to console.
chenjie-booker May 22, 2026
aedf47d
feat: support console pro bundle updates
chenjie-booker May 22, 2026
48c7ac6
debug license display
chenjie-booker May 22, 2026
70996c7
fix pro bundle update checks
chenjie-booker May 22, 2026
06000ae
add linux tar packaging workflow
chenjie-booker May 22, 2026
7a27f75
minor change
chenjie-booker May 22, 2026
7da560b
upload linux tar workflow artifact
chenjie-booker May 22, 2026
e3058cc
modify tar name
chenjie-booker May 22, 2026
655dbd6
fix pro bundle update version checks
chenjie-booker May 22, 2026
b977454
fix pro bundle version display
chenjie-booker May 22, 2026
86bb6cb
fix: refine pro bundle upgrade handling
chenjie-booker May 24, 2026
77fa279
fix: remove trial license upgrade paths
chenjie-booker May 24, 2026
1961f1a
debug pro-download
chenjie-booker May 25, 2026
b8a5916
add process bar for downloading
chenjie-booker May 25, 2026
baefc86
set default portal env vars for service startup
chenjie-booker May 26, 2026
38b7bf3
fix: set default console base url for backend startup
chenjie-booker May 26, 2026
c5fd8be
pro application form includes sales name
chenjie-booker May 26, 2026
c5183dd
fix license quota sync for account management
chenjie-booker May 27, 2026
db7b585
fix license sync timestamp display
chenjie-booker May 27, 2026
13a0d3c
fix pro branding and upgrade permissions
chenjie-booker May 27, 2026
91f16d6
update default console portal host
chenjie-booker May 27, 2026
4d75823
fix http middleware hook registration
chenjie-booker Jun 1, 2026
0ae0282
feat(workflow): add generic background poller with API and WebUI (#347)
xiami762 Jun 1, 2026
05738d7
Fix/provider base url required (#348)
JohnYin-hub Jun 1, 2026
f10f747
feat(device): add 360 WAF v5.5 adapter (#343)
magicmagicspider Jun 2, 2026
53290ad
feat(provider): add MiniMax M3 catalog support (#351)
xiami762 Jun 2, 2026
4aff5d6
fix(session): clear history and surface Feishu websocket disconnects …
xiami762 Jun 2, 2026
cb66323
fix: eliminate OOM in execution history trim under syslog load
duguwanglong Jun 2, 2026
9c9c0d0
fix(kafka): tighten ingest backpressure and compact execution storage…
xiami762 Jun 2, 2026
a7f9b33
Merge pull request #354 from AgentFlocks/fix/execution-trim-oom-json-…
xiami762 Jun 2, 2026
eeee46a
update pro upgrade form fields and validation
chenjie-booker Jun 3, 2026
81c5c24
remove eslint disable comment for exhaustive-deps in AuditLogsPage us…
stephamie7 Jun 3, 2026
26a644b
fix: secure pro upgrade status reporting
chenjie-booker Jun 3, 2026
c9f0773
Merge remote-tracking branch 'origin/feat/talk_to_subagent' into dev
stephamie7 Jun 3, 2026
9eb5cb6
Merge remote-tracking branch 'origin/feat/auth_hook3' into merge/auth…
stephamie7 Jun 3, 2026
792f444
Merge pull request #358 from AgentFlocks/merge/auth-hook3-into-dev
xiami762 Jun 3, 2026
2b7488c
Merge main into dev after auth_hook3
stephamie7 Jun 3, 2026
954dbd3
Merge pull request #360 from AgentFlocks/sync/main-into-dev-after-aut…
xiami762 Jun 3, 2026
4907423
fix(session): normalize legacy stored messages and parts on cache loa…
xiami762 Jun 3, 2026
341fa88
fix(channel): support /clear slash command in channel sessions (#356)
xiami762 Jun 3, 2026
4773de2
docs(readme): update remote access and proxy guidance (#359)
xiami762 Jun 3, 2026
68df754
fix(session): keep mention agent routing in queued sends
chenjie-booker Jun 3, 2026
124b325
feat(provider): add minimax-m3 and update model limits in catalog
duguwanglong Jun 3, 2026
644b9a2
Feat/device huorong hwwaf (#362)
duguwanglong Jun 3, 2026
22943b4
feat(workflow): merge Kafka configured inputs with consumed messages …
xiami762 Jun 3, 2026
4ff737f
Merge pull request #363 from AgentFlocks/feat/catalog-minimax-m3
xiami762 Jun 3, 2026
170c675
chore: update project version to v2026.6.3 in pyproject.toml and uv.l…
stephamie7 Jun 3, 2026
a4a0dd4
fix(channel): assign session ownership and restrict ownerless access …
xiami762 Jun 4, 2026
a9a63de
chore: bump project version to v2026.6.4
xiami762 Jun 4, 2026
3cff3be
Merge pull request #371 from AgentFlocks/chore/bump-version-2026-6-4
stephamie7 Jun 4, 2026
6d4ac3b
chore: merge main into dev after v2026.06.03 release
xiami762 Jun 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion flocks/channel/inbound/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -847,19 +847,24 @@ async def _handle_session_command(
scope_override: Optional[str],
) -> None:
from flocks.session.session import Session
from flocks.channel.inbound.session_binding import _build_title
from flocks.channel.inbound.session_binding import (
_build_title,
resolve_channel_session_owner_kwargs,
)

session = await Session.get_by_id(binding.session_id)
if not session:
await callbacks.deliver_text("当前会话不存在,请发送一条普通消息后重试。")
return

owner_kwargs = await resolve_channel_session_owner_kwargs(session)
new_session = await Session.create(
project_id=session.project_id,
directory=session.directory,
title=_build_title(msg),
agent=session.agent,
**Session.inherited_model_kwargs(session),
**owner_kwargs,
)
new_binding = await self.binding_service.rebind(
msg,
Expand Down
42 changes: 42 additions & 0 deletions flocks/channel/inbound/session_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,46 @@ class SessionBinding:
# corruption vector.
_db_owner_pid: Optional[int] = None


async def resolve_channel_session_owner_kwargs(source_session=None) -> dict[str, str]:
"""Return ownership kwargs for a channel-created session.

Channel dispatch runs outside the HTTP auth middleware, so
``Session.create`` cannot infer the owner from ``current_auth_user``.
When an existing channel session is being replaced, preserve its owner.
Otherwise, attach new channel sessions to the local admin if one exists.
Installs without local accounts remain ownerless for backward-compatible
no-login operation.
"""
owner_user_id = getattr(source_session, "owner_user_id", None) if source_session else None
owner_username = getattr(source_session, "owner_username", None) if source_session else None
if owner_user_id or owner_username:
owner_kwargs: dict[str, str] = {}
if owner_user_id:
owner_kwargs["owner_user_id"] = str(owner_user_id)
if owner_username:
owner_kwargs["owner_username"] = str(owner_username)
return owner_kwargs

try:
from flocks.auth.service import AuthService

if not await AuthService.has_users():
return {}
users = await AuthService.list_users()
except Exception as exc:
log.warn("channel.owner.resolve_failed", {"error": str(exc)})
return {}

admin = next((user for user in users if getattr(user, "role", None) == "admin"), None)
if admin is None:
return {}
return {
"owner_user_id": str(admin.id),
"owner_username": str(admin.username),
}


# Register channel_bindings DDL with Storage so the tables are created
# during Storage.init() as well (idempotent CREATE IF NOT EXISTS).
try:
Expand Down Expand Up @@ -488,11 +528,13 @@ async def _create_session(
from flocks.session.session import Session

title = _build_title(msg)
owner_kwargs = await resolve_channel_session_owner_kwargs()
session = await Session.create(
project_id="channel",
directory=_resolve_session_directory(directory),
title=title,
agent=default_agent,
**owner_kwargs,
)
return session.id

Expand Down
2 changes: 1 addition & 1 deletion flocks/server/routes/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def _session_to_response(session: SessionModel) -> SessionResponse:
current_user = get_current_auth_user()
can_write = SessionPolicy.can_write(session, current_user)
can_delete = SessionPolicy.can_delete(session, current_user)
is_shared = SessionPolicy.is_local_shared(session)
is_shared = SessionPolicy.is_shared(session)

return SessionResponse(
id=session.id,
Expand Down
33 changes: 29 additions & 4 deletions flocks/session/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ def is_local_shared(session: "SessionInfo") -> bool:
return False
return bool(metadata.get("shared_local"))

@staticmethod
def _has_no_owner(session: "SessionInfo") -> bool:
return not session.owner_user_id and not session.owner_username

@classmethod
def is_shared(cls, session: "SessionInfo") -> bool:
"""Whether the session is explicitly shared to all local users.

Ownerless sessions are a legacy / unauthenticated compatibility state,
not a sharing state. The UI badge should only reflect explicit sharing.
"""
return cls.is_local_shared(session)

@staticmethod
def _shared_read_user_ids(session: "SessionInfo") -> set[str]:
metadata = getattr(session, "metadata", None)
Expand All @@ -77,30 +90,42 @@ def can_read(cls, session: "SessionInfo", user: Optional["AuthUser"] = None) ->
Whether the session should be visible in listings / fetch.

- No auth context (CLI/internal runtime): keep legacy permissive behaviour.
- Logged-in users: owner or local-shared readers.
- Logged-in users: owner, local-shared readers, shared readers, or admins
managing ownerless legacy/channel sessions.
"""
resolved = cls._resolve_user(user)
if resolved is None:
return True
if cls.is_owner(session, resolved):
return True
if cls._has_no_owner(session) and cls.is_admin(resolved):
return True
return cls.is_shared_read_only(session, resolved)

@classmethod
def can_write(cls, session: "SessionInfo", user: Optional["AuthUser"] = None) -> bool:
"""
Session write permission.

Shared users are read-only. Only owner can write.
Owner can always write. Admins may repair/manage ownerless sessions
accumulated before local ownership was available.
"""
resolved = cls._resolve_user(user)
if resolved is None:
return False
return cls.is_owner(session, resolved)
if cls.is_owner(session, resolved):
return True
if cls._has_no_owner(session) and cls.is_admin(resolved):
return True
return False

@classmethod
def can_delete(cls, session: "SessionInfo", user: Optional["AuthUser"]) -> bool:
resolved = cls._resolve_user(user)
if resolved is None:
return False
return cls.is_owner(session, resolved)
if cls.is_owner(session, resolved):
return True
if cls._has_no_owner(session) and cls.is_admin(resolved):
return True
return False
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "flocks"
version = "v2026.6.3"
version = "v2026.6.4"
description = "AI-Native SecOps platform with multi-agent collaboration"
authors = [
{name = "Flocks Team", email = "team@example.com"}
Expand Down
70 changes: 70 additions & 0 deletions tests/channel/test_unified_prompt_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from flocks.channel.inbound.session_binding import (
SessionBindingService,
_resolve_session_directory,
resolve_channel_session_owner_kwargs,
)
from flocks.config.config import ChannelConfig

Expand Down Expand Up @@ -228,6 +229,75 @@ async def _fake_create(**kwargs):
assert captured["directory"] == "/instance/dir"


class TestChannelSessionOwnerPropagation:
@pytest.mark.asyncio
async def test_create_session_assigns_local_admin_owner(self):
captured = {}

class _StubSession:
id = "ses_admin_owned"

async def _fake_create(**kwargs):
captured.update(kwargs)
return _StubSession()

admin = SimpleNamespace(id="usr_admin", username="admin", role="admin")

with patch("flocks.session.session.Session.create", new=_fake_create), \
patch("flocks.auth.service.AuthService.has_users", new=AsyncMock(return_value=True)), \
patch("flocks.auth.service.AuthService.list_users", new=AsyncMock(return_value=[admin])):
sid = await SessionBindingService._create_session(
_msg(),
default_agent="rex",
directory="/explicit/dir",
)

assert sid == "ses_admin_owned"
assert captured["owner_user_id"] == "usr_admin"
assert captured["owner_username"] == "admin"

@pytest.mark.asyncio
async def test_create_session_stays_ownerless_without_local_accounts(self):
captured = {}

class _StubSession:
id = "ses_ownerless"

async def _fake_create(**kwargs):
captured.update(kwargs)
return _StubSession()

with patch("flocks.session.session.Session.create", new=_fake_create), \
patch("flocks.auth.service.AuthService.has_users", new=AsyncMock(return_value=False)), \
patch("flocks.auth.service.AuthService.list_users", new=AsyncMock()) as list_users:
sid = await SessionBindingService._create_session(
_msg(),
default_agent="rex",
directory="/explicit/dir",
)

assert sid == "ses_ownerless"
assert "owner_user_id" not in captured
assert "owner_username" not in captured
list_users.assert_not_awaited()

@pytest.mark.asyncio
async def test_owner_kwargs_preserve_existing_session_owner(self):
existing = SimpleNamespace(
owner_user_id="usr_existing",
owner_username="existing",
)

with patch("flocks.auth.service.AuthService.has_users", new=AsyncMock()) as has_users:
owner_kwargs = await resolve_channel_session_owner_kwargs(existing)

assert owner_kwargs == {
"owner_user_id": "usr_existing",
"owner_username": "existing",
}
has_users.assert_not_awaited()


# ---------------------------------------------------------------------------
# 2. default_agent unification
# ---------------------------------------------------------------------------
Expand Down
19 changes: 19 additions & 0 deletions tests/session/test_session_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,22 @@ def test_can_read_local_shared_visible_to_all_local_users():
assert SessionPolicy.can_read(session, owner) is True
assert SessionPolicy.can_read(session, admin) is True
assert SessionPolicy.can_read(session, stranger) is True


def test_ownerless_session_is_not_marked_shared():
session = _make_session(owner_user_id=None, owner_username=None)
assert SessionPolicy.is_shared(session) is False


def test_ownerless_session_admin_can_manage_but_member_cannot():
admin = _make_user(user_id="usr_admin", username="admin", role="admin")
member = _make_user(user_id="usr_member", username="member", role="member")
session = _make_session(owner_user_id=None, owner_username=None)

assert SessionPolicy.can_read(session, admin) is True
assert SessionPolicy.can_write(session, admin) is True
assert SessionPolicy.can_delete(session, admin) is True

assert SessionPolicy.can_read(session, member) is False
assert SessionPolicy.can_write(session, member) is False
assert SessionPolicy.can_delete(session, member) is False
4 changes: 4 additions & 0 deletions tests/user_workflow/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# 本地用户自定义 workflow 测试目录
# 默认忽略目录内所有内容,仅保留当前 .gitignore 以便目录可被提交。
*
!.gitignore
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading