Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/project-default-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@inkeep/open-knowledge': patch
---

Use `agents.defaultAgent` in a project config to choose Codex or Pi for the Ask AI composer on machines that have no locally remembered agent choice. A user-selected agent continues to take precedence.
5 changes: 4 additions & 1 deletion packages/app/src/components/BottomComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@ import {
} from '@/editor/selection-context';
import type { EditorSurface } from '@/editor/selection-stats';
import { useSelectionContext } from '@/hooks/use-selection-context';
import { useConfigContextOptional } from '@/lib/config-context';
import { resolveDefaultCli } from '@/lib/default-cli-resolver';
import { VISIBLE_TARGETS } from '@/lib/handoff/targets';
import { matchesKeyboardShortcut } from '@/lib/keyboard-shortcuts';
import { recordOnboardingAskedAi } from '@/lib/onboarding-signals';
import {
loadStickyAgent,
parseStickyCliId,
resolveInitialAgentPreference,
resolveStickyAgent,
saveStickyAgent,
terminalCliId,
Expand Down Expand Up @@ -199,6 +201,7 @@ export function BottomComposer({
const effectiveSurface: EditorSurface = surface ?? 'wysiwyg';
const reduced = useReducedMotion();
const workspace = useWorkspace();
const projectDefaultAgent = useConfigContextOptional()?.projectConfig?.agents?.defaultAgent;
const { pageMeta } = usePageList();
const { states } = useInstalledAgents();
const { dispatch } = useHandoffDispatch();
Expand Down Expand Up @@ -465,7 +468,7 @@ export function BottomComposer({
// sentinel (`terminal-cli:<cli>`) only resolves to terminal mode when the
// launcher is available, so a sticky CLI pick degrades to the first app target
// on the web host.
const effectiveId = selectedId ?? stickyId;
const effectiveId = selectedId ?? resolveInitialAgentPreference(stickyId, projectDefaultAgent);
// An explicit CLI pick (sticky or this session) launches that CLI regardless of
// install state — a missing binary just prints "command not found".
const explicitCli: TerminalCli | null =
Expand Down
11 changes: 11 additions & 0 deletions packages/app/src/lib/unified-agent-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import {
loadStickyAgent,
parseStickyCliId,
resolveInitialAgentPreference,
resolveStickyAgent,
saveStickyAgent,
TERMINAL_CLI_ID,
Expand Down Expand Up @@ -50,6 +51,16 @@ function states(
) as Record<HandoffTarget, InstallState>;
}

describe('resolveInitialAgentPreference', () => {
test('uses the project default when this machine has no local selection', () => {
expect(resolveInitialAgentPreference(null, 'terminal-cli:pi')).toBe('terminal-cli:pi');
});

test('keeps the local user selection over a project default', () => {
expect(resolveInitialAgentPreference('codex', 'terminal-cli:pi')).toBe('codex');
});
});

describe('unified-agent-store — load/save round-trip', () => {
test('round-trips through the unified key', () => {
const storage = makeStorage();
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/lib/unified-agent-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ export function saveStickyAgent(id: HandoffTarget | string, storage?: StickyAgen
}
}

/** Browser-local user choice wins over an installer-provided workspace default. */
export function resolveInitialAgentPreference(
stickyId: string | null,
projectDefault: string | null | undefined,
): string | null {
return stickyId ?? projectDefault ?? null;
}

/**
* Resolve which app agent the composer should target, given live install state
* and the sticky preference:
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,17 @@ export const ConfigSchema = z.looseObject({
// config paths are a user-facing `~/.ok/global.yml` contract and hard to rename.
agents: z
.looseObject({
// A workspace-provided initial handoff preference. It is intentionally a
// PROJECT field so an installer can select the runtime that owns this
// knowledge base without modifying a user's browser-local last-used
// choice. The app uses it only when no local preference exists.
defaultAgent: z.enum(['codex', 'pi', 'terminal-cli:pi']).optional().register(fieldRegistry, {
scope: 'project',
agentSettable: false,
defaultScope: 'project',
description:
'Initial agent for this workspace when this machine has no locally remembered choice. Allowed values are codex, pi, and terminal-cli:pi. A local user selection always takes precedence.',
}),
autoApproveOkTools: z
.boolean()
.register(fieldRegistry, {
Expand Down