Skip to content

refactor: pre-task-communication cleanup (C1–C14, one commit per item) - #229

Merged
juacker merged 28 commits into
mainfrom
cleanup/pre-task-communication
Oct 13, 2026
Merged

juacker merged 28 commits into
mainfrom
cleanup/pre-task-communication

Conversation

@juacker

@juacker juacker commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Pre-feature cleanup for the inter-agent task-communication work. Net about −1,690 lines (+4,646 / −6,335 across 92 files). One commit per item; every commit builds and passes its tests on its own, so any point of the branch can be checked out locally.

Plan and rationale: TODO.md → "Pre-feature cleanup" (workspace), data-model review plans/db-model-review-backend.md / -frontend.md.

Commits (in order)

# Item Commit What
1 C1 drop assistant_session_links Dead table (no producer since ce9751f). New DROP TABLE IF EXISTS migration; removes parent_session_id, include_ancestors, the ancestor-walking page loop (now build_message_page, unit-tested), FE parentSessionId/includeAncestors.
2 C4 drop write-only SessionContext fields tool_scopes (3 writers, 0 readers), inter_agent_call + InterAgentCallContext (never constructed). No migration (serde-defaulted; test session_context_ignores_removed_keys). Also ends a session-row rewrite ping-pong between the two reconcilers that disagreed on tool_scopes.
3 C5 remove unused useAssistantSession hook Dead FE hook (no importer; the live path is getOrCreateWorkspaceSession). −156 lines.
4 C2 task transcript panel follows live task status Workspace.tsx passed a one-time copy (viewingTask) to the panel, so status/title/assignee/sessionId never refreshed after open. Now derives the live task from the polled details.tasks (fallback to the snapshot). Test fails without the fix.
5 C3 call hooks unconditionally in task panel and message block Two rules-of-hooks violations (hook after if (!task) return null; useMemo inside a role branch). No behaviour change. Follow-up: enable react-hooks/rules-of-hooks in eslint.
6 C7 remove scheduled-run connection fallback One enabled connection selected in configured order for each scheduled run; removes the retry loop and output-detection heuristic. Shared selector also serves task runs; config shape stays intact.
7 C6a route session commands through explicit workspace DB Session/run-scoped commands take workspace_id and open only that DB; deletes the session_pool/run_pool/pool_for_new_session probes and five FE-uncalled commands (assistant_create_session, _list_sessions, _load_session_messages, _list_tool_calls, _retry_run). One repository::find_manager_session (newest non-task session, NOT EXISTS, id tie-break) shared by runner and UI. Fix: the scheduled runner could run inside a newer self-assigned task's transcript.
8 C6b drop workspace ids from SessionContext workspace_id/agent_workspace_id removed from the persisted context and the tool context; the id now comes from AssistantDeps/ToolBinding/command args. Tool gate that read agent_workspace_id.is_some() now uses the run's resolved workspace root (test for both branches). emit_event takes the workspace explicitly; event workspaceId is always set. Old rows still load.
9 C13 type workspace agent wrappers as WorkspaceAgentDetail FE wrappers were typed as the wrong response; get can return null. New TS type matches the Rust struct; removes an as unknown as cast; explicit "Agent not found." + test.
10 C6c build run config at run start, drop session reconcile SessionContext keeps only automation_id + CLI session id/provider. New non-persisted RunContext (MCP ids, execution, roster, name, description) built once per run from live config, for API and CLI paths (rides ToolBinding). Deletes the reconcile code; get-or-create never rewrites existing rows (fixes stale CLI session id write-back and spurious updated_at bumps). MCP toggle edits the main agent's config, errors visibly without one. Task runs load the assignee's live config. A run whose agent is gone fails at start.
11 sched release scheduler slot when a scheduled run fails to start Fix (pre-existing): seven early errors in run_next_agent after next_ready skipped complete_agent, so the agent never ran again until restart/config edit. On a start error the slot is now released, the run marked failed and retried in 5 min. Regression test fails without the fix.
12 C6d task runs follow the assignee's live config Deletes the throwaway AgentConfig in assign_task; shared first_enabled_connection. F5: AgentConfig loses selected_mcp_server_ids/execution; dead required_tools/agent_config_to_definition and unread WorkspaceAgentRow fields removed. Behaviour: a task whose assignee was removed fails instead of running on stale settings.
13 C8 delete dead provider/MCP/skill commands and UI, legacy run triggers, notices storage Removes 10 FE-uncalled commands + commands/provider.rs and dead helpers; AppConfig.ai_provider dropped (old config still loads, tested). RunTrigger::Retry/InterAgentCall deleted with a migration mapping legacy rows (quoted and plain forms, tested); inter_agent_call_depth removed. notices_json no longer read/written; AssistantRun.notices gone; "completed with warnings" status kept. Column drop in C9. FE: legacy ai_provider wrappers, ProviderSettings, AgentCard, NoticesBanner, unused provider/MCP/skill wrappers removed; react-hooks/rules-of-hooks is now an error. FE and Rust halves are one commit so ipc_surface holds at every commit.
14 C9 drop write-only task, session and run columns One migration: backfills user_response_at → attention_acknowledged_at (keeps old tasks out of the attention count), then drops workspace_tasks.user_response/user_response_at/result_json, assistant_sessions.kind/title, assistant_runs.notices_json. In-memory notices become one had_warnings flag (RunNotice/RunNoticeKind deleted; same status). SessionKind gone. Migration tests seed old schemas with raw SQL. Agent-visible: task results no longer include resultJson (it duplicated resultSummary).
15 C10 one task mapper and one agent-row loader New assistant/workspace_tasks_repo.rs holds the only task row/columns/load/list/create/update and WorkspaceTaskResponse; the tool-side copies are gone. One WorkspaceAgentRow loader with display_name(). Visible: agent task-tool JSON now equals the UI response (adds display names, omits empty optionals); workspace_listAgents orders Main first then join time; blank agent names show the definition id.
16 C12 return only latest and active runs; drop unused config/summary fields assistant_list_runs/workspace details return ≤2 runs (newest + newest active) instead of the whole history every 5 s poll. Drops WorkspaceConfig.preferred_provider_connection_id (old config loads, tested), WorkspaceAgentSummary.agent_definition_id/role, WorkspaceAgentResponse.role; manager check uses agent.id (tested). Context bar: the provider picker and MCP toggle are disabled while a save runs, the values are reloaded from the backend afterwards, and the bar remounts per workspace (key), so overlapping saves can't happen.
17 C14 store enum columns as plain text Migration unquotes assistant_messages.role, assistant_runs.status/trigger, assistant_tool_calls.status (guarded by json_valid/json_type), rebuilds assistant_compactions with plain CHECKs. enum_text/parse_enum_text bind/read every enum; one ACTIVE_RUN_STATUSES; no hand-written SQL special-cases quoted values any more. Migration test seeds quoted + plain rows. One-time rewrite of all message rows on upgrade.
18 — break started_at ties by id in get_active_run Matches current_runs ordering.
19 — treat completed-with-warnings runs as terminal Fix (pre-existing): assistant_cancel_run did not treat CompletedWithWarnings as finished, so cancelling such a run overwrote it as Cancelled and emitted RunCancelled. One RunStatus::is_active()/is_terminal() rule now.

| 20 | — | move workspace agent roster out of the command layer | Roster code moves to assistant/workspace_agents.rs; the assistant no longer imports from commands/. |
| 21 | — | tool context holds the run context | ToolExecutionContext holds Arc<RunContext> instead of field copies. |
| 22 | — | drop scheduler entries by workspace id on delete | Scheduler instances carry workspace_id; deleting a workspace removes them (otherwise the C6c start-failure path retried them every 5 min). |
| 23 | — | fail a task whose worker run is missing or still active | Fix: such a task was reported as completed. The BLOCKED: rule applies only to terminal runs. |
| 24 | — | AssistantDeps::emit for session events | One helper replaces repeated emit boilerplate (−58 lines). |
| 25 | — | generate workspace agent detail types from Rust | ts-rs bindings replace hand-written TS copies of WorkspaceAgentDetail and related types. |
| 26 | — | drop unused tabId / getActiveSessionForTab from the session store | Leftover of C5. |
| 27 | — | resolve a scheduled agent in its own workspace only | Looks up the agent in its own workspace instead of scanning all of them, so another workspace's unreadable config can't block it. |
| 28 | — | close the workspace database before deleting its directory | Fix: on Windows, deleting a workspace failed with os error 32 because the DB pool was still open. |

Review

Every commit had a read-only Rust or frontend review (production_quality) before the first squash. Four whole-PR review rounds followed. On owner direction (simplicity first), the fixes from those rounds were cut back to the items above (rows 20–28): regressions and changes that remove code. Hardening of older behaviour was dropped from this PR. That covers the unreadable-config sweep on MCP/skill delete, ConfigManager::try_update, MCP secret-clear ordering, the manual-run retry cap and the optimistic-save hook. Each of the 28 commits passes cargo check --all-targets and tsc --noEmit on its own.

Not verified

  • The DROP TABLE migration against a real user DB that still has the table (the migration chain creates then drops it in the test DB).

Upgrade note: migrations 20261013000000–20261015000000 rewrite stored enum/trigger values and drop columns; a binary built from main cannot read a DB migrated by this branch.

The table added in ce9751f never had a producer: its only writer was
assistant_create_session with a parent_session_id the frontend declared
but never sent, and compaction rotates the CLI session id in place rather
than creating a child session. No workspace DB holds a row.

Removed:
- repository: parent_session_id, create_session_rotation_link, the
  include_ancestors recursive-CTE branch of count_session_chain_messages
  (now count_session_messages) and their tests
- commands: CreateAssistantSessionRequest.parent_session_id, the parent
  validation/link creation, LoadSessionMessagesPageRequest.include_ancestors
  and the ancestor-walking page loop (single-session page now, same
  cursor/has_more/total_count semantics)
- frontend: parentSessionId / includeAncestors params and the ancestor
  comments in client.ts, sessionStore.ts, useAssistantSession.ts,
  Workspace.tsx, WorkspaceTaskTranscriptPanel.tsx

New migration 20261012000000_drop_assistant_session_links.sql drops the
table. Bindings regenerated (doc comment on AssistantMessagePage.totalCount).

AssistantMessageCursor.session_id (assistant/types.rs) is still set but
no longer read by the backend: listing is keyed on the request's session.
Kept for wire compatibility; it goes with C6a.
Remove `tool_scopes` and `inter_agent_call` from `SessionContext`, and the
`InterAgentCallContext` struct that only the latter referenced.

- `tool_scopes` was filled at every context build (workspace command,
  scheduled runner, task worker) but never read; tool availability is
  derived from the agent config and execution capabilities instead. The
  `WorkspaceDescriptor.tool_scopes` field that existed only to feed it
  goes with it.
- `inter_agent_call` had no `Some(..)` constructor anywhere; it was only
  copied forward from the existing session row, so it was always `None`.

`inter_agent_call_depth` on `ToolExecutionContext` is unrelated and stays.

No migration: both fields were serde-defaulted and `SessionContext` does
not use `deny_unknown_fields`, so old `context_json` rows deserialize
unchanged.

Side effect observed in review: before this change the two reconcilers
of the manager session disagreed on `tool_scopes` (`commands/workspace.rs`
hard-coded `["web"]`, `agents/runner.rs` wrote `required_tools()`), so
each chat-open / scheduled-tick alternation rewrote the row and emitted
`SessionCreated`. Removing the field removes that ping-pong.
Owner decision (cleanup C7): scheduled runs use exactly one provider
connection. The fallback loop masked provider failures behind extra run
rows, duplicated the queued-followup kick on both exit paths, and will be
superseded by a dispatcher-level retry policy once task communication
lands.

Deleted from agents/runner.rs: resolve_agent_connections,
run_scheduled_agent_with_fallback (loop, fallback_index logging, clippy
cognitive_complexity allow), run_allows_fallback, message_contains_output
and their now-unused imports. Replaced by run_scheduled_agent: one run
row, one turn, one start_queued_followup_if_idle call.

Added AgentConfig::first_enabled_connection, the single resolver for the
"first entry of provider_connection_ids that is enabled" rule; both the
scheduled runner and the workspace-task runner now call it. Config shape
(provider_connection_ids: Vec<String>) and error types are unchanged, so
no migration.

User-visible change: if the first enabled connection fails, the scheduled
run now ends as a single failed run (surfaced by the existing run-error
banner) instead of silently retrying on the next connection. No enabled
connection still yields NoProviderConfigured with no run row. The agent
settings form now states that only the first connection is used.
…e DB

Session- and run-scoped Tauri commands now take a required workspace_id
and open only that workspace's database; a session/run missing there is
"not found" instead of probing every indexed workspace.

- Add workspace_id to get_session, delete_session,
  load_session_messages_page, list_runs, get_tool_call_input/_result,
  send_message, compact_session, delete/edit_queued_message, cancel_run;
  FE wrappers and callers pass the owning workspace.
- Delete the session_pool/run_pool/pool_for_new_session probes.
- Delete commands with no frontend caller (from C8): assistant_create_session,
  assistant_list_sessions, assistant_load_session_messages,
  assistant_list_tool_calls, assistant_retry_run, plus
  repository::list_tool_calls, list_sessions, list_non_task_sessions and the
  FE wrappers.
- Add repository::find_manager_session: newest session for the manager's
  automation_id that is not linked to a workspace_tasks row (kind-agnostic,
  id tie-break). Used by the scheduled runner, workspace details,
  get-or-create, and the workspace_list message count.
- Fix: the scheduled runner picked any session with the manager's
  automation_id, so a self-assigned task's newer session could take the
  scheduled tick and the busy check. It now excludes task sessions, like
  the UI.
- Drop select_workspace_session and its workspace/agent-id filter; the
  selected DB already scopes the workspace, and descriptor.agent_id is
  always None.
Since C6a every session/run command opens the owning workspace's DB
explicitly, so the persisted `workspaceId`/`agentWorkspaceId` in
`SessionContext` were a second, possibly stale, copy of that fact.

- Remove `workspace_id` and `agent_workspace_id` from `SessionContext`
  and from `ToolExecutionContext`; stop writing them in
  `desired_workspace_context`, the MCP-update path, the scheduled runner
  and task sessions.
- `AssistantDeps` now carries `workspace_id` next to its pool (and the
  local MCP `ToolBinding` carries it to CLI tool calls). The engine,
  API/CLI turns, image resolution, CLI cwd, tools (permission/path-grant
  requests, workspace tasks) and `live_agent_description` read it there.
- `available_tools` takes the run's resolved `workspace_root`; the
  workspace-bound tools (history_query, create_vega_chart, bash_exec,
  fs_request_grant) and the web opt-in are gated on it. The system
  prompt's local-execution section is gated the same way and gets the
  workspace id explicitly.
- `emit_event` takes the workspace id explicitly; the event envelope's
  `workspaceId` is now always present (binding: `string`).
- `spawn_run_task`/`start_queued_followup_if_idle` take `AssistantDeps`;
  `fresh_execution_for_session`/`sweep_session_images` take the command's
  workspace id.

Behaviour: unchanged for every session current code writes (all had
`agentWorkspaceId == workspaceId` with a resolvable root). Unreconciled
legacy rows with a null or agent-id `agentWorkspaceId` now use the root
of the DB they live in instead of none/an engine error. Old
`context_json` keeps deserializing (unknown keys are ignored; pinned by
`session_context_ignores_removed_keys`) and is rewritten without the
keys on next update. New test covers both tool-availability branches.
workspace_get/create/update_agent return Rust WorkspaceAgentDetail
(Option for get), not WorkspaceAgentResponse. Mirror that shape in
api/client.ts, drop the `as unknown as` cast in AgentBehaviorForm, and
report a missing agent as "Agent not found." with a test for the null case.
@juacker
juacker force-pushed the cleanup/pre-task-communication branch from 9e9362d to 08345b4 Compare October 13, 2026 08:27
…ncile

SessionContext persisted copies of the agent's config (MCP servers,
execution grants, name, team roster) that four reconcilers refreshed at
different times, so edits reached a run only after a UI re-hydrate, and
the hydrate's read-modify-write could restore a stale CLI session id.

Changes:
- SessionContext keeps only automation_id, cli_session_id and
  cli_session_provider. Old rows with the removed keys still deserialize.
- New assistant::run_context: RunContext (never persisted) is resolved
  once per run in engine::run_session_turn from the live workspace config
  and roster, and handed to the API turn, the CLI turn and, via
  ToolBinding, the local MCP server. The MCP server no longer re-reads the
  session row on every tools/list or tools/call.
- build_system_prompt, available_tools and build_trigger_message take
  &RunContext; the agent description (with skills) is a field.
- Manager-session lookup is find-or-create
  (repository::find_or_create_manager_session) in both the hydrate
  command and the scheduler; an existing row is never rewritten.
- Deleted: desired_workspace_context, the reconcile branches,
  fresh_execution_for_session, live_agent_description,
  workspace_agent_runtime_description, resolve_workspace_manager_agent,
  agent_config_from_row, WorkspaceDescriptor.execution/automation_name,
  task_session_context, and the title sync.
- workspace_update_session_mcp only writes the Main's MCP refs.
- FE: WorkspaceContextBar drops the session.context.mcpServerIds
  fallback and shows the MCP-toggle error inline.

Behaviour:
- Config edits (MCP, grants, shell/web, roster, skills, name) apply to
  the next run of any session, with no hydrate or scheduler tick needed.
- A session whose agent was deleted or unassigned (or whose library
  definition is gone) fails at run start with a readable error and the
  run row is marked failed; it no longer runs on a stale snapshot.
  A missing workspace root now also fails the run row instead of leaving
  it queued.
- Manager-less workspaces run with web access and no MCP servers; the
  MCP toggle returns "Set up a main agent for this workspace first."
- Hydrate no longer bumps updated_at, so no spurious message reloads.
- Task sessions store only the assignee id and resolve its config per
  run, as before but live.
… to start

`run_next_agent` claimed an instance with `next_ready` (marking it and the
scheduler's single slot running) and then had several `?` exits before
`complete_agent`: missing instance, agent config load/not-found, workspace
DB open, manager-session lookup, no enabled provider connection, and
manager-session creation/emit. Any of them left the slot held, so every
later scheduled run was skipped until a config edit or app restart.

Split the tick into `claim_next_instance` + `run_claimed_instance`, wrapped
by `release_slot_on_error`: on error, if the instance still holds the slot,
it is completed as failed with a retry in 5 minutes (not ready-now, which
would hot-loop every 5 s tick and could starve other workspaces). The
error is still returned to and logged by the runner loop as before.

Adds a regression test that a failed start (no provider) frees the slot
and leaves the instance schedulable again.
assign_task no longer builds a throwaway AgentConfig: the connection is
picked from the roster row, and the session stores only the assignee id.
Each task run resolves MCP servers, grants, execution and roster from the
assignee's live config at run start, like a Manager run; an unassigned
assignee or a deleted library definition fails the run instead of running
on stale settings.

first_enabled_connection becomes a free function over the id list so the
scheduler and assign_task share it. AgentConfig drops
selected_mcp_server_ids and execution, and WorkspaceAgentRow drops the
skill/MCP/execution copies: nothing reads them. The dead
AgentConfig::required_tools duplicate of the scheduler's tool list goes
with them.
…s and notices storage

Cleanup C8. Everything removed had no importer or caller.

Backend:
- Delete 10 registered commands no frontend code invokes: the ai_provider
  get/set/clear/validate, available providers and provider models
  commands, provider_connection_get, provider_connection_list_models,
  get_mcp_server and skill_sources_list, plus their providers/config
  helpers.
- Remove the RunTrigger::Retry and RunTrigger::InterAgentCall variants;
  migration 20261013000000 maps stored rows of those triggers to user_message and
  workspace_task.
- Remove run-notice storage; the "completed with warnings" status stays.
- ipc_surface: lower the parser floor to > 80 (100 commands registered).

Frontend:
- src/api/client.ts: the legacy "AI Provider Functions" section,
  getSkillSources and getMcpServer.
- src/assistant/client.ts: getProviderConnection and listProviderModels.
- ProviderSettings.tsx, the only user of the ai_provider wrappers, was
  never mounted. ProviderSettings.module.css stays because
  AssistantProviderSettings imports it; its classes used only by the
  deleted component are pruned.
- AgentCard.tsx and its CSS module, plus their barrel exports.
- NoticesBanner and its CSS.

Enable react-hooks/rules-of-hooks as an error in both rule blocks. The
only two violations were anonymous `default:` mock components in
Routes.test.tsx and SettingsModal.test.tsx; they are now named
functions.

Frontend and backend land together so ipc_surface's
every_registered_command_is_invoked_by_the_frontend holds at every commit.
The UI commands and the agents' task tools each had their own
workspace_tasks row, column list, mapper and response, and their own
WorkspaceAgentRow, roster loader and row builder. The copies had already
drifted (roster order, blank-name label).

- New assistant/workspace_tasks_repo.rs owns the task row, the column
  list, load/list/create/update, and WorkspaceTaskResponse (TS shape
  unchanged; bindings.ts identical).
- commands/workspace.rs keeps the single WorkspaceAgentRow loader, now
  pub(crate), with WorkspaceAgentRow::display_name() as the one label
  rule (name, or definition id when blank). The task tools use it.
- creator_workspace_agent_id replaces find_workspace_agent_for_definition:
  the definition-id match could never hit (a run's automation_id is the
  workspace agent id), and it reuses the roster already loaded.

Visible changes:
- workspace_assignTask / workspace_getTaskResult `task` JSON is the UI
  response: adds createdByDisplayName, assignedAgentDisplayName,
  attentionAcknowledgedAt; None-valued optional keys are now omitted
  instead of null.
- workspace_listAgents and the unknown/disabled-agent error hints list
  agents Main first then by join time (created_at), matching the prompt
  roster and crew list; previously by id.
- A blank-named agent is labelled with its definition id in
  workspace_listAgents displayName, the error hints, and task cards'
  assignee/creator names (previously "").
assistant_messages.role, assistant_runs.status/trigger,
assistant_tool_calls.status and the assistant_compactions enums held
serde-JSON strings ('"running"') because they were bound with the generic
JSON helper. workspace_tasks.status and assistant_message_queue.status
were already plain, so hand-written SQL needed to know which spelling
each table used.

- enum_text/parse_enum_text bind and read unit enums as bare strings;
  serde stays the single spelling of each variant.
- Migration 20261015000000 unquotes existing rows (guarded so plain
  values are left alone) and rebuilds assistant_compactions with
  plain-value CHECKs, keeping its indexes.
- ACTIVE_RUN_STATUSES replaces four copies of the non-terminal set;
  every literal in repository SQL is now plain.
update_run_status and assistant_cancel_run each kept an inline terminal
set that omitted CompletedWithWarnings: update_run_status left
completed_at unset for such runs, and cancelling a finished
warnings run overwrote it as Cancelled and emitted RunCancelled.
RunStatus::is_active/is_terminal is now the single rule; the
ACTIVE_RUN_STATUSES SQL list is tested against it.
…ayer

WorkspaceAgentRow, its loader, the row and summary builders and the
display-name helpers move to assistant/workspace_agents.rs. run_context and
the workspace task tools no longer import from commands::workspace; the
command layer calls the assistant module instead. Summaries are built from
rows directly rather than via the command response type.
…pies

ToolExecutionContext copied five RunContext fields at both construction
sites. It now holds run_context: Arc<RunContext>, built once in the engine
and shared with ToolBinding.
workspace_delete learned which scheduler entries to remove by reading the
workspace config, so an unreadable config left the Main's instance behind;
it then failed its agent lookup on every fire and was retried every five
minutes, logging an error each time, until restart.

Scheduler definitions and instances now record their workspace id, and
delete removes them with Scheduler::remove_workspace without reading the
config. The unused Scheduler::remove_definition goes.
When the worker's turn returned, any run status other than completed,
failed or cancelled - including a missing run row or a failed lookup -
marked the task completed. Nothing advances the task after that point, so
those cases now fail it with an explicit error instead.
AssistantDeps::emit(session, run_id, event) replaces the repeated
emit_event(&deps..., workspace_id, ...) boilerplate at 35 call sites.
load_workspace_agent_as_config walked every workspace in the index and
returned on the first config it could not read, so one corrupt workspace
config stopped every other workspace's scheduled run from starting, each
retried every five minutes.

Scheduler instances record their workspace id, so the runner now reads
only that workspace's config.
…ctory

Removing the pool from the index only dropped a handle; the SQLite
connections stayed open until their worker threads exited, so on Windows
deleting the workspace directory failed with os error 32 while
data.sqlite was still held.
@juacker
juacker force-pushed the cleanup/pre-task-communication branch from 0e057c6 to 3de0b87 Compare October 13, 2026 13:39
@juacker
juacker marked this pull request as ready for review October 13, 2026 15:22
@juacker
juacker merged commit 410a611 into main Oct 13, 2026
2 checks passed
@juacker
juacker deleted the cleanup/pre-task-communication branch October 13, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant