Skip to content

refactor(server): move the integrations route and types into their own slice - #1686

Merged
devswha merged 2 commits into
mainfrom
refactor/slice-integrations
Oct 22, 2026
Merged

devswha merged 2 commits into
mainfrom
refactor/slice-integrations

Conversation

@devswha

@devswha devswha commented Oct 22, 2026

Copy link
Copy Markdown
Owner

The first feature slice out of server/index.ts and shared/protocol.ts: GET /api/integrations and its two types move into their own files. Behaviour, path, status codes and messages stay the same. The plan, with the ownership of every route and protocol type, the order of the next slices, and this slice's risks, is .omo/plans/feature-slices-plan.md in the maintainer's workspace.

This PR moves responsibility, so it is not move-only and takes the normal review path.

Why integrations first

It is the smallest family still written inline in index.ts (9 lines). It has two protocol types, no state, no dependency on the server core, and a live contract test that already checks its answer (server/api.contract.test.ts, "lists herdr's integrations as integration.list reports them, and refuses anything but GET"). The next candidates, in plan order, are the agent catalog, plugins, background tasks, plans and files.

Two commits: the check first, then the move

  1. test(server): pin GET /api/integrations before it moves adds server/integrations-routes.test.ts. It runs in the unit lane without herdr: it starts a real createServer({ port: 0, hostname: "127.0.0.1", stateDir: mkdtempSync(…), … }) and stubs integrationList with spyOn(herdr, "integrationList"), the same seam server/http.test.ts uses for paneFind. It pins:

    • GET answers 200 with exactly { integrations: <what integration.list returned> }, including a state this build does not know;
    • a HerdrError("connect_failed") answers 502 with that code and message, and any other error answers 500 internal_error without its text;
    • a POST answers 400 method_not_allowed / use GET without asking herdr.

    On unchanged code it gives 3 pass, 0 fail.

  2. refactor(server): move the integrations route and types into their own slice

    • Moved: the route branch (with its "read only" comment), unchanged, into handleIntegrationsRequest(request, pathname): Promise<Response | null> in the new server/integrations-routes.ts. This is the nullable route pattern of workspace-routes.ts and pane-routes.ts: no state, no dependency bag, and it imports integrationList and the HTTP helpers directly.
    • Moved: AgentIntegration and IntegrationsResponse, with their doc comment, verbatim into the new shared/integrations.ts. That file holds types only, with no imports and no values.
    • Stays in index.ts: one call at the same place in the dispatch order, after /api/agents and before /api/fs/*. Every access, origin, watch and timeout gate before it, the /api/machines/local alias allowlist and the machine proxy path list are untouched. The now-unused integrationList and IntegrationsResponse imports are dropped.
    • Stays in protocol.ts: export type { AgentIntegration, IntegrationsResponse } from "./integrations.ts"; at the old place, as protocol.ts already does for the machine types. All seven existing importers (server/herdr/client.ts, server/api.contract.test.ts, src/lib/api.ts, SettingsDialog.tsx, src/lib/integrations.ts and its test, site/demo/transport.ts) compile unchanged, so this PR does not touch those shared files.

Checks

  • The pinning test, after the move: HERDR_TEST_MODE=unit bun test server/integrations-routes.test.ts server/http.test.ts src/lib/integrations.test.ts gives 41 pass, 0 fail; the pinning test itself is unchanged between the two commits.
  • --color-moved on the move commit: git diff --color-moved=plain --color-moved-ws=allow-indentation-change HEAD~1 HEAD -- server shared shows 71 lines as moved: the handler body (one indent level shallower in its new file) and both interfaces. The 15 other lines are:
    • in index.ts: the new import, the shortened protocol type import, the dropped integrationList import, and the 2-line call;
    • in the new route file: its 2-line header, 3 imports, the function's doc comment and signature, and return null;
    • in protocol.ts: the type re-export.
  • Client imports: bun run typecheck passes on both tsconfigs, so every importer still compiles through the re-export. No file in src/ or site/ imports shared/integrations.ts, so the client keeps its type-only rule. shared/integrations.ts has no import and no value export.
  • Sizes: server/index.ts goes from 862 to 854 lines and shared/protocol.ts from 1,522 to 1,503. The new modules are 22 and 20 lines.
  • Risks checked:
    • Import cycles: none new. The route imports only herdr/client.ts and http.ts, and neither reaches index.ts.
    • Initialization order: unchanged. The handler has no module state and constructs nothing, so server/startup-order.test.ts still applies as is.
    • Environment reads: the protocol value exports are untouched, so no environment read moves.
  • bun run check fast: passes (unit 6131 pass, 0 fail, 12 skip in 435 files, 8 at a time, 57.7s).
  • CI's integration lane runs the existing api.contract.test.ts case against an isolated live herdr.

Overlapping open PRs

Open PRs that touch these files: #1597 (/api/agents branch in index.ts, ending about 8 lines above the integrations branch), #1598 (adds a session-resume path to the lists that mention /api/integrations; it does not change them otherwise), and #1652, #1442 and #1427 (other parts of protocol.ts). None of them changes the moved lines, so any order works. Whichever merges second rebases; if #1597 merges first and its context overlaps, the rebase keeps both changes as they are.

No changelog fragment: users see no change.

A unit-lane test through a real createServer, with integrationList stubbed the
way server/http.test.ts stubs paneFind, so it runs without herdr: the answer is
integration.list as it is, herdr's own error passes through (502) and any other
is hidden (500 internal_error), and a non-GET is refused before herdr is asked.
…n slice

The first feature slice out of server/index.ts and shared/protocol.ts (plan:
.omo/plans/feature-slices-plan.md). GET /api/integrations moves, unchanged, into
handleIntegrationsRequest in server/integrations-routes.ts, the nullable route
pattern of workspace-routes.ts and pane-routes.ts, called at the same place in
the dispatch order. AgentIntegration and IntegrationsResponse move verbatim into
shared/integrations.ts (types only), and protocol.ts re-exports them, so every
existing importer compiles unchanged.
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 4f156f2 and 25981dd.

📒 Files selected for processing: 5
  • server/index.ts
  • server/integrations-routes.test.ts
  • server/integrations-routes.ts
  • shared/integrations.ts
  • shared/protocol.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.

📝 Summary

Summary by CodeRabbit

  • New Features
    • The integrations API now provides a consistent list of integrations, including their availability and status.
  • Bug Fixes
    • Requests using unsupported methods receive a clear error response. Integration lookup failures return appropriate error responses, with unexpected errors kept generic.

The integrations endpoint now delegates to a dedicated route handler. Its response interfaces are defined in shared/integrations.ts and re-exported from shared/protocol.ts. Tests cover listing, error responses, and rejected methods.

Changes: Integrations API

Layer / File(s) Summary
Integrations response contract
shared/integrations.ts, shared/protocol.ts
Defines AgentIntegration and IntegrationsResponse in a shared module. The protocol module re-exports both interfaces.
Route handling and server wiring
server/integrations-routes.ts, server/index.ts, server/integrations-routes.test.ts
The route handler serves GET requests and converts listing errors to error responses. The server delegates the endpoint to the handler. Tests cover successful listing, error responses, and POST rejection.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Refactor

Suggested reviewers: haeminway1

Merge Risk: ⚪ Minimal · up to 25981

No actionable behavior change or merge-blocking risk is established; this is mergeable after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: moving the integrations route and types into separate modules.
Description check Passed The description explains the change, resulting behavior, risks, affected files, tests, contract coverage, and changelog decision. It uses "## Checks" instead of the template's "## Validation" heading,…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description explains the change, resulting behavior, risks, affected files, tests, contract coverage, and changelog decision. It uses "## Checks" instead of the template's "## Validation" heading, but it provides the required validation information and is otherwise complete.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devswha

devswha commented Oct 22, 2026

Copy link
Copy Markdown
Owner Author

Merge-pass review. This PR moves an HTTP route and protocol types, so besides CodeRabbit a code-review agent reviewed it: APPROVE, no findings to fix.

Behaviour is identical.

  • With indentation ignored, the old branch (server/index.ts:543-552 on main) and handleIntegrationsRequest (server/integrations-routes.ts:11-20) are the same lines.
  • shared/integrations.ts is a byte-for-byte copy of the two types and their comment.
  • --color-moved marks 71 lines as moved.
  • The call keeps its place: after /api/agents and every gate, before /api/fs/*.
  • These lists are untouched: the /api/machines/local alias allowlist, the bridge path list and the machine proxy list.

The pinning test can fail. It passes 3/3 on commit 1 alone. Each of seven mutations fails it:

  • a weaker method check;
  • a renamed error code;
  • a flattened error mapping;
  • status 201;
  • a filtered list;
  • asking herdr before the method check;
  • removing the call from index.ts.

No cycle, no order change, no contract change.

  • Nothing the route file imports leads back to server/index.ts, and startup-order.test.ts passes.
  • The protocol re-export is export type, so src/ and site/demo see no runtime or bundle change.
  • JSON shapes and type names are unchanged, and bun run typecheck passes on both configs.

Tests: HERDR_TEST_MODE=unit bun test server/integrations-routes.test.ts server/http.test.ts src/lib/integrations.test.ts gives 41 pass. The new test follows AGENTS.md: temporary stateDir, port: 0, no real herdr, no sleeps.

@devswha
devswha merged commit 410b51a into main Oct 22, 2026
17 checks passed
@devswha
devswha deleted the refactor/slice-integrations branch October 22, 2026 21:45
devswha added a commit that referenced this pull request Oct 22, 2026
…ce (#1687)

The next feature slice out of `server/index.ts` and
`shared/protocol.ts`, after the integrations pilot (#1686): the plugin
action routes and their types move into their own files. Behaviour,
paths, status codes and messages stay the same. The plan, with the order
and the risks, is `.omo/plans/feature-slices-plan.md` in the
maintainer's workspace.

The plan lists the agent catalog before plugins. It comes after this one
because the open fix #1597 changes the same `/api/agents` branch, and
the refactor brief says to fix first, then move.

This PR moves responsibility, so it is **not move-only**.

## Two commits: the check first, then the move

1. **`test(server): pin the plugin routes before they move`** adds
`server/plugin-routes.test.ts`. The existing route coverage is
`server/plugins.contract.test.ts`, which needs a live isolated herdr and
a disposable plugin registry, and `route-envelope.contract.test.ts` for
the gates. `plugin-actions.test.ts` covers the helpers but not the
routes. So the new test runs the routes in the unit lane without herdr:
a real `createServer({ port: 0, hostname: "127.0.0.1", stateDir:
mkdtempSync(…), … })`, with `pluginList`, `pluginActionList`,
`pluginLogList`, `pluginActionInvoke` and, in one case,
`sessionSnapshot` stubbed through `spyOn(herdr, …)`, as
`server/http.test.ts` does for `paneFind`. It pins:
   - **`GET /api/plugins/actions`:**
- each plugin with only this platform's actions (an action for another
platform is dropped; one with no platforms is kept);
- `description` null when missing, `contexts` `[]` when missing, and no
command line in the answer;
     - a POST is `400 method_not_allowed`.
   - **`GET /api/plugin/action`:**
     - a missing `plugin_id` or `log_id` is a 400;
- a known log answers its result (failed run: exit code and its own
words);
     - the log list is read with herdr's limit of 200;
     - an unknown log is `404 plugin_log_not_found`;
     - herdr's own error passes through (502).
   - **`POST /api/plugin/action`, validation before herdr is asked:**
- `invalid_json`, `missing_plugin_id`, `missing_action_id` and
`invalid_pane_id` are 400s;
     - a PUT is `400 method_not_allowed`;
     - in all of these, nothing is invoked.
   - **`POST /api/plugin/action`, runs:**
     - without a pane, herdr is invoked with no context;
- with a pane, herdr gets exactly `pluginPaneContext(snapshot, pane)`;
- with a pane that is not there, the answer is `404 pane_not_found` and
nothing is invoked.

   On unchanged code it gives **4 pass, 0 fail**.
2. **`refactor(server): move the plugin routes and types into their own
slice`**
   - **Moved into the new `server/plugin-routes.ts`:**
- the three branches, unchanged, as `handlePluginRequest(request, url,
pathname, options): Promise<Response | null>`, the nullable route
pattern of `workspace-routes.ts`, `pane-routes.ts` and
`integrations-routes.ts`;
- the three things only those branches used: `PLUGIN_PLATFORM`,
`PLUGIN_ACTION_WAIT_MS` and `livePaneIds`.
- **Moved into the new `shared/plugins.ts`:** the six plugin action
types (`PluginActionContext`, `PluginAction`, `PluginActions`,
`PluginActionsResponse`, `PluginActionRequest`, `PluginActionResult`),
verbatim with their doc comments. The file holds types only, with no
imports and no values.
- **Stays in `index.ts`:** one call at the same place in the dispatch
order, after the workspace routes and before the pane routes.
`createServer` still passes its `pluginActionWaitMs` option through
`options`. The access, origin, watch and timeout gates, the
`/api/machines/local` alias allowlist and the machine proxy path list
are untouched. The now-unused imports `HerdrError`, `readJsonObject`,
the four plugin RPCs, `PluginInvocationContext`, `PluginActionsResponse`
and the `plugin-actions.ts` helpers leave `index.ts`.
- **Stays in `protocol.ts`:** a type re-export of the six names, so
every importer compiles unchanged: `server/plugin-actions.ts`,
`server/plugins.contract.test.ts`, `src/lib/api.ts`,
`machineContext.tsx`, `pluginRun.ts`, `paletteSearch.ts`,
`CommandPalette.tsx`, `SettingsDialog.tsx` and `site/demo/transport.ts`.
This PR does not touch them.

## Checks

- **After the move:** `HERDR_TEST_MODE=unit bun test
server/plugin-routes.test.ts server/plugin-actions.test.ts
server/integrations-routes.test.ts src/lib/pluginRun.test.ts
src/lib/paletteSearch.test.ts` gives **46 pass, 0 fail**. The pinning
test is unchanged between the two commits.
- **`--color-moved` on the move commit:** `git diff --color-moved=plain
--color-moved-ws=allow-indentation-change HEAD~1 HEAD -- server shared`
shows 265 lines as moved: the three branches (one indent level shallower
in their new file), the three constants, both import lines that moved
with them, and the six types. The 21 other lines are:
  - in `index.ts`:
    - the shortened protocol import;
    - the `http.ts` import without `readJsonObject`;
    - the six dropped `herdr/client.ts` names;
    - the new `plugin-routes.ts` import;
    - the 2-line call.
  - in the new route file:
    - its 3-line header;
    - the types import and the `herdr/client.ts` import;
    - the function's doc comment and signature;
    - `return null`.
  - in `protocol.ts`: the re-export line.
- **Client imports:** `bun run typecheck` passes on both tsconfigs. No
file in `src/` or `site/` imports `shared/plugins.ts`.
`shared/plugins.ts` has no import and no value export.
- **Sizes:** `server/index.ts` goes from 854 to 778 lines and
`shared/protocol.ts` from 1,503 to 1,452. The new modules are 87 and 52
lines.
- **Risks checked:**
- Import cycles: none new. The route imports `herdr/client.ts`,
`http.ts` and `plugin-actions.ts`, none of which reaches `index.ts`.
- Initialization order: unchanged. The moved constants are pure, and
`PLUGIN_PLATFORM` reads `process.platform` as before. The handler
constructs nothing.
- `bun run check fast`: passes (unit 6135 pass, 0 fail, 12 skip in 436
files, 8 at a time, 57.7s).
- CI's integration lane runs `server/plugins.contract.test.ts` and
`route-envelope.contract.test.ts` against an isolated live herdr.

## Overlapping open PRs

None of the open PRs that touch `server/index.ts` or
`shared/protocol.ts` (#1597, #1598, #1652, #1442, #1427) changes the
moved lines or the plugin types, so any merge order works.

No changelog fragment: users see no change.
devswha added a commit that referenced this pull request Oct 22, 2026
…r own slice (#1688)

The next feature slice out of `server/index.ts` and
`shared/protocol.ts`, after integrations (#1686) and plugins (#1687):
the background tasks route and its types move into their own files.
Behaviour, path, status codes and messages stay the same. The plan is
`.omo/plans/feature-slices-plan.md` in the maintainer's workspace.

The agent catalog slice is on hold: the open PR #1597 changes the same
`/api/agents` branch, and the refactor brief says the bug fix goes
first, then the move.

This PR moves responsibility, so it is **not move-only**.

## Three commits: the check first, then the move, then the branch the
move made testable

1. **`test(server): pin GET /api/pane/omo-tasks before it moves`** adds
`server/tasks-routes.test.ts`. The existing route coverage is live only:
`api.contract.test.ts` checks the missing pane and an unknown pane, and
`claude-session.contract.test.ts` checks a Claude Code pane's subagents.
The parsers have unit tests (`omo-tasks.test.ts`,
`claude-subagents.test.ts`). The new test runs the route itself in the
unit lane without herdr: a real `createServer({ port: 0, hostname:
"127.0.0.1", stateDir: mkdtempSync(…), … })` with `paneGet` stubbed
through `spyOn(herdr, "paneGet")`. It pins:
- a POST answers `400 method_not_allowed`, and a request without
`pane_id` answers `400 missing_pane_id`;
- a pane that runs neither OmO nor Claude Code answers 200 with no
tasks, no runs and a valid `server_time`, after asking herdr for that
pane;
- when herdr does not answer that pane lookup, the route still answers
200 with what it knows, once its 1-second bound (`within(1000, …)`) runs
out. That makes this test take about a second.

   On unchanged code it gives **3 pass, 0 fail**.
2. **`refactor(server): move the background tasks route and types into
their own slice`**
- **Moved:** the branch, unchanged, into `handleTasksRequest(request,
url, pathname, { omo, claudeAgents }): Promise<Response | null>` in the
new `server/tasks-routes.ts`. This is the nullable route pattern of the
earlier slices.
- **The trackers stay the core's:** `index.ts` passes the core's own
`omo` (`OmoStatus`) and `claudeAgents` (`ClaudeSubagentStatus`). The
route's type allows only the methods it calls (`sessionOf`, and `ensure`
on the Claude tracker), and it constructs nothing, so there is one
tracker each as before and startup order does not change.
- **Moved:** `OmoTask`, `OmoRunNode`, `OmoRun` and `OmoActivity`,
verbatim with their doc comments, into the new `shared/tasks.ts`. It
holds types only. `OmoTaskResult` stays in `protocol.ts`, because it is
conversation data.
- **Stays in `index.ts`:** one call at the same place in the dispatch
order, after the pane routes and before `/api/pane/plan`. The now-unused
`processAlive`, `omoTasks`, `omoRuns` and `claudeSubagents` imports
leave. `within` stays, because the plan route still uses it.
- **Stays in `protocol.ts`:** a type re-export of the four names, so
`server/omo-tasks.ts`, `server/claude-subagents.ts`,
`server/claude-session.contract.test.ts`, `src/lib/api.ts`,
`src/lib/omoTasks.ts` and its test, and `BackgroundTasks.tsx` compile
unchanged.
- **Also in this commit:** as the CI session asked after #1687, the
comment on `createServer`'s `pluginActionWaitMs` option now says
`PLUGIN_ACTION_WAIT_MS` lives in `server/plugin-routes.ts`.

3. **`test(server): cover the OmO branch of the tasks route`**, added
after CodeRabbit's nitpick. `handleTasksRequest` now takes the trackers
as an argument, so the unit lane can call it with a fake OmO tracker
that names a session. The session's task record comes back as its task,
the Claude tracker is not asked, and another path is not this route's.

Rebased onto main after PR #1597 (86e0643) merged: the one conflict was
the import block in `server/index.ts`, where PR #1597 renamed the
`shell-agent.ts` import; both changes are kept.

## Checks

- **After the move:** `HERDR_TEST_MODE=unit bun test
server/tasks-routes.test.ts server/omo-tasks.test.ts
server/claude-subagents.test.ts src/lib/omoTasks.test.ts
server/plugin-routes.test.ts server/shell-agent.test.ts` gives **127
pass, 0 fail** (with the third commit). The pinning test's three cases
are unchanged by the move.
- **`--color-moved` on the move commit:** `git diff --color-moved=plain
--color-moved-ws=allow-indentation-change HEAD~2 HEAD~1 -- server
shared` shows 143 lines as moved: the branch (one indent level shallower
in its new file), the `omo-tasks.ts` import that moved with it, and the
four types. The 23 other lines are:
- in `index.ts`: 4 import lines, the `pluginActionWaitMs` comment
(before and after), and the 2-line call;
- in the new route file: its 5-line header, 4 imports, the
`TaskTrackers` type with its comment, the function's doc comment and
signature, and `return null`;
  - in `protocol.ts`: the re-export line.
- **Client imports:** `bun run typecheck` passes on both tsconfigs. No
file in `src/` or `site/` imports `shared/tasks.ts`. `shared/tasks.ts`
has no import and no value export.
- **Sizes:** `server/index.ts` goes from 785 to 763 lines and
`shared/protocol.ts` from 1,452 to 1,410. The new modules are 41 and 43
lines.
- **Risks checked:**
- Import cycles: none new. The route imports `claude-subagents.ts`,
`herdr/client.ts`, `http.ts`, `omo-status.ts` and `omo-tasks.ts`, none
of which reaches `index.ts`. It imports nothing from `core/`, so the
core ownership rule (`core/ownership.test.ts`) holds.
- Initialization order: unchanged. The route only reads the trackers it
is given.
- `bun run check fast` (on the rebased branch): passes (unit 6151 pass,
0 fail, 12 skip in 437 files, 8 at a time, 57.6s).
- CI's integration lane runs the `api.contract.test.ts` and
`claude-session.contract.test.ts` cases against an isolated live herdr.

## Overlapping open PRs

The open PRs that touch these files (#1598 and #1442 in `index.ts` and
`protocol.ts`, #1652 and #1427 in `protocol.ts`) do not change the moved
lines or these types. The open PR #1597, which edits `index.ts`, does
not touch them either.

No changelog fragment: users see no change.
devswha added a commit that referenced this pull request Oct 23, 2026
…n slice (#1690)

The next feature slice out of `server/index.ts` and
`shared/protocol.ts`, after integrations (#1686), plugins (#1687) and
background tasks (#1688): the agent catalog route and its type move into
their own files. Behaviour, path, status codes and messages stay the
same. The plan is `.omo/plans/feature-slices-plan.md` in the
maintainer's workspace.

This slice waited for PR #1597 (merged as 86e0643), which changed this
same `/api/agents` branch. The refactor brief puts a bug fix first and
the move after it. The branch moves here exactly as PR #1597 left it,
including its on-demand login PATH lookup through the server's
shell-agent locator.

This PR moves responsibility, so it is **not move-only**.

## Two commits: the check first, then the move

1. **`test(server): pin GET /api/agents before it moves`** adds
`server/agents-routes.test.ts`.
- **Why it is needed:** the existing route coverage is
`api.contract.test.ts` (fallbacks, PATH, sort), which needs a live
isolated herdr. `server/shell-agent.test.ts` covers the locator but not
the route.
   - **How it runs without herdr, in the unit lane:**
- a real `createServer({ port: 0, hostname: "127.0.0.1", stateDir:
mkdtempSync(…), …, shellAgents })`, given a stand-in locator through
`createServer`'s own `shellAgents` option;
- herdr's `agentManifests` stubbed with `spyOn(herdr,
"agentManifests")`;
- `PATH` pointed at a temp folder that holds only a `codex` executable,
and restored after each test.
   - **What it pins:**
- herdr's agents (`codex`, `pi`, `someagent`) are offered, plus Oh My Pi
and Claude Code always;
- the shell agent the locator finds (`gjc`) is offered and the one it
does not find (`omo`) is not, and the locator is asked about both;
- each agent has its label (an unknown kind keeps its kind as its label)
and its `installed` flag: true for `codex` on PATH and for `gjc`, which
the locator found, false for the rest;
     - the order is by label, then kind;
     - herdr's own error passes through (502);
     - a POST answers `400 method_not_allowed`.

   On unchanged code it gives **2 pass, 0 fail**.
2. **`refactor(server): move the agent catalog route and type into their
own slice`**
- **Moved:** the branch, unchanged, into `handleAgentsRequest(request,
pathname, shellAgents): Promise<Response | null>` in the new
`server/agents-routes.ts`, the nullable route pattern of the earlier
slices.
   - **Moved with it:** `AGENT_LABELS`, which only this branch used.
- **The locator stays the server's:** `index.ts` passes its own
`shellAgents`, the one launches use too. The route's type allows only
`executable`, so nothing is constructed, the locator's `stop()` stays
with the server, and startup order does not change.
- **Moved:** `AgentKind`, into the new `shared/agents.ts` (types only),
together with its doc comment ("GET /api/agents: one agent kind herdr
can start …"). That comment sat a few declarations away in
`protocol.ts`, above the directory listing type. This is the one line
that changes place for a reason other than the move, so the comment now
documents the type it describes.
- **Stays in `index.ts`:** one call at the same place in the dispatch
order, after the auth, device, update and diagnostics routes and before
`/api/fs/*`. The now-unused `agentManifests`, `SHELL_AGENTS` and
`AgentKind` imports leave.
- **Stays in `protocol.ts`:** a type re-export of `AgentKind`, so
`server/api.contract.test.ts`, `src/lib/api.ts`, `AgentPicker.tsx`,
`NewSessionDialog.tsx`, `SessionHistory.tsx` and `WorktreeDialog.tsx`
compile unchanged.

## Checks

- **After the move:** `HERDR_TEST_MODE=unit bun test
server/agents-routes.test.ts server/shell-agent.test.ts
src/lib/agentRoster.test.ts server/tasks-routes.test.ts
server/startup-order.test.ts` gives **26 pass, 0 fail**. The pinning
test is unchanged between the two commits.
- **`--color-moved` on the move commit:** `git diff --color-moved=plain
--color-moved-ws=allow-indentation-change HEAD~1 HEAD -- server shared`
shows 106 lines as moved: the branch (one indent level shallower in its
new file), the label table, `AgentKind` and its comment. The 21 other
lines are:
  - in `index.ts`:
    - the shortened protocol import;
    - the `shell-agent.ts` import without `SHELL_AGENTS`;
    - the new `agents-routes.ts` import;
    - the dropped `agentManifests` import line;
    - the 2-line call.
  - in the new route file:
    - its 5-line header;
    - 4 imports;
    - the function's doc comment and signature;
    - `return null`.
  - in `protocol.ts`: the re-export line.
- **Client imports:** `bun run typecheck` passes on both tsconfigs. No
file in `src/` or `site/` imports `shared/agents.ts`. `shared/agents.ts`
has no import and no value export.
- **Sizes:** `server/index.ts` goes from 763 to 724 lines and
`shared/protocol.ts` from 1,410 to 1,404. The new modules are 55 and 7
lines.
- **Risks checked:**
- Import cycles: none new. The route imports `herdr/client.ts`,
`http.ts` and `shell-agent.ts`, none of which reaches `index.ts`.
- Initialization order: unchanged. `AGENT_LABELS` is a constant, and the
locator is created by `createServer` as before.
- `bun run check fast`: passes (unit 6153 pass, 0 fail, 12 skip in 438
files, 8 at a time, 57.5s).
- CI's integration lane runs the `api.contract.test.ts` agent cases
against an isolated live herdr.

## Overlapping open PRs

Open PRs that touch these files: #1598 and #1442 (`index.ts` and
`protocol.ts`), and #1652 and #1427 (`protocol.ts`). Each diff was
checked for the moved lines and for `AgentKind`, and none of them
changes either one:
- #1598 adds a path to the `bridgePath` list in `index.ts`. That list
names `/api/agents`, but it stays in `index.ts` and this PR does not
touch it.
- #1427 changes an import line in `server/api.contract.test.ts` that
names `AgentKind`. That import still resolves through the re-export in
`protocol.ts`.

No changelog fragment: users see no change.
devswha added a commit that referenced this pull request Oct 23, 2026
…1694)

The next feature slice out of `server/index.ts` and
`shared/protocol.ts`, after integrations (#1686), plugins (#1687),
background tasks (#1688) and the agent catalog (#1690): the plan route
and its types move into their own files. Behaviour, path, status codes
and messages stay the same. The plan is
`.omo/plans/feature-slices-plan.md` in the maintainer's workspace.

This PR moves responsibility, so it is **not move-only**.

## Two commits: the check first, then the move

1. **`test(server): pin GET /api/pane/plan before it moves`** adds
`server/plan-routes.test.ts`.
- **Why it is needed:** the existing route coverage is live only:
`api.contract.test.ts` (missing pane, wrong method) and
`claude-session.contract.test.ts` (a Claude Code pane's plan). The plan
readers have unit tests (`session-plan.test.ts`,
`session-plan-readers.test.ts`).
- **How it runs without herdr, in the unit lane:** a real
`createServer({ port: 0, hostname: "127.0.0.1", stateDir:
mkdtempSync(…), … })`, with `sessionSnapshot` stubbed through
`spyOn(herdr, "sessionSnapshot")`.
   - **What it pins:**
- a POST answers `400 method_not_allowed`, and a request without
`pane_id` answers `400 missing_pane_id`;
- a pane herdr does not have, and a pane that keeps no plan, both answer
200 `{ plan: null }` with a valid `server_time`;
- when herdr does not answer the snapshot, the route still answers 200
`{ plan: null }` once its 1-second bound (`within(1000, …)`) runs out.
That makes this test take about a second.

   On unchanged code it gives **3 pass, 0 fail**.
2. **`refactor(server): move the plan route and types into their own
slice`**
- **Moved:** the branch, unchanged, into `handlePlanRequest(request,
url, pathname, { omo, claudeAgents, plans }): Promise<Response | null>`
in the new `server/plan-routes.ts`. This is the nullable route pattern
of the earlier slices.
- **The trackers stay the core's:** `index.ts` passes the core's own
`omo` (`OmoStatus`), `claudeAgents` (`ClaudeSubagentStatus`) and `plans`
(`SessionPlans`). The route's type allows only the methods it calls:
`apply`; `ensure`; and `known`, `ensure`, `planOf`. It constructs
nothing, so there is one tracker each as before and startup order does
not change.
- **Moved:** `PlanStep` and `PanePlan`, verbatim with their doc
comments, into the new `shared/plans.ts` (types only).
- **Stays in `protocol.ts`:** `PlanSummary`, because the session
snapshot's pane enrichment and the `pane-status` WebSocket message both
carry it; a re-export would add nothing there. Also a type re-export of
`PlanStep` and `PanePlan`, so `server/session-plan.ts`,
`server/session-plan-reader.ts`,
`server/claude-session.contract.test.ts`, `src/lib/api.ts`,
`src/lib/plan.ts` and its test, `BackgroundTasks.tsx` and
`site/demo/transport.ts` compile unchanged.
- **Stays in `index.ts`:** one call at the same place in the dispatch
order, after the background tasks route and before session history. The
now-unused `within`, `sessionSnapshot`, `HerdrPane` and `PanePlan`
imports leave.

## Checks

- **After the move:** `HERDR_TEST_MODE=unit bun test
server/plan-routes.test.ts server/tasks-routes.test.ts
server/session-plan.test.ts server/session-plan-readers.test.ts
src/lib/plan.test.ts server/startup-order.test.ts` gives **41 pass, 0
fail**. The pinning test is unchanged between the two commits.
- **`--color-moved` on the move commit:** `git diff --color-moved=plain
--color-moved-ws=allow-indentation-change HEAD~1 HEAD -- server shared`
shows 102 lines as moved: the branch (one indent level shallower in its
new file) and both types. The 23 other lines are:
  - in `index.ts`:
    - the shortened protocol import;
    - the dropped `within` and `sessionSnapshot` imports;
    - the new `plan-routes.ts` import;
    - the 2-line call.
  - in the new route file:
    - its 3-line header;
    - 7 imports;
    - the `PlanTrackers` type with its comment;
    - the function's doc comment and signature;
    - `return null`.
  - in `protocol.ts`: the re-export line.
- **Client imports:** `bun run typecheck` passes on both tsconfigs. No
file in `src/` or `site/` imports `shared/plans.ts`. `shared/plans.ts`
has no import and no value export.
- **Sizes:** `server/index.ts` goes from 724 to 704 lines and
`shared/protocol.ts` from 1,404 to 1,381. The new modules are 41 and 24
lines.
- **Risks checked:**
- Import cycles: none new. The route imports `claude-subagents.ts`,
`herdr/client.ts` and `http.ts`, plus types only from `omo-status.ts`
and `session-plan.ts`. None of them reaches `index.ts`, and nothing
comes from `core/`.
  - Initialization order: unchanged.
- `bun run check fast`: passes (unit 6156 pass, 0 fail, 12 skip in 439
files, 8 at a time, 57.9s).
- CI's integration lane runs the `api.contract.test.ts` and
`claude-session.contract.test.ts` plan cases against an isolated live
herdr.

## Overlapping open PRs

Open PRs that touch these files: #1652, #1598, #1442 and #1427. I
checked each diff for the moved lines and for `PlanStep` and `PanePlan`:
- #1652, #1598 and #1442 change neither.
- #1427 changes an import line that names `PanePlan` and `PlanStep`. It
imports them from `protocol.ts`, so the line still resolves through the
re-export.

No changelog fragment: users see no change.
devswha added a commit that referenced this pull request Oct 23, 2026
…#1696)

The last inline feature slice out of `server/index.ts` and
`shared/protocol.ts`, after integrations (#1686), plugins (#1687),
background tasks (#1688), the agent catalog (#1690) and plans (#1694):
the file routes and their types move into their own files. Behaviour,
paths, status codes, headers and messages stay the same. The plan is
`.omo/plans/feature-slices-plan.md` in the maintainer's workspace.

This PR moves responsibility, so it is **not move-only**.

## Two commits: the check first, then the move

1. **`test(server): pin the file routes before they move`** adds
`server/file-routes.test.ts`.
- **Why it is needed:** the existing route coverage is mostly live:
`page-media.contract.test.ts`, `route-envelope.contract.test.ts` and
`device-access.contract.test.ts`. The helpers have unit tests
(`file-view.test.ts`, `project-favicon.test.ts`, `files.test.ts`). In
the unit lane, only `open-access.test.ts` reaches a file route, and only
for the watching-device refusal.
- **How it runs without herdr, in the unit lane:** a real
`createServer({ port: 0, hostname: "127.0.0.1", stateDir:
mkdtempSync(…), … })` reading a temp project folder, with `paneGet`
stubbed through `spyOn(herdr, "paneGet")` where a pane is involved.
   - **What it pins:**
     - **Methods and other sites:**
       - a POST answers `400 method_not_allowed`, and HEAD is allowed;
- another site's `no-cors` read answers `403 cross_site`, while a
top-level navigation is served.
     - **A file by its absolute path:**
       - `/api/fs/stat` answers its name, kind and size;
- `/api/fs/file` answers its bytes, `inline` by default and `attachment`
with `download=1`;
       - a missing file answers `404 not_found`.
- **A relative path in a pane's folder:** with `pane_id`, a relative
path is found in the folder herdr reports for that pane. When herdr
cannot say that folder, the relative path is `404 not_found`, but an
absolute path is still served.
- **The project icon:** `/api/fs/favicon` without `root` answers `400
missing_root`. A folder with `public/favicon.ico` answers that icon, and
a folder without one answers `{ icon: null }`.
- **Page media:** an address never given out answers `404 not_found`,
and a POST answers `400 method_not_allowed`.

   On unchanged code it gives **5 pass, 0 fail**.
2. **`refactor(server): move the file routes and types into their own
slice`**
- **Moved:** both branches, unchanged, into `handleFileRequest(request,
url, pathname, requester): Promise<Response | null>` in the new
`server/file-routes.ts`:
     - `/api/fs/stat`, `/api/fs/file` and `/api/fs/favicon`;
     - `/api/fs/page-media/:media_id`.
- **Moved with them:** `claudeTempFolder`, which only the file read
used.
- **Who asks:** the access gate in `index.ts` already worked out who
asks: `bridgeAuthorized`, `requestHost`, `forwarded`, `client` and
`access`. The route gets those values as they are (`FileRequester`),
because a page's media plays only for the client and device that
rendered it. Nothing is computed again.
- **Shared state stays where it is:** the page-media registry stays in
`file-view.ts`. Sign-out and device revocation still call
`forgetPageMedia` from `index.ts`.
- **Moved:** `FileKind`, `FileInfo` and `ProjectFavicon`, verbatim with
their doc comments, into the new `shared/files.ts` (types only).
- **Stays in `index.ts`:** one call at the same place in the dispatch
order, after the agent catalog and before the workspace routes. Every
gate before it is untouched, including the watching-device refusal of
file reads. The imports only these routes used leave `index.ts`:
`paneContext`, `resolveAgent`, `claudePaneSession`,
`claudeSessionTempDir`, `findProjectFavicon`, `paneGet`,
`isSecureRequest`, the file-view helpers except `forgetPageMedia`, and
`ProjectFavicon`.
- **Stays in `protocol.ts`:** a type re-export of the three names, so
every importer compiles unchanged.

## Checks

- **After the move:** `HERDR_TEST_MODE=unit bun test
server/file-routes.test.ts server/file-view.test.ts
server/project-favicon.test.ts server/files.test.ts
server/open-access.test.ts server/startup-order.test.ts` gives ****71
pass, 0 fail****. The pinning test is unchanged between the two commits.
- **`--color-moved` on the move commit:** `git diff --color-moved=plain
--color-moved-ws=allow-indentation-change HEAD~1 HEAD -- server shared`
shows 168 lines as moved: both branches (one indent level shallower in
their new file), `claudeTempFolder` and the three types. The 26 other
lines are:
  - in `index.ts`:
- the shortened protocol, `auth.ts` and `file-view.ts` imports (each
before and after);
    - the new `file-routes.ts` import;
    - the dropped `paneGet` import line;
    - the 2-line call.
  - in the new route file:
    - its 4-line header;
    - the 6 imports that did not move with the code;
    - the `FileRequester` type with its comment;
    - the function's doc comment and signature;
    - `return null`.
  - in `protocol.ts`: the re-export line.

The imports only the moved code used (`paneContext`/`resolveAgent`,
`claudePaneSession`, `claudeSessionTempDir`, `findProjectFavicon`) show
as moved from `index.ts` into the route file.
- **Client imports:** `bun run typecheck` passes on both tsconfigs. No
file in `src/` or `site/` imports `shared/files.ts`. `shared/files.ts`
has no import and no value export.
- **Sizes:** `server/index.ts` goes from 704 to 657 lines and
`shared/protocol.ts` from 1,381 to 1,353. The new modules are 69 and 29
lines.
- **Risks checked:**
- Import cycles: none new. The route imports `access.ts` (type only),
`auth.ts`, `claude-store.ts`, `conversation.ts`, `file-view.ts`,
`herdr/client.ts`, `http.ts`, `pane-context.ts` and
`project-favicon.ts`. These were all already imported by `index.ts`, and
none of them imports `index.ts`.
  - Initialization order: unchanged. No module state moves.
- `bun run check fast`: passes (unit 6161 pass, 0 fail, 12 skip in 440
files, 8 at a time, 57.4s).
- CI's integration lane runs `page-media.contract.test.ts`,
`route-envelope.contract.test.ts` and `device-access.contract.test.ts`
against an isolated live herdr.

## Overlapping open PRs

Open PRs that touch these files: #1652, #1598, #1442 and #1427. I
checked each diff for the moved lines, the page-media names and the
three file types:
- #1652, #1442 and #1427 change none of them.
- #1598 changes the `bridgePath` list in `index.ts`, which names
`/api/fs/`. That list stays in `index.ts`, and this PR does not touch
it.

No changelog fragment: users see no change.
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