Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
180 commits
Select commit Hold shift + click to select a range
5350e41
fix(log): restructure log directory retention
Jun 3, 2026
2282ec2
fix(log): address retention review feedback
Jun 4, 2026
602cc5a
Merge pull request #368 from AgentFlocks/fix/logging-retention-stability
xiami762 Jun 4, 2026
244d132
fix(updater): avoid Windows project install during dependency sync (#…
xiami762 Jun 4, 2026
025e4f3
fix(tdp): normalize base_url by stripping UI/API path suffixes (#376)
xiami762 Jun 5, 2026
decc503
feat(workspace): jsonl text preview and Files tab load stability (#367)
xiami762 Jun 5, 2026
53c7b2e
fix(session): recover orphaned running tools after server restart (#370)
xiami762 Jun 5, 2026
b09b9ea
feat(webui): improve workflow graph layout and editor edge UX (#379)
xiami762 Jun 5, 2026
f27ab70
Feat/workflow trigger integration (#375)
xiami762 Jun 5, 2026
8aaa1bf
feat(device): add multi-room support and i18n for device integration
duguwanglong Jun 5, 2026
281611f
Merge pull request #380 from AgentFlocks/feat/device-multi-room
xiami762 Jun 5, 2026
b29d961
feat(device): auto-inject device_id into tool test params
duguwanglong Jun 5, 2026
f5b1320
Merge pull request #382 from AgentFlocks/feat/device-tool-test-auto-d…
xiami762 Jun 5, 2026
bb0d8c3
fix(device): preserve device_id when applying a test fixture
duguwanglong Jun 5, 2026
767aa0b
fix(device): sync toolEnabled state into ToolDetailModal on open
duguwanglong Jun 5, 2026
e346088
feat(webui,device,web2cli): custom device access wizard and credentia…
xiami762 Jun 5, 2026
4d43671
fix(credential-context): resolve config override for versioned SERVIC…
duguwanglong Jun 5, 2026
57664c8
feat(session): add concise workflow tool output (#381)
xiami762 Jun 5, 2026
d40ca8d
fix: correct merge order and operator precedence from review
duguwanglong Jun 5, 2026
7c849b1
refactor(credential-context): address S2/S3/S4 from PR review
duguwanglong Jun 5, 2026
0f10cd0
test: fix ContextVar isolation and remove unused import
duguwanglong Jun 5, 2026
78ca7ed
Merge pull request #383 from AgentFlocks/fix/device-tool-test-fixture…
xiami762 Jun 5, 2026
4daef11
feat(channel): complete bidirectional file/image support for wecom, d…
xiami762 Jun 6, 2026
193fc55
docs(channel): add review guide for file/image attachment support
xiami762 Jun 6, 2026
cd6a3a8
docs(channel): remove standalone review guide (move content into PR d…
xiami762 Jun 6, 2026
2c1480c
docs(contributing): restructure PR description guidelines around chan…
xiami762 Jun 6, 2026
f0b57df
build(deps): bump starlette to >=1.0.1
xiami762 Jun 7, 2026
c3ac89d
fix(webui): remove global flex-col wrapper from standard pages (#384)
xiami762 Jun 8, 2026
47be58e
feat(user-defined-pages): add custom page runtime (#389)
Jieatgit Jun 8, 2026
e061ffa
Fix Telegram file roundtrip media upload
xiami762 Jun 8, 2026
e3cfcda
Fix channel media filename and caption handling
xiami762 Jun 8, 2026
de7b075
fix(provider): thinking params (#387)
xiami762 Jun 8, 2026
40c1c5f
feat(workflow): improve diagram usability
duguwanglong Jun 8, 2026
15e0dc0
chore(gitignore): ignore .codex/ directory
xiami762 Jun 8, 2026
a46c244
Merge pull request #393 from AgentFlocks/chore/ignore-codex
stephamie7 Jun 8, 2026
d107d0c
Fix fallback port conflict detection (#394)
xiami762 Jun 9, 2026
6839175
Improve chat model picker controls (#391)
xiami762 Jun 9, 2026
532bf38
Merge pull request #386 from AgentFlocks/feat/channel-file-roundtrip
xiami762 Jun 9, 2026
67b55cd
refactor: unify subagent delegation under delegate_task (#385)
xiami762 Jun 9, 2026
ea2b7d0
feat(workflow): support config-driven publish templates
duguwanglong Jun 9, 2026
54dfd66
feat(workflow): add editable workflow document flow
duguwanglong Jun 9, 2026
ddd44d6
Merge branch 'dev' of github.com:AgentFlocks/flocks into feat/workflo…
duguwanglong Jun 9, 2026
e00226a
feat(session): refine chat selector controls
duguwanglong Jun 9, 2026
cc7712f
Fix channel image preview rendering
xiami762 Jun 9, 2026
1f48e4b
Fix DingTalk inbound file detection
xiami762 Jun 9, 2026
24c5bb9
feat(session): refine agent and model selectors
duguwanglong Jun 9, 2026
e58e324
Merge pull request #397 from AgentFlocks/fix/channel-file-image-render
stephamie7 Jun 9, 2026
aed9d41
feat(session): add context usage indicator
duguwanglong Jun 9, 2026
4cb2601
fix(session): stabilize selector button widths
duguwanglong Jun 9, 2026
4638011
fix(session): localize selector control widths
duguwanglong Jun 9, 2026
bd34a35
fix(session): cap adaptive agent selector width
duguwanglong Jun 9, 2026
970c8d1
fix(session): stabilize streaming status (#396)
xiami762 Jun 9, 2026
60a9e94
Merge pull request #398 from AgentFlocks/input-selector-visual-tuning
xiami762 Jun 9, 2026
707c801
Reduce workflow default log noise (#399)
xiami762 Jun 9, 2026
4d798c2
fix: clarify browser doctor and session streaming status (#400)
xiami762 Jun 9, 2026
f0c4e07
feat(workflow): add publish config templates
duguwanglong Jun 9, 2026
d373579
revert(workflow): restore pre-pr398 workflow experience
duguwanglong Jun 9, 2026
f51a309
Merge pull request #401 from AgentFlocks/codex-revert-pr398-workflow
xiami762 Jun 9, 2026
3e419ac
feat(device): unify device plugin intake (#392)
xiami762 Jun 9, 2026
c4c4d0a
feat(workflows): improve workflow configuration UX
duguwanglong Jun 9, 2026
d31716c
fix(channel): preserve plugin instances across load_all (#402)
xiami762 Jun 10, 2026
cd47968
Fix session model persistence (#403)
xiami762 Jun 10, 2026
3a3a033
feat(device): Improve device integration auto-sync (#405)
xiami762 Jun 10, 2026
a4d7090
fix: default model reasoning to enabled (#406)
xiami762 Jun 10, 2026
247eadf
fix skill install from GitHub blob URLs (#407)
xiami762 Jun 10, 2026
4e7744b
chore/update-version-2026-6-10 (#408)
stephamie7 Jun 10, 2026
e889883
feat(workflows): improve workbench publishing guidance
duguwanglong Jun 10, 2026
a3e54e2
Fix device refresh sync flow (#410)
xiami762 Jun 10, 2026
a79b7e6
feat(workflow): refine builder workbench experience
duguwanglong Jun 10, 2026
5e81171
docs: reorganize web2cli reference guides (#411)
xiami762 Jun 10, 2026
18a911f
feat(tool): Add l IM send message tool (#404)
xiami762 Jun 11, 2026
796e01b
fix: bound SSH connection pool (#415)
xiami762 Jun 11, 2026
d1e5589
feat(device): add 360 FW v5.5 integration
magicmagicspider Jun 8, 2026
9bb498e
feat(session): refine todo tool rendering
duguwanglong Jun 11, 2026
fab2172
Fix updater cancellation and session statistics (#414)
xiami762 Jun 11, 2026
7d63769
Support structured ACP command arguments (#412)
JohnYin-hub Jun 11, 2026
88be504
Merge pull request #416 from AgentFlocks/feat/todo-write-flat-ui
xiami762 Jun 11, 2026
f8c1be4
fix(sip): preserve plugin result output
duguwanglong Jun 11, 2026
e2ec55e
Fix Windows updater venv rotation (#413)
xiami762 Jun 11, 2026
24166bd
Merge pull request #419 from AgentFlocks/fix/sip-toolresult-compat
xiami762 Jun 11, 2026
ee3f29b
Fix skill install timeouts and session streaming state (#418)
xiami762 Jun 11, 2026
d8c24cb
fix(updater): preserve restart after upgrade disconnect
Jun 11, 2026
6b040a4
Merge pull request #422 from AgentFlocks/fix-updater-restart-handover
stephamie7 Jun 11, 2026
0aeb7e8
fix: handle windows image paths and default workflow history
Jun 11, 2026
5baede8
Merge pull request #423 from AgentFlocks/fix-windows-image-rendering-…
stephamie7 Jun 11, 2026
1ffbed3
feat(workflow): improve workbench and publishing flows
duguwanglong Jun 11, 2026
cc5dd34
Merge branch 'dev' of github.com:AgentFlocks/flocks into feat/workflo…
duguwanglong Jun 11, 2026
791ea92
fix(workflow): show guide info tooltip above panels
duguwanglong Jun 11, 2026
8a5d47c
fix(updater): restore restart argv reconstruction
xiami762 Jun 11, 2026
8429de2
chore(updater): remove unused service restart argv helper
xiami762 Jun 11, 2026
d3e63b8
Add Windows updater restart handoff
xiami762 Jun 11, 2026
17b3693
fix(updater): tune restart handoff timeouts
xiami762 Jun 11, 2026
f25237f
Merge pull request #426 from AgentFlocks/fix/updater-restart-argv
stephamie7 Jun 12, 2026
a165330
fix(session): correct context usage after compaction
duguwanglong Jun 12, 2026
9033809
fix(session): handle multimodal image paths across platforms
Jun 12, 2026
6b5a138
fix(provider): enable vision for ThreatBook MiniMax M3
Jun 12, 2026
e0b18c9
fix(workflow): preserve cron schedules and refine guides
duguwanglong Jun 12, 2026
0a7e7cf
Merge pull request #427 from AgentFlocks/fix-multimodal-image-paths
stephamie7 Jun 12, 2026
77b0411
fix(updater): defer dependency sync to restart handoff
Jun 12, 2026
9114971
chore(skills): remove unused cybersecurity skills
Jun 12, 2026
c31fe0b
chore(skills): remove supply chain malware analysis skill
Jun 12, 2026
4d47fe8
feat(workflow): improve workflow authoring usability
duguwanglong Jun 12, 2026
98db373
fix(provider): support configured OpenAI-compatible extra_body (#424)
xiami762 Jun 12, 2026
a7248bb
Merge pull request #425 from AgentFlocks/feat/workflow-usability
xiami762 Jun 12, 2026
1300597
Resolve custom model limits automatically (#421)
xiami762 Jun 12, 2026
9a56308
Merge branch 'dev' of github.com:AgentFlocks/flocks into fix/context-…
duguwanglong Jun 12, 2026
06fba7c
fix(session): attribute context usage breakdown
duguwanglong Jun 12, 2026
9baaed1
fix(session): exclude delegated tools from usage calls
duguwanglong Jun 12, 2026
689f860
fix(session): include system prompt usage segment
duguwanglong Jun 12, 2026
850f000
fix(session): count tool schemas as definitions
duguwanglong Jun 12, 2026
51e95e6
fix(session): show zero agent delegation usage
duguwanglong Jun 12, 2026
7d055c7
fix(session): split reasoning from conversation usage
duguwanglong Jun 12, 2026
c2bef6d
fix(session): refine context usage and compaction divider
duguwanglong Jun 12, 2026
5e492a2
Merge pull request #428 from AgentFlocks/fix-updater-handoff-dependen…
stephamie7 Jun 12, 2026
f01aa01
fix(session): constrain compaction divider width
duguwanglong Jun 12, 2026
ae0bf8d
fix(session): show compacted history in timeline
duguwanglong Jun 12, 2026
acb4643
fix(session): reset context usage after compaction
duguwanglong Jun 12, 2026
57691d7
fix(session): stabilize context usage popover
duguwanglong Jun 12, 2026
7c11839
fix(session): compact only new turns after summary
duguwanglong Jun 12, 2026
30e7ec7
fix(session): refresh context usage on compaction failure
duguwanglong Jun 12, 2026
173f291
fix(session): keep context usage during compaction
duguwanglong Jun 12, 2026
7b6c556
fix(session): reduce context usage refresh overhead
duguwanglong Jun 12, 2026
14dc1f7
Merge pull request #429 from AgentFlocks/fix/context-usage-popover
xiami762 Jun 12, 2026
bed5ad0
Merge pull request #420 from magicmagicspider/feat/360-fw-device
xiami762 Jun 15, 2026
c995cca
Add WebUI dark mode support (#430)
xiami762 Jun 15, 2026
719b956
fix(workflow): collapse invisible chat process markers
duguwanglong Jun 15, 2026
a22b96c
fix(workflow): stabilize compact chat bubble width
duguwanglong Jun 15, 2026
586f6ec
Merge branch 'dev' of github.com:AgentFlocks/flocks into fix/workflow…
duguwanglong Jun 15, 2026
8ca6b87
fix(session): expand grouped process steps by default
duguwanglong Jun 15, 2026
b20b704
Merge pull request #432 from AgentFlocks/fix/workflow-process-collapse
xiami762 Jun 15, 2026
c95b990
fix(session): enforce disabled agent availability (#433)
xiami762 Jun 15, 2026
caf9239
feat(workflow): improve publish runtime configuration
duguwanglong Jun 15, 2026
ac82700
feat: add persistent goal mode (#431)
xiami762 Jun 15, 2026
4ab9d53
Merge pull request #434 from AgentFlocks/feat/workflow-publish-ui
xiami762 Jun 15, 2026
7062f1d
feat(webui): add guided Rex creation for capabilities
duguwanglong Jun 15, 2026
3fc1c90
feat(workflow): refine overview run experience
duguwanglong Jun 15, 2026
721add9
feat(webui): refine guided capability workbench
duguwanglong Jun 15, 2026
54724ef
fix(webui): handle guided workbench edge cases
duguwanglong Jun 15, 2026
1e55fb7
fix(webui): load user page bundles with credentials
duguwanglong Jun 15, 2026
3be2ec5
fix: enforce workflow API keys and restore agent tests
duguwanglong Jun 15, 2026
40b36fd
Merge pull request #436 from AgentFlocks/feat/guided-capability-creation
xiami762 Jun 15, 2026
0f80fd6
Reduce workflow progress storage contention (#435)
xiami762 Jun 15, 2026
883b3f1
fix(user-defined-pages): handle client disconnects in page api
duguwanglong Jun 16, 2026
834fc92
Merge pull request #438 from AgentFlocks/feat/workflow-run-followup
xiami762 Jun 16, 2026
04b6bb6
fix: preserve device draft config during tests (#437)
xiami762 Jun 16, 2026
a3b242e
fix(workflow): improve API service recovery
Jun 16, 2026
e4de7a4
fix(workflow): prevent workbench interaction timeouts
duguwanglong Jun 16, 2026
be7d651
Merge pull request #439 from AgentFlocks/fix-workflow-api-service-rec…
stephamie7 Jun 16, 2026
ffc7f3a
fix: improve device plugin loading and config defaults
Jun 17, 2026
09c4991
fix(webui): collapse entity workbench process details
duguwanglong Jun 17, 2026
8b5081b
fix: load package entry points for tool plugins
Jun 17, 2026
55771ac
fix: align device tool switch semantics
Jun 17, 2026
62f40e8
fix(webui): align entity Rex workbench with workflow
duguwanglong Jun 17, 2026
a234dd4
fix(webui): document workflow config API auth
duguwanglong Jun 17, 2026
3e5bd5d
Merge pull request #441 from AgentFlocks/fix-device-plugin-refresh
stephamie7 Jun 17, 2026
c9ca51d
fix(workflow): stabilize API service publishing
duguwanglong Jun 17, 2026
25ff7ed
Merge pull request #442 from AgentFlocks/fix/workbench-session-collapse
stephamie7 Jun 17, 2026
2161941
Merge pull request #440 from AgentFlocks/fix/workflow-workbench-timeouts
stephamie7 Jun 17, 2026
85a9439
fix(deps): upgrade litellm for CVE-2026-42271
duguwanglong Jun 17, 2026
7ff2d17
Merge pull request #443 from AgentFlocks/fix/litellm-cve-2026-42271
xiami762 Jun 17, 2026
ab457bd
fix(workflow): stabilize docker publish startup
Jun 17, 2026
e996d55
fix: prevent dingtalk stream from blocking event loop
Jun 17, 2026
bb70ce7
Merge pull request #444 from AgentFlocks/fix/docker-workflow-publish
stephamie7 Jun 17, 2026
e4e391a
Merge pull request #445 from AgentFlocks/fix/dingtalk-stream-event-lo…
stephamie7 Jun 17, 2026
737ba82
chore/update-version-2026-6-17 (#446)
stephamie7 Jun 17, 2026
e1b8ad8
Merge branch 'main' into fix/resolve-dev-main-conflicts
duguwanglong Jun 17, 2026
b8ab62a
Merge pull request #447 from AgentFlocks/fix/resolve-dev-main-conflicts
stephamie7 Jun 17, 2026
670261a
fix(cli): constrain typer below incompatible release
duguwanglong Jun 17, 2026
12238f6
Fix workflow integration tab JSX closure
Jun 17, 2026
d6b401f
Merge pull request #449 from AgentFlocks/fix/cli-and-ci-failures
xiami762 Jun 17, 2026
f8b811f
Remove duplicate WebUI declarations
Jun 17, 2026
9d9006b
Merge pull request #450 from AgentFlocks/fix/workflow-integration-tab…
stephamie7 Jun 17, 2026
0f77242
fix(ci): restore FlocksHub validation
duguwanglong Jun 17, 2026
960aac2
Merge pull request #452 from AgentFlocks/fix/flockshub-ci-validation
stephamie7 Jun 17, 2026
885f79c
perf: optimize session page loading (#453)
xiami762 Jun 18, 2026
2304d35
fix: allow nested delegation and clean Windows installs (#456)
xiami762 Jun 18, 2026
754d5bf
chore/update-version-2026-6-18 (#457)
stephamie7 Jun 18, 2026
2174abc
v2026.06.17 (#451) (#458)
stephamie7 Jun 18, 2026
c2c4abd
Merge main into dev and resolve conflicts
Jun 18, 2026
3212465
Merge pull request #460 from AgentFlocks/resolve-main-dev-conflicts
stephamie7 Jun 18, 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
476 changes: 284 additions & 192 deletions flocks/server/routes/session.py

Large diffs are not rendered by default.

268 changes: 208 additions & 60 deletions flocks/session/message.py

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions flocks/session/streaming/stream_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ def _resolve_tool_error(result: ToolResult) -> str:
return "Unknown error"


def _invalid_tool_call_parse_error(tool_input: Dict[str, Any]) -> tuple[str, str]:
"""Return original tool name and user-facing parse error for invalid tool events."""
original_tool = str(tool_input.get("tool") or "unknown").strip() or "unknown"
raw_error = str(tool_input.get("error") or "Failed to parse tool arguments.").strip()
prefix = f"Failed to parse tool arguments for {original_tool}"
if raw_error.startswith(prefix):
return original_tool, raw_error
return original_tool, f"{prefix}: {raw_error}"


@dataclass
class ToolCallState:
"""State for tracking tool calls"""
Expand Down Expand Up @@ -508,6 +518,56 @@ async def _handle_tool_call(self, event: ToolCallEvent) -> None:

tool_state = self.tool_calls[tool_call_id]
tool_state.input = tool_input

if tool_name == "invalid":
original_tool, parse_error = _invalid_tool_call_parse_error(tool_input)
tool_state.name = original_tool
tool_state.status = "error"
tool_state.error = parse_error

tool_error_time = int(datetime.now().timestamp() * 1000)
error_state = ToolStateError(
status="error",
input=tool_input,
error=parse_error,
time={"start": tool_error_time, "end": tool_error_time},
)
error_part = ToolPart(
id=tool_state.part_id,
sessionID=self.session_id,
messageID=self.assistant_message.id,
type="tool",
callID=tool_call_id,
tool=original_tool,
state=error_state,
)
await Message.store_part(self.session_id, self.assistant_message.id, error_part)

if self.event_publish_callback:
await self.event_publish_callback("message.part.updated", {
"part": {
"id": tool_state.part_id,
"messageID": self.assistant_message.id,
"sessionID": self.session_id,
"type": "tool",
"callID": tool_call_id,
"tool": original_tool,
"state": {
"status": "error",
"input": tool_input,
"error": parse_error,
"time": {"start": tool_error_time, "end": tool_error_time},
},
},
})

log.warn("stream.tool_call.invalid_arguments", {
"tool_call_id": tool_call_id,
"tool_name": original_tool,
"error": parse_error,
})
return

tool_state.status = "running"

# Update ToolPart to running state (like Flocks's Session.updatePart)
Expand Down
8 changes: 0 additions & 8 deletions flocks/tool/agent/delegate_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,6 @@ async def _subagent_session_permissions(agent_name: str) -> list:
from flocks.agent.registry import Agent
from flocks.session.session import PermissionRule as SessionPermissionRule

def deny_nested_delegation() -> list:
return [
SessionPermissionRule(permission="delegate_task", action="deny", pattern="*"),
SessionPermissionRule(permission="task", action="deny", pattern="*"),
]

try:
agent = await Agent.get(agent_name)
except Exception as exc:
Expand All @@ -67,7 +61,6 @@ def deny_nested_delegation() -> list:
pattern=getattr(rule, "pattern", None) or "*",
)
)
rules.extend(deny_nested_delegation())
return rules

if agent_name == "prometheus":
Expand All @@ -78,7 +71,6 @@ def deny_nested_delegation() -> list:
])
elif not rules:
rules.append(SessionPermissionRule(permission="question", action="deny", pattern="*"))
rules.extend(deny_nested_delegation())
return rules


Expand Down
20 changes: 17 additions & 3 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,22 @@ function Unblock-InstallFiles {
}
}

function Remove-ExistingInstallDirectory {
param([string]$TargetDir)

if ([string]::IsNullOrWhiteSpace($TargetDir) -or -not (Test-Path -LiteralPath $TargetDir)) {
return
}

Write-Info "Removing previous Flocks installation: $TargetDir"
try {
Remove-Item -LiteralPath $TargetDir -Recurse -Force -ErrorAction Stop
}
catch {
Fail "Failed to remove previous Flocks installation directory '$TargetDir'. Close any running Flocks process and retry. Error: $($_.Exception.Message)"
}
}

function Invoke-WorkspaceInstaller {
param(
[string]$InstallerPath,
Expand Down Expand Up @@ -252,9 +268,7 @@ function Main {
if ((-not [string]::IsNullOrWhiteSpace($installParent)) -and -not (Test-Path -LiteralPath $installParent)) {
New-Item -ItemType Directory -Path $installParent -Force | Out-Null
}
if (Test-Path $InstallDir) {
Remove-Item -Path $InstallDir -Recurse -Force
}
Remove-ExistingInstallDirectory -TargetDir $InstallDir
Copy-Item -Path $projectRoot -Destination $InstallDir -Recurse -Force
Unblock-InstallFiles -TargetDir $InstallDir

Expand Down
20 changes: 17 additions & 3 deletions install_zh.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,22 @@ function Unblock-InstallFiles {
}
}

function Remove-ExistingInstallDirectory {
param([string]$TargetDir)

if ([string]::IsNullOrWhiteSpace($TargetDir) -or -not (Test-Path -LiteralPath $TargetDir)) {
return
}

Write-Info "正在删除历史 Flocks 安装目录: $TargetDir"
try {
Remove-Item -LiteralPath $TargetDir -Recurse -Force -ErrorAction Stop
}
catch {
Fail "无法删除历史 Flocks 安装目录 '$TargetDir'。请关闭正在运行的 Flocks 进程后重试。错误: $($_.Exception.Message)"
}
}

function Invoke-WorkspaceInstaller {
param(
[string]$InstallerPath,
Expand Down Expand Up @@ -291,9 +307,7 @@ function Main {
if ((-not [string]::IsNullOrWhiteSpace($installParent)) -and -not (Test-Path -LiteralPath $installParent)) {
New-Item -ItemType Directory -Path $installParent -Force | Out-Null
}
if (Test-Path $InstallDir) {
Remove-Item -Path $InstallDir -Recurse -Force
}
Remove-ExistingInstallDirectory -TargetDir $InstallDir
Copy-Item -Path $projectRoot -Destination $InstallDir -Recurse -Force
Unblock-InstallFiles -TargetDir $InstallDir

Expand Down
25 changes: 25 additions & 0 deletions packaging/windows/flocks-setup.iss
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,31 @@ Type: filesandordirs; Name: "{app}\*"
Type: dirifempty; Name: "{app}"

[Code]
procedure RemoveExistingFlocksRepoDir;
var
ExistingRepoDir: string;
begin
ExistingRepoDir := ExpandConstant('{app}\flocks');
if not DirExists(ExistingRepoDir) then
exit;

WizardForm.StatusLabel.Caption := 'Removing previous Flocks installation...';
if not DelTree(ExistingRepoDir, True, True, True) then
begin
RaiseException(
'Failed to remove previous Flocks installation directory:' + #13#10 +
ExistingRepoDir + #13#10 + #13#10 +
'Please close any running Flocks process and retry.'
);
end;
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
RemoveExistingFlocksRepoDir;
end;

function IsUnderBaseDir(const CandidateDir, BaseDir: string): Boolean;
var
NormalizedCandidate: string;
Expand Down
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.17"
version = "v2026.6.18"
description = "AI-Native SecOps platform with multi-agent collaboration"
authors = [
{name = "Flocks Team", email = "team@example.com"}
Expand Down
14 changes: 14 additions & 0 deletions tests/scripts/test_install_script_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,20 @@ def test_windows_bootstrap_installers_only_create_missing_parent_directories() -
assert "Test-Path -LiteralPath $installParent" in script


def test_windows_bootstrap_installers_remove_existing_install_directory_before_copy() -> None:
for path in (
REPO_ROOT / "install.ps1",
REPO_ROOT / "install_zh.ps1",
):
script = path.read_text(encoding="utf-8-sig")
assert "function Remove-ExistingInstallDirectory" in script
assert "Remove-Item -LiteralPath $TargetDir -Recurse -Force -ErrorAction Stop" in script
assert "Remove-ExistingInstallDirectory -TargetDir $InstallDir" in script
assert script.index("Remove-ExistingInstallDirectory -TargetDir $InstallDir") < script.index(
"Copy-Item -Path $projectRoot -Destination $InstallDir -Recurse -Force"
)


def test_main_bash_installer_falls_back_to_nvm_when_brew_is_missing_on_macos() -> None:
script = (SCRIPT_DIR / "install.sh").read_text(encoding="utf-8")
script_without_main = re.sub(r'\nmain "\$@"\s*$', "\n", script)
Expand Down
89 changes: 89 additions & 0 deletions tests/server/routes/test_session_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,60 @@ async def test_list_sessions_roots_excludes_children(self, client: AsyncClient):
assert parent_id in ids
assert child_id not in ids

@pytest.mark.asyncio
async def test_list_sessions_light_manager_filters_and_omits_heavy_fields(self, client: AsyncClient):
"""Lightweight manager list returns only sidebar metadata."""
from flocks.session.goal import GoalManager

user_resp = await client.post("/api/session", json={"title": "User"})
user_id = user_resp.json()["id"]
workflow_resp = await client.post(
"/api/session",
json={"title": "Workflow", "category": "workflow"},
)
workflow_id = workflow_resp.json()["id"]
task_resp = await client.post(
"/api/session",
json={"title": "Task", "category": "task"},
)
task_id = task_resp.json()["id"]
child_resp = await client.post(
"/api/session",
json={"title": "Child", "parentID": user_id},
)
child_id = child_resp.json()["id"]
await GoalManager.set_goal(user_id, "Do not hydrate in list mode")

resp = await client.get(
"/api/session",
params={"view": "list", "manager": "true", "roots": "true", "limit": "100"},
)

assert resp.status_code == status.HTTP_200_OK
data = resp.json()
ids = {item["id"] for item in data}
assert user_id in ids
assert workflow_id in ids
assert task_id not in ids
assert child_id not in ids

row = next(item for item in data if item["id"] == user_id)
assert set(row) == {
"id",
"title",
"time",
"category",
"parentID",
"provider",
"model",
"model_pinned",
"canWrite",
"canDelete",
"isShared",
}
assert "goal" not in row
assert "summary" not in row

@pytest.mark.asyncio
async def test_get_session(self, client: AsyncClient, session_id: str):
"""GET /api/session/{id} returns the specific session."""
Expand Down Expand Up @@ -289,6 +343,41 @@ async def test_list_messages_uses_preloaded_orphan_recovery_path(
preloaded_recovery.assert_awaited_once()
legacy_recovery.assert_not_called()

@pytest.mark.asyncio
async def test_list_messages_page_uses_lazy_recent_path(
self,
client: AsyncClient,
session_id: str,
monkeypatch: pytest.MonkeyPatch,
):
old_msg = await Message.create(session_id, MessageRole.USER, "old")
mid_msg = await Message.create(session_id, MessageRole.USER, "middle")
new_msg = await Message.create(session_id, MessageRole.ASSISTANT, "new")

async def _fail_full_list(*args, **kwargs):
raise AssertionError("full list_with_parts should not be used for paged reads")

monkeypatch.setattr(Message, "list_with_parts", _fail_full_list)

first_resp = await client.get(
f"/api/session/{session_id}/message",
params={"page": "true", "limit": "2"},
)

assert first_resp.status_code == status.HTTP_200_OK
first_page = first_resp.json()
assert [item["info"]["id"] for item in first_page["items"]] == [mid_msg.id, new_msg.id]
assert first_page["hasMore"] is True
assert first_page["nextBefore"] == mid_msg.id

older_resp = await client.get(
f"/api/session/{session_id}/message",
params={"page": "true", "limit": "2", "before": first_page["nextBefore"]},
)
assert older_resp.status_code == status.HTTP_200_OK
older_page = older_resp.json()
assert [item["info"]["id"] for item in older_page["items"]] == [old_msg.id]
assert older_page["hasMore"] is False

# ===========================================================================
# Delete permissions (single-admin model)
Expand Down
32 changes: 32 additions & 0 deletions tests/session/test_message_parts_persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,38 @@ async def test_legacy_blob_reads_without_migration() -> None:
assert await Storage.list_keys(prefix=f"message_parts:{session_id}:") == []


@pytest.mark.asyncio
async def test_recent_legacy_page_reads_legacy_blob_once(monkeypatch: pytest.MonkeyPatch) -> None:
session_id = "ses_parts_legacy_recent_page"
await _write_legacy_session(
session_id,
{
"msg_a": "legacy a",
"msg_b": "legacy b",
"msg_c": "legacy c",
},
)

original_get = Storage.get
legacy_blob_reads = 0

async def counting_get(key: str):
nonlocal legacy_blob_reads
if key == f"message_parts:{session_id}":
legacy_blob_reads += 1
return await original_get(key)

monkeypatch.setattr(Storage, "get", counting_get)

messages, has_more, next_before = await Message.list_recent_with_parts(session_id, limit=3)

assert [message.info.id for message in messages] == ["msg_a", "msg_b", "msg_c"]
assert [message.parts[0].text for message in messages] == ["legacy a", "legacy b", "legacy c"]
assert has_more is False
assert next_before is None
assert legacy_blob_reads == 1


@pytest.mark.asyncio
async def test_legacy_session_updates_continue_writing_legacy_blob() -> None:
session_id = "ses_parts_legacy_update"
Expand Down
Loading
Loading