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
47 changes: 0 additions & 47 deletions .github/ISSUE_TEMPLATE/gastown-bug.yml

This file was deleted.

2 changes: 1 addition & 1 deletion ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Manage shared web env var additions and rotations with `pnpm web:env set <VARIAB
- `GITHUB_ADMIN_STATS_TOKEN` - Token for admin GitHub API stats lookups; used in `apps/web/src/scripts/backfill-pr-author-github-ids.ts`. `[SECRET]`
- `GITHUB_CLI_PAT` - GitHub personal access token for `gh` CLI operations inside contractors; used in `services/gastown/container/src/process-manager.ts`. `[SECRET]`
- `GITHUB_TOKEN` - Generic GitHub token for API calls used as fallback when `GIT_TOKEN` or `GITHUB_CLI_PAT` is absent; used in `services/gastown/container/src/process-manager.ts`. `[SECRET]`
- `GH_TOKEN` - Short alias for GitHub token; used in `services/gastown/container/plugin/mayor-tools.ts`. `[SECRET]`
- `GH_TOKEN` - Short alias used by GitHub CLI processes. `[SECRET]`
- `GIT_TOKEN` - Dynamic git credential token (often a GitHub App installation token) scoped for git clone/push; propagated from Town DO to containers in `services/gastown/src/dos/town/container-dispatch.ts` and `services/gastown/container/src/agent-runner.ts`. `[SECRET]`
- `GOOGLE_WORKSPACE_OAUTH_CLIENT_ID` - Google Workspace OAuth client ID; used in tests and integration code. [PUBLIC]
- `GOOGLE_WORKSPACE_OAUTH_CLIENT_SECRET` - Google Workspace OAuth client secret. `[SECRET]`
Expand Down
20 changes: 3 additions & 17 deletions apps/storybook/stories/gastown/TerminalBarPickers.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { Meta, StoryObj } from '@storybook/nextjs';
import { PositionPicker, BugReportMenu } from '@/components/gastown/TerminalBar';
import { PositionPicker } from '@/components/gastown/TerminalBar';

// PositionPicker (Radix Popover) and BugReportMenu (Radix DropdownMenu) are the
// gastown terminal-bar overlays. Each renders its own trigger; the stories open
// the overlay via a play function so the screenshot captures the open state.
// PositionPicker renders its own trigger; the story opens the overlay via a play
// function so the screenshot captures the open state.

const meta: Meta = {
title: 'Overlays/Popovers/TerminalBarPickers',
Expand Down Expand Up @@ -46,16 +45,3 @@ export const Position: Story = {
await new Promise(resolve => setTimeout(resolve, 150));
},
};

export const BugReport: Story = {
render: () => (
<TerminalControlStrip>
<BugReportMenu />
</TerminalControlStrip>
),
play: async () => {
await new Promise(resolve => setTimeout(resolve, 50));
openByPointer(document.querySelector('button[aria-label="Report a bug"]'));
await new Promise(resolve => setTimeout(resolve, 150));
},
};
55 changes: 0 additions & 55 deletions apps/web/src/components/gastown/TerminalBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ import { Switch } from '@/components/ui/switch';
import { useConfirm } from '@/components/ui/confirm';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
useTerminalBar,
COLLAPSED_SIZE,
Expand All @@ -39,9 +33,6 @@ import {
PanelTop,
PanelLeft,
PanelRight,
Bug,
Github,
MessageCircle,
CircleDollarSign,
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
Expand Down Expand Up @@ -782,13 +773,6 @@ function TabBar({
</div>
)}

{/* Bug report dropdown */}
{horizontal && (
<div className="shrink-0">
<BugReportMenu />
</div>
)}

{/* Position picker */}
<PositionPicker current={position} onSelect={setPosition} horizontal={horizontal} />
</div>
Expand Down Expand Up @@ -855,45 +839,6 @@ export function PositionPicker({
);
}

// ── Bug Report Menu ──────────────────────────────────────────────────────

const BUG_REPORT_OPTIONS = [
{
label: 'New GitHub Issue',
href: 'https://github.com/Kilo-Org/cloud/issues/new?template=gastown-bug.yml&labels=gastown,bug',
Icon: Github,
},
{
label: 'Discord Channel',
href: 'https://discord.com/channels/1349288496988160052/1485796776635142174',
Icon: MessageCircle,
},
];

export function BugReportMenu() {
return (
<DropdownMenu>
<DropdownMenuTrigger
aria-label="Report a bug"
className="mr-2 flex items-center gap-1 rounded px-2 py-1 text-[10px] text-white/30 transition-colors hover:bg-white/[0.04] hover:text-white/50"
>
<Bug className="size-3" />
<span className="hidden sm:inline">Report a Bug</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" sideOffset={6} className="w-max">
{BUG_REPORT_OPTIONS.map(({ label, href, Icon }) => (
<DropdownMenuItem key={label} asChild>
<a href={href} target="_blank" rel="noopener noreferrer">
<Icon className="size-3.5" />
{label}
</a>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}

// ── Terminal Content Area ─────────────────────────────────────────────────

function TerminalContent({
Expand Down
154 changes: 5 additions & 149 deletions services/gastown/container/plugin/mayor-tools.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { MayorGastownClient } from './client';
import type {
Agent,
Expand Down Expand Up @@ -172,6 +172,10 @@ describe('mayor tools', () => {
tools = createMayorTools(client);
});

it('does not expose GitHub issue reporting', () => {
expect(tools).not.toHaveProperty('gt_report_bug');
});

describe('gt_sling', () => {
it('delegates a single task and returns result summary', async () => {
const result = await tools.gt_sling.execute(
Expand Down Expand Up @@ -411,154 +415,6 @@ describe('mayor tools', () => {
});
});

describe('gt_report_bug', () => {
const originalEnv = process.env;

beforeEach(() => {
process.env = {
...originalEnv,
GH_TOKEN: 'fake-gh-token',
GASTOWN_TOWN_ID: 'town-123',
GASTOWN_AGENT_ID: 'mayor-agent-1',
};
});

afterEach(() => {
process.env = originalEnv;
vi.restoreAllMocks();
});

it('returns fallback message when GH_TOKEN is not set', async () => {
delete process.env.GH_TOKEN;
const result = await tools.gt_report_bug.execute(
{
title: 'Test bug',
description: 'Something broke',
area: 'Mayor / Chat' as const,
},
CTX
);
expect(result).toContain('GH_TOKEN is not available');
expect(result).toContain('github.com/Kilo-Org/cloud/issues/new');
});

it('reports potential duplicates when search finds matches', async () => {
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(
new Response(
JSON.stringify({
items: [
{
number: 42,
title: 'Similar bug',
html_url: 'https://github.com/Kilo-Org/cloud/issues/42',
},
],
}),
{ status: 200 }
)
);

const result = await tools.gt_report_bug.execute(
{
title: 'Similar bug report',
description: 'Something broke',
area: 'Mayor / Chat' as const,
},
CTX
);

expect(result).toContain('potentially related');
expect(result).toContain('#42');
expect(fetchSpy).toHaveBeenCalledOnce();
});

it('creates issue when no duplicates found', async () => {
const fetchSpy = vi
.spyOn(globalThis, 'fetch')
.mockResolvedValueOnce(new Response(JSON.stringify({ items: [] }), { status: 200 }))
.mockResolvedValueOnce(
new Response(
JSON.stringify({ number: 99, html_url: 'https://github.com/Kilo-Org/cloud/issues/99' }),
{ status: 201 }
)
);

const result = await tools.gt_report_bug.execute(
{
title: 'New bug',
description: 'Something broke',
area: 'Container / Git' as const,
rig_id: 'rig-5',
recent_errors: 'Error: connection refused',
},
CTX
);

expect(result).toContain('#99');
expect(result).toContain('Bug report filed');
expect(fetchSpy).toHaveBeenCalledTimes(2);

// Verify the create call body
const createCall = fetchSpy.mock.calls[1];
const body = JSON.parse(createCall[1]?.body as string);
expect(body.title).toBe('[Gastown] New bug');
expect(body.labels).toEqual(['bug', 'gt:mayor']);
expect(body.body).toContain('town-123');
expect(body.body).toContain('rig-5');
expect(body.body).toContain('connection refused');
});

it('retries without labels on 422 (label permission error)', async () => {
const fetchSpy = vi
.spyOn(globalThis, 'fetch')
.mockResolvedValueOnce(new Response(JSON.stringify({ items: [] }), { status: 200 }))
.mockResolvedValueOnce(
new Response('Validation Failed: label permissions', { status: 422 })
)
.mockResolvedValueOnce(
new Response(
JSON.stringify({
number: 100,
html_url: 'https://github.com/Kilo-Org/cloud/issues/100',
}),
{ status: 201 }
)
);

const result = await tools.gt_report_bug.execute(
{ title: 'Label bug', description: 'Labels fail', area: 'Other' as const },
CTX
);

expect(result).toContain('#100');
expect(result).toContain('Bug report filed');
expect(fetchSpy).toHaveBeenCalledTimes(3);

// Retry call should omit labels
const retryCall = fetchSpy.mock.calls[2];
const retryBody = JSON.parse(retryCall[1]?.body as string);
expect(retryBody.labels).toBeUndefined();
});

it('handles create failure gracefully', async () => {
vi.spyOn(globalThis, 'fetch')
.mockResolvedValueOnce(new Response(JSON.stringify({ items: [] }), { status: 200 }))
.mockResolvedValueOnce(new Response('Forbidden', { status: 403 }));

const result = await tools.gt_report_bug.execute(
{
title: 'Bug',
description: 'Broken',
area: 'Other' as const,
},
CTX
);

expect(result).toContain('Failed to create issue');
expect(result).toContain('403');
});
});

describe('gt_nudge', () => {
it('sends a nudge and returns the nudge_id', async () => {
const result = await tools.gt_nudge.execute(
Expand Down
Loading
Loading