Skip to content
Merged
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
16 changes: 16 additions & 0 deletions .github/workflows/verify-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ jobs:
- name: Check format
run: npm run format

typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
cache: npm
- name: Install dependencies
run: npm ci
- name: Check types
run: npm run typecheck

build:
name: Build
runs-on: ubuntu-latest
Expand Down
10 changes: 7 additions & 3 deletions agent/runAgentForOnePass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,15 @@ export async function runAgentForOnePass(
hasUnknownPrices: sessionStats.hasUnknownPrices,
});

// `_maxTurns` is `number | null` upstream; null means "unset", in which case we
// keep the configured limit rather than clobbering it (a null here would make
// the `currentTurn + 1 >= maxTurns` check below fire immediately).
const nextMaxTurns = stream.state._maxTurns ?? trackedState.maxTurns;
if (
trackedState.currentTurn !== stream.state._currentTurn || trackedState.maxTurns !== stream.state._maxTurns
trackedState.currentTurn !== stream.state._currentTurn || trackedState.maxTurns !== nextMaxTurns
) {
trackedState.currentTurn = stream.state._currentTurn;
trackedState.maxTurns = stream.state._maxTurns;
trackedState.maxTurns = nextMaxTurns;
emitToListeners('SettingsUpdated', undefined);
if (trackedState.currentTurn + 1 >= trackedState.maxTurns) {
await pushNewItemsIntoSession();
Expand Down Expand Up @@ -318,7 +322,7 @@ export async function runAgentForOnePass(
});

trackedState.currentTurn = stream.state._currentTurn;
trackedState.maxTurns = stream.state._maxTurns;
trackedState.maxTurns = stream.state._maxTurns ?? trackedState.maxTurns;
emitToListeners('SettingsUpdated', undefined);

if (stream.interruptions?.length) {
Expand Down
4 changes: 2 additions & 2 deletions ink/components/BlinkingTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export function BlinkingTextInput({

return (
<Box flexGrow={1} minWidth={1}>
<Text wrap="end">{displayContent}</Text>
<Text wrap="truncate-end">{displayContent}</Text>
</Box>
);
}
Expand Down Expand Up @@ -216,7 +216,7 @@ export function BlinkingTextInput({

result.push(
<Box key={lineIndex} flexGrow={1}>
<Text wrap="end">{lineContent}</Text>
<Text wrap="truncate-end">{lineContent}</Text>
</Box>,
);
if (lineIndex < lines.length - 1) {
Expand Down
6 changes: 4 additions & 2 deletions ink/configurationWizard/ApiKeyStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ describe('ApiKeyStep', () => {
it('renders API key prompt for OpenAI', () => {
const onConfirm = vi.fn();
const onBack = vi.fn();
const { lastFrame } = render(<ApiKeyStep provider="OpenAI" onConfirm={onConfirm} onBack={onBack} />);
const { lastFrame } = render(
<ApiKeyStep provider="OpenAI" defaultValue="" onConfirm={onConfirm} onBack={onBack} />,
);

expect(lastFrame()).toContain('Can you provide us with your OpenAI API key?');
expect(lastFrame()).toContain('Get your key at: https://platform.openai.com/api-keys');
Expand All @@ -29,7 +31,7 @@ describe('ApiKeyStep', () => {
it('calls onBack when ESC is pressed', () => {
const onConfirm = vi.fn();
const onBack = vi.fn();
render(<ApiKeyStep provider="OpenAI" onConfirm={onConfirm} onBack={onBack} />);
render(<ApiKeyStep provider="OpenAI" defaultValue="" onConfirm={onConfirm} onBack={onBack} />);

const inputHandler = (useInput as any).mock.calls[0][0];
inputHandler('', { escape: true });
Expand Down
6 changes: 4 additions & 2 deletions ink/configurationWizard/ApiUrlStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ describe('ApiUrlStep', () => {
it('renders API URL prompt for Ollama', () => {
const onConfirm = vi.fn();
const onBack = vi.fn();
const { lastFrame } = render(<ApiUrlStep provider="Ollama" onConfirm={onConfirm} onBack={onBack} />);
const { lastFrame } = render(
<ApiUrlStep provider="Ollama" defaultValue="" onConfirm={onConfirm} onBack={onBack} />,
);

expect(lastFrame()).toContain('Where are you hosting Ollama?');
});

it('calls onBack when ESC is pressed', () => {
const onConfirm = vi.fn();
const onBack = vi.fn();
render(<ApiUrlStep provider="Ollama" onConfirm={onConfirm} onBack={onBack} />);
render(<ApiUrlStep provider="Ollama" defaultValue="" onConfirm={onConfirm} onBack={onBack} />);

const inputHandler = (useInput as any).mock.calls[0][0];
inputHandler('', { escape: true });
Expand Down
2 changes: 2 additions & 0 deletions ink/configurationWizard/ModelSelectionStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe('ModelSelectionStep', () => {
<ModelSelectionStep
title="Pick a model"
models={['model-1', 'model-2']}
defaultValue=""
onConfirm={onConfirm}
onBack={onBack}
/>,
Expand All @@ -42,6 +43,7 @@ describe('ModelSelectionStep', () => {
<ModelSelectionStep
title="Pick a model"
models={['model-1']}
defaultValue=""
onConfirm={onConfirm}
onBack={onBack}
/>,
Expand Down
3 changes: 2 additions & 1 deletion ink/configurationWizard/ProviderStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ describe('ProviderStep', () => {

it('renders provider selection message', () => {
const onConfirm = vi.fn();
const { lastFrame } = render(<ProviderStep onConfirm={onConfirm} />);
const onExit = vi.fn();
const { lastFrame } = render(<ProviderStep defaultValue="" onConfirm={onConfirm} onExit={onExit} />);

expect(lastFrame()).toContain('What model provider would you like to use today?');
});
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"format:fix": "dprint fmt",
"format:staged": "dprint check --staged --allow-no-files",
"format-and-lint:fix": "npm run format:fix && npm run lint:fix",
"typecheck": "tsc --noEmit",
"test": "vitest --run",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest",
Expand Down
4 changes: 3 additions & 1 deletion tools/browser/browserClickTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const ToolParameters = z.object({
export async function execute({ selector, button, clickCount }: z.infer<typeof ToolParameters>) {
try {
const page = await getPage();
await page.click(selector, { button, clickCount });
// Puppeteer calls this `count`; passing `clickCount` was silently ignored, so
// multi-click requests always performed a single click.
await page.click(selector, { button, count: clickCount });
Comment thread
dawsontoth marked this conversation as resolved.
return `Successfully clicked on ${selector}`;
} catch (error) {
return `Error clicking on ${selector}: ${error}`;
Expand Down
7 changes: 5 additions & 2 deletions tools/files/applyPatchTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,18 @@ async function getSkillsRead(): Promise<string[]> {
}
}

function pickExistingSkill(candidates: string[]): string | null {
/** The skill names `getHarperSkillTool` actually accepts. */
type HarperSkillName = (typeof harperSkills)[number];

function pickExistingSkill(candidates: readonly HarperSkillName[]): HarperSkillName | null {
for (const c of candidates) { if (harperSkills.includes(c)) { return c; } }
return null;
}

async function requiredSkillForOperation(
path: string,
type: 'create_file' | 'update_file' | 'delete_file' | 'overwrite_file',
): Promise<string | null> {
): Promise<HarperSkillName | null> {
if (type === 'delete_file') { return null; }
const p = normalizedPath(path);
const read = await getSkillsRead();
Expand Down