Skip to content

Commit 533ce7f

Browse files
Sync public snapshot from freebuff-private
1 parent 0d0f551 commit 533ce7f

5 files changed

Lines changed: 146 additions & 5 deletions

File tree

‎cli/src/components/__tests__/sponsored-account-prompt.test.tsx‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import React from 'react'
1111
import { SponsoredAccountPrompt } from '../sponsored-account-prompt'
1212
import { initializeThemeStore } from '../../hooks/use-theme'
1313

14+
import { trackedSponsoredCredentials } from '../../utils/sponsored-account-step'
15+
1416
import type {
1517
SponsoredAccountAnswer,
1618
SponsoredAccountAsk,
@@ -30,6 +32,7 @@ async function mount(
3032
reply: (answer: SponsoredAccountAnswer) => string | null = () => null,
3133
) {
3234
const answers: SponsoredAccountAnswer[] = []
35+
const opened: string[] = []
3336
const interrupts: number[] = []
3437
const setup = await createTestRenderer({
3538
width: 70,
@@ -50,6 +53,7 @@ async function mount(
5053
return reply(answer)
5154
}}
5255
onInterrupt={() => interrupts.push(1)}
56+
openUrl={(url) => opened.push(url)}
5357
/>,
5458
)
5559
})
@@ -62,7 +66,9 @@ async function mount(
6266
return {
6367
answers,
6468
interrupts,
69+
opened,
6570
input: setup.mockInput,
71+
mouse: setup.mockMouse,
6672
frame: () => setup.captureCharFrame(),
6773
async act(fn: () => unknown) {
6874
await fn()
@@ -155,3 +161,40 @@ describe('the key form', () => {
155161
expect(prompt.frame()).toContain('That Acme API key looks too short.')
156162
})
157163
})
164+
165+
describe('a key page link with a {bfcid} slot (COD-965)', () => {
166+
const TOKEN = 'bfc_1.p.s'
167+
const declared = [
168+
{
169+
env: 'ACME_API_KEY',
170+
label: 'Acme API key',
171+
getUrl: 'https://acme.example/k?bfcid={bfcid}',
172+
},
173+
]
174+
const askFor = (advertiserLink: string | null): SponsoredAccountAsk => ({
175+
kind: 'credentials',
176+
offerName: 'Acme',
177+
credentials: trackedSponsoredCredentials(declared, { advertiserLink }),
178+
error: null,
179+
})
180+
181+
test('is shown and opened with the signed click id', async () => {
182+
const prompt = await mount(askFor(`https://acme.example/?bfcid=${TOKEN}`))
183+
const lines = prompt.frame().split('\n')
184+
const y = lines.findIndex((line) => line.includes('acme.example/k'))
185+
expect(y).toBeGreaterThanOrEqual(0)
186+
expect(lines[y]).toContain(`bfcid=${TOKEN}`)
187+
expect(prompt.frame()).not.toContain('{bfcid}')
188+
await prompt.act(() =>
189+
prompt.mouse.click(lines[y]!.indexOf('acme.example') + 2, y),
190+
)
191+
expect(prompt.opened).toEqual([`https://acme.example/k?bfcid=${TOKEN}`])
192+
})
193+
194+
test('without a signed click id, no link and no literal {bfcid}', async () => {
195+
const prompt = await mount(askFor(null))
196+
expect(prompt.frame()).toContain('Acme API key (ACME_API_KEY)')
197+
expect(prompt.frame()).not.toContain('{bfcid}')
198+
expect(prompt.frame()).not.toContain('acme.example/k')
199+
})
200+
})

‎cli/src/components/sponsored-account-prompt.tsx‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ export const SponsoredAccountPrompt: React.FC<{
5151
onAnswer: (answer: SponsoredAccountAnswer) => string | null
5252
/** Ctrl-C: stop the sponsored run, as it does everywhere else in chat. */
5353
onInterrupt?: () => void
54-
}> = ({ ask, onAnswer, onInterrupt }) => {
54+
/** Opens a link the user clicks; injected by tests. */
55+
openUrl?: (url: string) => void
56+
}> = ({ ask, onAnswer, onInterrupt, openUrl = safeOpen }) => {
5557
const theme = useTheme()
5658
const { keyHandler } = useAppContext()
5759
const [choice, setChoice] = useState(0)
@@ -198,7 +200,7 @@ export const SponsoredAccountPrompt: React.FC<{
198200
<TerminalLink
199201
text={ask.url}
200202
lineWrap
201-
onActivate={() => safeOpen(ask.url)}
203+
onActivate={() => openUrl(ask.url)}
202204
/>
203205
{[SPONSORED_SIGNUP_YES, SPONSORED_SIGNUP_NO].map((label, index) => (
204206
<text
@@ -232,7 +234,7 @@ export const SponsoredAccountPrompt: React.FC<{
232234
<TerminalLink
233235
text={credential.getUrl}
234236
lineWrap
235-
onActivate={() => safeOpen(credential.getUrl!)}
237+
onActivate={() => openUrl(credential.getUrl!)}
236238
/>
237239
) : null}
238240
<text style={{ fg: theme.foreground }}>

‎cli/src/utils/__tests__/sponsored-account-step.test.ts‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import {
1515
SponsoredAccountStep,
1616
SponsoredHeldCredentials,
17+
trackedSponsoredCredentials,
1718
} from '../sponsored-account-step'
1819

1920
import type {
@@ -192,6 +193,68 @@ describe('a tracked signup link', () => {
192193
})
193194
})
194195

196+
describe('a key page link with a {bfcid} slot (COD-965)', () => {
197+
const TOKEN = 'bfc_1.p.s'
198+
const declared = [
199+
{
200+
env: 'ACME_API_KEY',
201+
label: 'Acme API key',
202+
getUrl: 'https://acme.example/keys?bfcid={bfcid}',
203+
},
204+
{
205+
env: 'ACME_ORG',
206+
label: 'Acme org id',
207+
getUrl: 'https://acme.example/org',
208+
},
209+
]
210+
211+
test('takes the signed click id, and the form shows that link', async () => {
212+
const credentials = trackedSponsoredCredentials(declared, {
213+
advertiserLink: `https://acme.example/?bfcid=${TOKEN}`,
214+
})
215+
expect(credentials).toEqual([
216+
{ ...declared[0]!, getUrl: `https://acme.example/keys?bfcid=${TOKEN}` },
217+
declared[1]!,
218+
])
219+
const h = harness({ credentials })
220+
void h.step.request(new AbortController().signal)
221+
await Promise.resolve()
222+
const ask = h.step.asking
223+
expect(ask?.kind).toBe('credentials')
224+
if (ask?.kind !== 'credentials') return
225+
expect(ask.credentials[0]!.getUrl).toBe(
226+
`https://acme.example/keys?bfcid=${TOKEN}`,
227+
)
228+
})
229+
230+
test('without a signed click id the link is dropped, never shown with the slot', () => {
231+
for (const advertiserLink of [null, 'https://acme.example/', undefined]) {
232+
const credentials = trackedSponsoredCredentials(declared, {
233+
advertiserLink: advertiserLink as string | null,
234+
})
235+
expect(credentials).toEqual([
236+
{ env: 'ACME_API_KEY', label: 'Acme API key' },
237+
declared[1]!,
238+
])
239+
expect(JSON.stringify(credentials)).not.toContain('bfcid}')
240+
}
241+
})
242+
243+
test('an encoded slot is filled too, and an {email} slot is removed', () => {
244+
const [credential] = trackedSponsoredCredentials(
245+
[
246+
{
247+
env: 'ACME_API_KEY',
248+
label: 'Acme API key',
249+
getUrl: 'https://acme.example/keys?bfcid=%7Bbfcid%7D&email={email}',
250+
},
251+
],
252+
{ advertiserLink: `https://acme.example/?bfcid=${TOKEN}` },
253+
)
254+
expect(credential!.getUrl).toBe(`https://acme.example/keys?bfcid=${TOKEN}`)
255+
})
256+
})
257+
195258
test('no account step answers unavailable', async () => {
196259
const h = harness({ credentials: [] })
197260
expect(await h.step.request(new AbortController().signal)).toEqual({

‎cli/src/utils/sponsored-account-step.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
labSignInConfirmedNote,
3737
} from '@codebuff/common/ads/agentic-ad-lab-run'
3838
import { signupUrlWithEmail } from '@codebuff/common/ads/sponsored-instant-setup'
39+
import { resolveSponsoredBfcidTemplate } from '@codebuff/common/ads/sponsored-procedure-inputs'
3940
import {
4041
redactSponsoredCredentialDeep,
4142
redactSponsoredCredentialText,
@@ -47,6 +48,7 @@ import type {
4748
SponsoredRedactableSecret,
4849
SponsoredRunCredential,
4950
} from '@codebuff/common/ads/sponsored-run-credentials'
51+
import type { SponsoredProcedureRuntimeInputs } from '@codebuff/common/ads/sponsored-procedure-inputs'
5052
import type { SponsoredSignupStep } from '@codebuff/common/ads/sponsored-signup-step'
5153

5254
export const SPONSORED_SIGNUP_TOOL_NAME = 'request_sponsored_signup'
@@ -147,6 +149,29 @@ export class SponsoredHeldCredentials {
147149
}
148150
}
149151

152+
/**
153+
* MIRRORED from Desktop's `requiredCredentials` (COD-965): each declared key
154+
* page with its `{bfcid}` slot filled from this run's signed click id. The
155+
* key form may be the first advertiser visit, so a link that cannot be
156+
* attributed (no signed id) is dropped rather than shown with a literal
157+
* placeholder, and an `{email}` slot is removed: nobody agreed to share the
158+
* email on this form (COD-830).
159+
*/
160+
export function trackedSponsoredCredentials(
161+
credentials: readonly SponsoredRunCredential[],
162+
inputs: SponsoredProcedureRuntimeInputs,
163+
): SponsoredRunCredential[] {
164+
return credentials.map(({ getUrl, ...credential }) => {
165+
const bfcidResolved = getUrl
166+
? resolveSponsoredBfcidTemplate(getUrl, inputs)
167+
: null
168+
const resolved = bfcidResolved
169+
? signupUrlWithEmail(bfcidResolved, null)
170+
: null
171+
return { ...credential, ...(resolved ? { getUrl: resolved } : {}) }
172+
})
173+
}
174+
150175
/** User copy for a refused key. Never names a value. */
151176
export function sponsoredCredentialRefusalCopy(
152177
credential: SponsoredRunCredential,

‎cli/src/utils/sponsored-run.ts‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ import {
164164
SPONSORED_ACCOUNT_STEP_MARGIN_MS,
165165
SponsoredAccountStep,
166166
SponsoredHeldCredentials,
167+
trackedSponsoredCredentials,
167168
} from './sponsored-account-step'
168169
import { sponsoredSignupTool } from './sponsored-signup-tool'
169170
import {
@@ -1199,7 +1200,12 @@ export class SponsoredRun {
11991200
// and the agent calls `request_sponsored_signup` once the work that needs
12001201
// no account is done. A tracked signup link, declared keys, or a skill's
12011202
// user step each make one.
1202-
const declaredCredentials = declaredRunCredentials(active.procedure)
1203+
// A key page's `{bfcid}` slot takes this run's signed click id, as on
1204+
// Desktop and Cloud; a link that cannot be attributed is not shown.
1205+
const declaredCredentials = trackedSponsoredCredentials(
1206+
declaredRunCredentials(active.procedure),
1207+
active.runtimeInputs,
1208+
)
12031209
const signup = sponsoredSignupStep(
12041210
active.procedure,
12051211
active.runtimeInputs,
@@ -1254,7 +1260,9 @@ export class SponsoredRun {
12541260
const installTool = context.installTool
12551261
? sponsoredInstallTool({
12561262
...sponsoredInstallProbe(
1257-
createSponsoredRootedFileSystem({ workspaceRoot: this.projectRoot }),
1263+
createSponsoredRootedFileSystem({
1264+
workspaceRoot: this.projectRoot,
1265+
}),
12581266
this.projectRoot,
12591267
),
12601268
resolve: (request) => this.resolvePackage(active, request),

0 commit comments

Comments
 (0)