Repository navigation
feat(ask): --open adds an Open button that shows a page to sign in to in the thread's panel #604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7821d3d
08e21de
16b409d
1ce638e
f5d3b27
ca05783
e5ffcdb
5ed95f6
93b170a
3dd6d44
9e50fc3
16fac49
7d5f772
87796d0
4e3ec5b
bc1c9f7
ba61f2f
fee553c
c7cf139
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,7 @@ export function agentGuide(asyncQuestions: boolean, logAccess = false, skills: r | |
| '`boite show <file>[:line]`, `boite diff [file]`, `boite browse <url>` open the panel. `boite preview <file.html>` previews HTML and local assets. `boite attach <file>` sends files/media into chat (512 MB max).', | ||
| 'Write a file reference as inline code, `path:line`: a click opens it in the panel.', | ||
| VIEW_GUIDE_LINE, | ||
| ...(asyncQuestions ? ['`boite ask "<question>" [option ...]` asks asynchronously. Keep working on independent tasks; the answer arrives as a message. Silence grants no approval.'] : []), | ||
| ...(asyncQuestions ? ['`boite ask [--open <url>] "<question>" [option ...]` asks asynchronously. Keep working on independent tasks; the answer arrives as a message. Silence grants no approval. Login wall: `--open <url>`, the site and why, options "Signed in" Skip; never in chat.'] : []), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win Specify what must not be requested in chat. “Never in chat” does not identify the prohibited content. Explicitly tell the agent not to ask for passwords or other login credentials in chat. The PR objective requires this boundary. 🤖 Prompt for AI Agents |
||
| '`boite settings [agent] [request|open]` reads Boite settings or a persistent agent\'s, or puts a card in chat for the user to change one.', | ||
| 'Task tracking is optional. Use it only when steps help you and the user. `boite task add <text>` / `boite task start|done <id>` track those steps. `boite todo list|add <text>|claim <id>` manages project todos; claim marks work done for user confirmation.', | ||
| '`boite projects` lists projects; `boite projects add <folder>` adds one. `boite thread new <project> <brief>` starts independent work in a project, not a subagent; its first answer comes back.', | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { afterEach, expect, test } from 'bun:test'; | ||
| import { connect } from '../src/client.ts'; | ||
| import { echoThread, startTestCore, waitFor, type TestCore } from './harness.ts'; | ||
|
|
||
| let h: TestCore; | ||
| afterEach(async () => { await h?.stop(); }); | ||
|
|
||
| test('boite ask --open keeps the page on the card, and refuses an address that is not a web page or carries a login', async () => { | ||
| h = await startTestCore(); | ||
| const owner = await h.connect(); | ||
| const { threadId } = await echoThread(h, owner); | ||
| await owner.call('turns.start', { threadId, prompt: 'hello' }); | ||
| await waitFor(() => h.core.journal.lastTurn(threadId) !== null); | ||
|
|
||
| const ask = (open: string) => owner.call('questions.ask', { threadId, text: 'billing.example: to download the invoices', options: ['Signed in', 'Skip'], open }); | ||
| const { questionId } = await ask('https://billing.example/login?next=%2Finvoices'); | ||
| const card = h.core.journal.listMessages(threadId).flatMap(message => message.parts).find(part => part.type === 'question' && part.questionId === questionId); | ||
| expect(card).toMatchObject({ open: { url: 'https://billing.example/login?next=%2Finvoices' }, async: true }); | ||
|
|
||
| await expect(ask('file:///etc/passwd')).rejects.toThrow('http or https'); | ||
| await expect(ask('https://me:hunter2@billing.example/')).rejects.toThrow('user name or password'); | ||
| }); | ||
|
|
||
| test('a paired phone opens the page of a pending question in the conversation it watches, and nothing else', async () => { | ||
| h = await startTestCore(); | ||
| const owner = await h.connect(); | ||
| const { threadId } = await echoThread(h, owner); | ||
| await owner.call('turns.start', { threadId, prompt: 'hello' }); | ||
| await waitFor(() => h.core.journal.lastTurn(threadId) !== null); | ||
| const { questionId } = await owner.call('questions.ask', { threadId, text: 'ci.example: to read the failed run', options: ['Signed in', 'Skip'], open: 'https://ci.example/login' }); | ||
| const { questionId: plain } = await owner.call('questions.ask', { threadId, text: 'Which branch?', options: ['main', 'dev'] }); | ||
| const opened: string[] = []; | ||
| h.core.browser.command = (async ({ action }: { action: { url: string } }) => { opened.push(action.url); return { value: { ok: true } }; }) as typeof h.core.browser.command; | ||
| const { grant } = await owner.call('pairing.grant', {}); | ||
| const phone = await connect(h.url, '', { grant, client: { name: 'pwa', version: 'test' } }); | ||
| try { | ||
| await expect(phone.call('questions.openPage', { threadId, questionId })).rejects.toThrow('subscribe to the active conversation'); | ||
| await phone.call('threads.subscribe', { threadId }); | ||
| await phone.call('questions.openPage', { threadId, questionId }); | ||
| expect(opened).toEqual(['https://ci.example/login']); | ||
| await expect(phone.call('questions.openPage', { threadId, questionId: plain })).rejects.toThrow('not a pending question with a page'); | ||
| await expect(phone.call('panel.open', { threadId, surface: { kind: 'browser', url: 'https://ci.example/login' } })).rejects.toThrow('for the owner only'); | ||
| expect(opened).toHaveLength(1); | ||
| } finally { phone.close(); } | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,9 @@ | |
| replying = false, | ||
| submit, | ||
| skip, | ||
| onwrite | ||
| onwrite, | ||
| page, | ||
| onopen | ||
| }: { | ||
| text: string; | ||
| options: QuestionOption[]; | ||
|
|
@@ -43,6 +45,9 @@ | |
| skip?: () => unknown; | ||
| /** Hands the composer to this question, when another one or an ordinary message has it. */ | ||
| onwrite?: () => void; | ||
| /** The page asked with `boite ask --open`, and what its Open button does. */ | ||
| page?: string; | ||
| onopen?: () => unknown; | ||
| } = $props(); | ||
|
|
||
| let sent = $state(false); | ||
|
|
@@ -165,6 +170,9 @@ | |
|
|
||
| {#if pending} | ||
| <div class="actions"> | ||
| {#if page && onopen} | ||
| <button type="button" class="ghost" data-testid="question-open" title={page} onmousedown={keepFocus} onclick={onopen}><span class="ui-label">{strings.chat.questionOpenPage}</span></button> | ||
| {/if} | ||
|
Comment on lines
+173
to
+175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: rg -n 'Visual changes|phone widths|captures|QuestionCard' AGENTS.md packages/ui docs/development.md | head -85
sed -n '155,195p' packages/ui/src/components/QuestionCard.svelteRepository: beboite/boite Length of output: 5874 Add the required phone-width capture.
🤖 Prompt for AI Agents |
||
| {#if skip} | ||
| <button type="button" class="ghost" data-testid="question-skip" disabled={sent} onmousedown={keepFocus} onclick={pass}><span class="ui-label">{strings.chat.questionSkip}</span></button> | ||
| {/if} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: beboite/boite
Length of output: 1804
Make the browser guide clickable.
docs/browser.mdis plain text, so readers cannot open the guide from this sentence. Use a relative Markdown link:Suggested fix
🤖 Prompt for AI Agents