Skip to content

feat(client): consume brand identity agents/services allowlists - #479

Open
lucas77778 wants to merge 9 commits into
masterfrom
chenyu/code-618
Open

feat(client): consume brand identity agents/services allowlists#479
lucas77778 wants to merge 9 commits into
masterfrom
chenyu/code-618

Conversation

@lucas77778

@lucas77778 lucas77778 commented Aug 25, 2026

Copy link
Copy Markdown
Member

Summary

Client half of per-brand agent/provider control (CODE-618, part of CODE-612). The pinned render pipeline may now deliver optional agents / services allowlists on the config build bundle (publisher side: arcboxlabs/linkcodehq#49); this PR consumes them at build time. Absent fields = unrestricted — the standard build is byte-identical.

  • Contract: ConfigBuildBundleSchema and the brand-identity mirror accept optional agents (AgentKind subset) / services (kebab-case ids); non-empty, duplicate-free, still fail-closed on unknown fields. Frozen fixtures untouched. Both files avoid a value import of @linkcode/schema's root barrel (only reachable from build scripts under plain Node, which can't resolve its directory re-export) — the known agent-kind list is duplicated locally instead, matching this codebase's CONFIG_PLATFORMS/CONFIG_CHANNELS precedent.
  • Desktop main: new MAIN_VITE_AGENT_RESTRICTIONS define → agent-restrictions.ts (present-but-invalid aborts boot, mirroring brand identity), surfaced to the renderer via a synchronous identity.restrictions() SystemBridge channel that fails closed if the main-process snapshot is ever missing (never defaults to unrestricted).
  • Daemon enforcement boundary (agents): LINKCODE_ALLOWED_AGENTS from the supervisor is checked wherever an adapter process could actually start: SessionOrchestrator.startLive (the single chokepoint every start/resume/relaunch path constructs a live adapter through) and agent.catalog in AgentRequestHandler (whose startCatalog spawns real agent processes for codex/opencode). This refuses only a new live run of an excluded kind; it never touches session.list's per-record historyCapabilities or HistoryService's list/read/branch-cursor paths, so a persisted session of an excluded kind stays fully listable and readable. The boot runtime-probe snapshot and collectAgentRuntimes() are filtered (filterAgentRuntimes) so an excluded agent always reads missing regardless of how it was actually detected, and the wire-level asset surface is gated (restrictedAssetService): asset.ensure refuses an excluded agent's managed download and statuses/subscribe omit its entries entirely — both live in apps/daemon/src/agent-restrictions.ts, composed at the daemon boundary without touching @linkcode/agent-adapter's public API.
  • Daemon enforcement boundary (services): catalog-display filtering only (UI layer) — as planned, there is no daemon-side enforcement for services.
  • Packaging: AGENT_SDK_PACKAGE_PATHS is the single source of truth for both brand-conditional electron-builder files excludes (temp extends overlay, written into a fresh mkdtempSync dir per run) and a new verifyNoRestrictedAgentPackages post-pack asar assertion (exact path-segment matching).
  • UI: harness picker intersects the brand allowlist and its submenu disappears at ≤1 remaining agent (desktop composer + mobile new-thread sheet) — the trigger's agent icon stays visible whenever any harness is selectable at all, since it's the only harness signal once the submenu is gone. Add-account catalog filters oauth entries by bound agent and endpoint/custom entries by service id. serviceById, account list/detail, and resolution stay unfiltered so stored accounts keep rendering even if their service is no longer offered.

Fixed after review / CI

  • CI's Desktop App Entry / e2e:unpackaged failed with ERR_UNSUPPORTED_DIR_IMPORT loading vite.main.config.mts under plain Node — fixed by the contract-layer import change above.
  • SessionOrchestrator's adapter-factory wrapper originally threw for any excluded kind at construction, which broke session.list for daemons with even one persisted session of an excluded kind — moved to the startLive-only boundary described above.
  • Composer's harness icon incorrectly disappeared at ≤1 harness (should only hide the submenu) — reverted to harnesses.length > 0 for the icon.
  • Renderer identity.restrictions() defaulted to unrestricted on a missing IPC snapshot — now throws.
  • Minor cleanups: unreachable branch in daemonAllowedAgents(), an ambient-env guard now consistent with its neighbors, and the packaging files-exclude overlay now uses a unique temp dir instead of a fixed filename.

Validation

  • pnpm typecheck / pnpm lint / pnpm format:check clean
  • pnpm test: 3051–3052 passed; 3 failures confirmed pre-existing on the unmodified base (tmpdir/realpath quirk in release-artifact.test.ts, unrelated to this PR); one flaky timing failure observed intermittently in dev-mock-transport.test.ts (an unrelated pre-existing integration test, polling-based, flaky under the sandbox's concurrent load — passes cleanly in isolation)
  • cd apps/desktop && pnpm e2e:unpackaged: daemon/main/preload/renderer build and the full Electron IPC boundary read (linkcodeSystem/linkcodeConfig) now succeed; the only remaining failure locally is the script's hardcoded assert.equal(boundary.platform, 'linux'), pre-existing and Linux-CI-only (this sandbox is macOS)
  • TODO (post-merge, with the restricted brand registered in linkcode-config): package a restricted installer and run verify-artifacts.mts both ways; e2e config-canary restricted fixture

Linear: CODE-618

CODE-618: the client-side schema and brand-identity mirror must accept the
publisher's optional per-brand agents/services fields (subset of known agent
kinds / kebab-case service ids) before any restricted-brand bundle can be
consumed. Absent stays absent so unrestricted builds are byte-identical.
CODE-618: mirrors brand.ts's MAIN_VITE_BRAND_IDENTITY precedent for a new
MAIN_VITE_AGENT_RESTRICTIONS define, parsed once at boot into
AGENT_RESTRICTIONS (fail-closed on a present-but-invalid snapshot, same as
the brand identity). Exposes it to the renderer via a new synchronous
systemBridge.identity.restrictions() IPC channel (sendSync, like
settings.snapshot) so the composer's harness picker never flashes the full
agent set on a restricted build. Absent bundle agents/services is a
no-op end to end.
CODE-618: apps/daemon/src/config.ts#daemonAllowedAgents() parses
LINKCODE_ALLOWED_AGENTS (set by the desktop supervisor from AGENT_RESTRICTIONS,
next commit); restrictedAdapterFactory() wraps createAdapter to reject any
kind outside it via the existing EngineDeps.factory injection seam — no
change to @linkcode/agent-adapter itself. Only gates new adapter
construction: a session started before a restriction landed keeps replaying
on its existing adapter, and history reads never call this. Also skips
background-refreshing a disallowed agent's managed install at boot. `null`
(unrestricted) is an exact no-op.
CODE-618 acceptance (a): agentFilesExcludes() maps each disallowed agent kind
to its staged node_modules SDK package (claude-agent-sdk / codex /
opencode-ai sdk -- pi's SDK is a hosted download and grok-build has no SDK,
so neither needs an entry). package-app.mts wraps the resolved
electron-builder config in a temporary `extends` overlay adding these as
`files` excludes only when the rendered bundle declares `agents`;
verify-artifacts.mts asserts their absence post-pack by exact asar path
segment (never a prefix match, so e.g. @openai/codex-darwin-* can't
false-positive). Both are no-ops when the bundle carries no `agents`,
keeping the standard build's packaging and verification unchanged.
…ootstrap

CODE-618: BundledConfigBootstrap gains allowedAgents/allowedServices
(bundle.agents/.services ?? null); the dev sentinel and every existing
platform fixture stay null (unrestricted), so today's builds are unaffected.
new-thread-sheet.tsx's kind picker narrows to BUNDLED_CONFIG_BOOTSTRAP
.allowedAgents ?? every known kind, and hides its whole Section outright
(never just disables) once at most one kind remains selectable -- mobile is
single-process, so this needs no IPC.
… by brand

CODE-618 acceptance (c, d): selectableHarnessKinds(providers, allowedAgents?)
intersects the enabled set with the brand allowlist; workbench.tsx threads
it in from a new Workbench allowedAgents prop (desktop's app.tsx supplies
systemBridge.identity.restrictions().allowedAgents, keeping this package
IPC-free). composer-controls.tsx's ModelSelectorMenu now hides the harness
submenu at <=1 selectable harness instead of 0, so a single remaining
harness disappears everywhere the picker is rendered.

add-flow.tsx's ServiceCatalogView filters two axes: oauth entries by their
bound agent against allowedAgents, everything else (including custom, which
needs no special case since it is excluded by the same id-set intersection
as any other service) by allowedServices. serviceById, the account
list/detail, and account resolution stay unfiltered everywhere, so an
account bound to a since-hidden service keeps rendering and resolving.
agents-settings.tsx's row list gets the same allowedAgents narrowing (hides
the row rather than disabling its switch); view.ts's account-binding
resolution is untouched.

All of the above default to null (unrestricted) and are no-ops for the
standard/unbranded build.
Copilot AI lite review requested due to automatic review settings August 25, 2026 03:43
@linear-code

linear-code Bot commented Aug 25, 2026

Copy link
Copy Markdown

CODE-618

CODE-612

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown

Greptile Summary

The PR consumes build-time brand agent and service allowlists across desktop, mobile, daemon enforcement, packaging, and shared UI surfaces.

  • Extends config and identity schemas with validated optional allowlists.
  • Enforces agent restrictions at daemon live-start, runtime-discovery, asset-download, and package-content boundaries.
  • Exposes immutable restrictions through desktop IPC and filters agent and account-creation UI without hiding persisted history or accounts.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/host/engine/src/session/orchestrator.ts Places agent allowlist enforcement at the shared live-adapter startup boundary while preserving history access.
apps/daemon/src/index.ts Threads the daemon allowlist into engine startup, runtime reporting, managed refresh, and client-facing asset operations.
apps/desktop/src/main/daemon-supervisor.ts Passes the immutable desktop agent allowlist to the supervised daemon without changing unrestricted builds.
packages/system-plane/ipc/src/electron-renderer.ts Adds a synchronous restriction snapshot that fails closed when the main-process value is unavailable.
apps/desktop/scripts/package-app.mts Adds per-run builder overlays that exclude disallowed agent SDK packages from restricted artifacts.
packages/client/workbench/src/settings/providers/add-flow.tsx Filters only the add-account catalog by agent and service allowlists while preserving stored-account rendering.
packages/client/workbench/src/surface/workbench.tsx Intersects selectable new-session harnesses with the host-provided agent allowlist.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  B[Config build bundle] --> V[Vite build-time snapshot]
  V --> M[Electron main]
  M -->|SystemBridge snapshot| R[Desktop renderer]
  M -->|LINKCODE_ALLOWED_AGENTS| D[Daemon]
  D --> G[Live-start guard]
  D --> P[Runtime probe filter]
  D --> A[Asset-service gate]
  V --> K[Packaging exclusions]
  R --> U[Agent and service catalog filtering]
  B --> I[Mobile bundled config]
  I --> N[Mobile harness filtering]
Loading

Reviews (4): Last reviewed commit: "fix(engine,daemon): close review gaps in..." | Re-trigger Greptile

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

EngineDeps.factory is not only the session-spawn path — SessionOrchestrator.list() and HistoryService call it for capability lookups, so a throwing factory takes down session.list rather than just refusing a spawn.

Reviewed changes — full read of all 45 files across the 6 commits on chenyu/code-618, tracing the allowlist from the schema contract through desktop main, the daemon gate, packaging, and the four consuming UI surfaces.

  • Schema + brand-identity contractConfigBuildBundleSchema and the packages/foundation/common mirror accept optional non-empty, duplicate-free agents / services; absent stays unrestricted and unknown keys still fail closed.
  • Desktop mainMAIN_VITE_AGENT_RESTRICTIONS is inlined only from a generated bundle, parsed by agent-restrictions.ts (present-but-invalid aborts boot), and exposed to the renderer over a new synchronous identity.restrictions() sendSync channel.
  • Daemon gateLINKCODE_ALLOWED_AGENTS is injected by the supervisor, read by daemonAllowedAgents(), and wrapped into EngineDeps.factory by restrictedAdapterFactory; consentedManagedAgents is filtered at boot.
  • PackagingAGENT_SDK_PACKAGE_PATHS drives both a temp extends overlay adding files negations and the new verifyNoRestrictedAgentPackages asar assertion.
  • UIselectableHarnessKinds intersects the allowlist, the composer harness picker hides at ≤1 kind, the add-account catalog filters oauth entries by bound agent and everything else by service id, and mobile's new-thread sheet reads the bundled bootstrap.

The packaging half I verified independently and it is sound: the three SDKs hoist flat into the staging dir's node_modules/ under pnpm --prod deploy, apps/daemon/tsup.config.ts marks all three external so none are inlined into the daemon bundle, app-builder-lib's mergeFileSets concatenates files across extends (base first, overlay negations last) so the CODE-114 globs survive, and adapters load their SDK through dynamic import() so an absent package degrades to sdk-unavailable. brandConfig is absolute, so the extends target resolves as the comment claims.

⚠️ Three enforcement claims in the PR description are not what the code does

The description says "Runtime probing and managed downloads filter the same way" and "persisted-history reads are untouched". Neither holds: AgentRuntimeProber.collect() still reports a blocked kind as available/detected over agent-runtime.list, asset.ensure / asset.list in the engine have no allowlist check at all, and history reads go through the very factory the gate wraps. Separately, allowedServices has exactly one consumer — the add-account grid — so a services allowlist is advisory rather than enforced. None of this is a spawn bypass, but it does mean a restricted build tells the UI it can do things it cannot, and the daemon will happily download a blocked agent's CLI on request.

Technical details
# Restriction enforcement stops at `EngineDeps.factory`

## Affected sites
- `packages/host/agent-adapter/src/probe/prober.ts:97``collect()` builds the `AgentRuntimes` map for every probed kind with no `allowedAgents` intersection; served on `agent-runtime.list` via `packages/host/engine/src/agent/request-handler.ts:82`. A restricted build's Agents settings tab shows a blocked kind as installed and available.
- `packages/host/engine/src/asset/service.ts:55` — the `asset.ensure` handler calls `assets.ensure(id)` unconditionally. `apps/daemon/src/index.ts:191-194` filters only `consentedManagedAgents(assets)`, which is the boot auto-refresh, not this wire path.
- `packages/host/engine/src/asset/service.ts:51``asset.list` returns `assets.statuses()` unfiltered.
- `packages/host/engine/src/session/history-service.ts:82`, `:133`, `:215` — each calls `this.factory(kind)`, and `packages/host/engine/src/engine.ts:113` hands `HistoryService` the same gated factory. History reads are therefore *not* untouched; see the separate inline comment on `agent-factory.ts`.
- `packages/client/workbench/src/settings/providers/add-flow.tsx:59``isServiceSelectable` is applied only in `ServiceCatalogView`'s grid. Nothing daemon-side consults `services`.

## Required outcome
- The PR description and the `restrictedAdapterFactory` doc comment must describe what the code actually enforces, or the code must be extended to match them. Pick one — the mismatch is the problem.
- If probing stays unfiltered, decide deliberately: a restricted build surfacing a blocked agent as "available" is a UX bug even when the session start is correctly refused.
- State whether `services` is intended as enforcement or as catalog curation. If enforcement, it needs a home outside the renderer, since the renderer is not a trust boundary.

## Open questions for the human
- Is `asset.ensure` reachable for a blocked kind from any currently shipped UI path, or only from a hand-built wire client? I traced the onboarding caller in `packages/client/workbench/src/agent-runtime/onboarding.ts` but could not rule out other entry points.
- The Validation section defers packaging verification to post-merge. Is a restricted brand registered in `linkcode-config` yet, or does `verifyNoRestrictedAgentPackages` ship never having run against a real restricted artifact?

ℹ️ Nitpicks

  • apps/daemon/src/config.ts:102kinds.length > 0 ? kinds : null is unreachable. String.prototype.split never returns an empty array, and the raw === '' case already returned above, so kinds always has at least one element.
  • apps/desktop/scripts/config-bundle.mts:108if (env.MAIN_VITE_AGENT_RESTRICTIONS) is a truthiness check where the sibling guards use !== undefined. An explicitly-empty override slips past this one.
  • apps/desktop/scripts/package-app.mts:185join(tmpdir(), 'linkcode-desktop-agent-excludes.json') is a fixed name in a shared directory, written and never removed. Two concurrent packaging runs clobber each other, and on a multi-user host the path is predictable. mkdtempSync costs one line.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread apps/daemon/src/agent-factory.ts Outdated
Comment thread packages/presentation/ui/src/shell/composer-controls.tsx Outdated
Comment thread packages/system-plane/ipc/src/electron-renderer.ts Outdated
…iew findings

CODE-618 PR review fixups:

- schema/brand-identity: config-bundle.mts loads unbundled under plain Node
  (CI's Desktop App Entry / e2e:unpackaged), and its import chain reached
  brand-identity.ts's `import { AgentKindSchema } from '@linkcode/schema'` --
  a barrel re-exporting a directory, which the bare Node ESM loader cannot
  resolve (ERR_UNSUPPORTED_DIR_IMPORT). remote-config.ts's own
  `./model/primitives` import hit the same loader via the
  `@linkcode/schema/remote-config` subpath. Both now duplicate the known
  agent-kind list locally instead of importing it as a value, matching this
  codebase's CONFIG_PLATFORMS/CONFIG_CHANNELS precedent; `AgentKind` stays a
  type-only import (erased before it reaches the runtime loader).

- engine/daemon gating: the previous `EngineDeps.factory` wrapper threw for
  any excluded kind, but `SessionOrchestrator.list()` and `HistoryService`
  call the bare factory for every *persisted* record's `historyCapabilities`
  -- one history session of an excluded kind broke the whole `session.list`.
  Moved enforcement to the one place that actually constructs a *live*
  adapter to start/resume/relaunch a run (`SessionOrchestrator.startLive`),
  gated by a new `EngineDeps.allowedAgents` threaded in from the daemon;
  history reads and `list()` are never touched. Renamed
  apps/daemon/src/agent-factory.ts to agent-restrictions.ts and replaced the
  removed `restrictedAdapterFactory` with `filterAgentRuntimes` (masks a
  disallowed agent as `missing` in the boot-probe snapshot regardless of how
  it was actually detected) and `restrictedAssetService` (refuses
  `asset.ensure` for an excluded agent's managed download, reusing
  ManagedAssetService's existing "cannot be installed here" path -- tool
  assets are never agent-gated). `services` stays catalog-only filtering, as
  planned; daemon-side enforcement was never in scope for it.

- composer-controls.tsx: reverted the harness AgentIcon's visibility back to
  `harnesses.length > 0` (only the submenu itself hides at <=1) -- a single
  remaining harness, restricted build or a user manually disabling every
  other one from Settings, still needs the icon since it is the only
  harness signal once the picker disappears.

- electron-renderer.ts: `identity.restrictions()` now throws when the main
  process's sendSync snapshot is missing instead of defaulting to
  unrestricted -- this gates which agents/services render, so a missing
  snapshot must fail closed like every other security boundary here, not
  silently widen access. Kept the guard manual (no foxts/guard import):
  apps/desktop's preload externalizes every non-workspace dependency it
  doesn't explicitly bundle, the same reason this file already stays
  zod-free.

- small cleanups: dropped an unreachable branch in daemonAllowedAgents()
  (split always yields >=1 element for a non-empty string);
  config-bundle.mts's ambient MAIN_VITE_AGENT_RESTRICTIONS guard now matches
  its neighboring `!== undefined` checks; package-app.mts's files-exclude
  overlay now writes into a fresh mkdtempSync directory instead of a fixed
  filename, so two concurrent (or one interrupted) packaging runs can't
  collide.
Copilot AI review requested due to automatic review settings August 25, 2026 10:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The three findings from the previous review are genuinely fixed (factory-gate regression, composer icon, IPC fail-closed) and I've resolved those threads. Moving the gate into SessionOrchestrator.startLive was the right call — the new session.list-over-a-persisted-excluded-kind test locks in the regression that motivated it. One consequence of that move still needs handling: agent.catalog was covered by the old factory wrapper and is now ungated, and it is reached on every restricted-build render of the new-session surface.

The agent.catalog path (inline on deps.ts) is the only finding I'd call blocking. It isn't a malicious-client hypothetical: useAgentStartCatalogs (packages/client/workbench/src/surface/use-agent-catalogs.ts:22-30) fires getAgentCatalog for claude-code, codex, opencode, and grok-build unconditionally — only pi is gated on runtime availability, and filterAgentRuntimes doesn't reach that call anyway. So on a restricted brand build that excludes codex, simply opening the new-session surface spawns codex app-server if the user has the CLI on PATH. Details and the fix location are in the inline comment.

Design question: history reads of an excluded kind still spawn its server

SessionOrchestrator's new doc comment (orchestrator.ts:45-49) is explicit and deliberate: the bare factory stays in use for list/read/resolveLiveBranchCursor so a persisted session of an excluded kind remains readable. list() is genuinely cheap — I confirmed .historyCapabilities is a plain property read on a constructed-but-inert adapter, no spawn for any of the five kinds. But an actual history read is not: opencode's read path goes through the shared opencode serve history server, and codex's through codex app-server. So a restricted build that excludes codex will still spawn codex app-server when a user opens a pre-restriction codex thread.

That may well be the intended trade — "excluded" plausibly means "can't start new runs", not "the binary must never execute". But it's worth stating the intent explicitly, because a brand excluding an agent for compliance/licensing reasons would likely read it the other way. If the narrower reading is correct, the alternative is to keep list() unfiltered (so threads stay visible) while refusing the history read itself with the same forbidden code, rather than reverting to a gate that breaks listing. No change requested here — I'd rather the answer be recorded than guessed at.

Verified sound

  • isServiceSelectable's two-axis split (oauth by bound agent, everything else by service id) is coherent and the custom-has-no-special-case reasoning holds; the account list/detail staying unfiltered is correct and tested.
  • parseDesktopAgentRestrictionsLINKCODE_ALLOWED_AGENTSdaemonAllowedAgents()EngineDeps.allowedAgents is fully wired end to end, including the delete env.LINKCODE_ALLOWED_AGENTS branch for unrestricted builds.
  • The requireExactKeys required/optional split is a clean extension, and the agents/services validation covers empty, unknown, malformed, and duplicate cases in both the artifact and the bundle schema.
  • The hand-duplicated agent-kind lists' ESM justification checks out — remote-config.ts now has only bare imports, which is what makes it loadable from config-bundle.mts under plain Node.
  • sendSync for the restrictions snapshot matches the existing settings/daemon-url precedent, and failing closed there is the right asymmetry.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread packages/host/engine/src/deps.ts
Comment thread packages/foundation/schema/src/remote-config.ts
Comment thread apps/daemon/src/agent-restrictions.ts Outdated
package-app.mts and verify-artifacts.mts run under bare node, whose ESM
loader rejects extensionless relative imports; tsx-run scripts were
unaffected.
Copilot AI review requested due to automatic review settings August 25, 2026 11:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

- gate agent.catalog in AgentRequestHandler: startCatalog spawns real
  agent processes for codex/opencode, and the old factory wrapper's
  coverage of this path was lost when enforcement moved to startLive
- hide excluded agents' managed assets from asset statuses/subscribe so
  the wrapper agrees with filterAgentRuntimes and the ensure refusal
- pin both hand-duplicated agent-kind lists to AgentKindSchema with
  drift tests
Copilot AI review requested due to automatic review settings August 26, 2026 12:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the one new commit since my last review (068c291..5671d2a):

  • Gated agent.catalog in AgentRequestHandler behind allowedAgents, replying forbidden before any startCatalog spawn.
  • Hid excluded agents' managed assets from statuses() and subscribe() in restrictedAssetService, so the three restriction surfaces now agree.
  • Pinned both hand-duplicated agent-kind lists to AgentKindSchema.options with drift tests.
  • Rewrote the EngineDeps.allowedAgents doc comment to name both enforcement points.

All three of my prior threads are genuinely fixed and I've resolved them. The agent.catalog gate is exactly where I asked for it, and the new test provably fails when I neuter the condition (I verified this by temporarily replacing the gate with if (false as boolean) and re-running — the adapters).toHaveLength(0) assertion fires). The two drift tests are real coverage, and the asset-filtering change has no regression path: tool assets and allowed agents always pass through, aigateway reads the raw assets, and the unrestricted path is a strict identity return.

Important

One finding: agent-login.start is still an ungated spawn path for an excluded agent's CLI — the same class the commit just closed for agent.catalog, sitting two cases below it in the same switch. Details inline.

Design questions from my last review — answered, closing them out

The PR body now states both intents explicitly, so I'm not re-raising either:

  • History reads of an excluded kind: persisted sessions stay fully listable and readable. "Excluded" means "can't start new runs", not "the binary must never execute". Recorded — that's the trade I flagged, now deliberate.
  • services enforcement: catalog-display filtering at the UI layer only, with no daemon-side enforcement, as planned.

Not blocking, but worth naming before merge

The packaging half of this feature has never run against a real restricted artifact. verifyNoRestrictedAgentPackages, the electron-builder extends overlay, and the e2e config-canary restricted fixture are all deferred to post-merge verification pending a restricted brand existing in linkcode-config. That's a reasonable sequencing call given nothing in the standard unbranded build changes, but it does mean the asar assertion is untested code guarding the compliance-relevant half of the feature. Worth a tracked follow-up rather than trusting it silently on the first real branded release.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread packages/host/engine/src/agent/request-handler.ts
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.

2 participants