Skip to content

Commit 7df761c

Browse files
Merge latest main into recreate/1101-freebuff-commit-attribution
2 parents 4254e60 + 253ee6c commit 7df761c

213 files changed

Lines changed: 29436 additions & 2690 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ user-emails-*.csv
99
.env
1010
.env.*
1111
!.env.example
12+
!.env.local.example
1213

1314
# ctags
1415
tags
@@ -55,6 +56,8 @@ cli/release-staging/launcher.js
5556
cli/release-staging/http.js
5657
freebuff/cli/release/launcher.js
5758
freebuff/cli/release/http.js
59+
xcuserdata/
60+
.claude/settings.local.json
5861

5962
# QA harness fixture: a snapshot of REAL feedback rows, including the Discord
6063
# and GitHub handles of real people and what they complained about. Useful
@@ -73,6 +76,11 @@ scratch-*
7376
# Ad-hoc investigation scripts — scratch, never committed
7477
scripts/_tmp-*
7578

79+
# The intent-batch orchestrator's resume state. It holds a live approval
80+
# capability (shown once, at the mint) and the cohort's own references, so it
81+
# is operator-local and never committed.
82+
scratch/
83+
7684
# Local operator launch manifests contain real advertiser identifiers. The
7785
# checked-in example is intentionally non-routable.
7886
ads-pilot-launch.local.json

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,20 @@ Freebuff includes a curated model catalog. The regular picker currently offers:
3434

3535
| Model | Access | Best for |
3636
| --------------------------- | ----------------------- | ----------------------------------------------------------------- |
37-
| **GLM 5.3 Flash** | Full access | The default in full mode; deepest reasoning, unmetered |
37+
| **GLM 5.3 Flash** | Full and limited access | The default everywhere; deepest reasoning, unmetered |
38+
| **DeepSeek V4 Flash 07/31** | Full and limited access | Fast coding and tool use, unmetered |
3839
| **GPT-5.6 Luna** | Full access | Strong all-around with native images |
39-
| **DeepSeek V4 Flash 07/31** | Full access | Fast coding and tool use; pauses during peak hours |
40-
| **MiMo 2.5** | Full and limited access | The limited-mode default; balanced performance with image support |
41-
| **Solar Pro 4** | Full access | Limited-time trial; 524K context, text only |
40+
| **MiMo 2.5** | Full and limited access | Balanced performance with image support |
41+
| **Solar Pro 4** | Full and limited access | Limited-time trial; 524K context, text only; unmetered at full access |
42+
| **Muse Spark 1.3** | Full access | Meta's agentic coding model; 1M context. Rate limited and shared by every user, so it queues when busy and answers on DeepSeek V4 Flash rather than making you wait |
4243

43-
Most models draw on your normal daily sessions rather than a separate limit. GLM 5.3 Flash and MiMo 2.5 are unmetered and cost no session at all. Models may still serve from a quantized (Q8_0) build.
44+
Most models draw on your normal daily sessions rather than a separate limit. GLM 5.3 Flash, DeepSeek V4 Flash 07/31, MiMo 2.5 and Solar Pro 4 are unmetered at full access and cost no session at all. Models may still serve from a quantized (Q8_0) build.
4445

4546
DeepSeek V4 Pro was retired from the catalog; GLM 5.3 Flash replaces it as the deep-reasoning pick.
4647

4748
Beyond the regular picker:
4849

49-
- **GLM 5.2** is available through earned sessions rather than as an always-unlocked model.
50+
- **Referrals and bounties** earn extra sessions on top of the free daily allowance.
5051
- **Gemini 3.1 Flash Lite** powers specialist tasks such as file finding and research rather than appearing in the main picker.
5152

5253
Availability and limits depend on your access tier, product, and current capacity. Freebuff Desktop can also run locally installed Claude Code and Codex agents using your existing provider account; those connected models are separate from Freebuff's included catalog.
@@ -63,15 +64,15 @@ Freebuff uses specialized agents instead of sending every task through one model
6364

6465
## Free access
6566

66-
Freebuff is available in every country. Supported regions receive full access; other regions and VPN users receive limited access, currently MiMo 2.5 with three one-hour sessions per day, earnable up to seven.
67+
Freebuff is available in every country. Supported regions receive full access; other regions and VPN users receive limited access to DeepSeek V4 Flash 07/31, GLM 5.3 Flash, MiMo 2.5, and Solar Pro 4. Accounts on Freebucks use the displayed model price and balance. On the legacy session system, limited access includes six one-hour sessions per day, earnable up to seven; GLM uses earned reward sessions instead.
6768

6869
Text ads support the included models. Freebuff shows the applicable session limits and any model-specific data-use notice before you start.
6970

7071
<!-- BEGIN GENERATED FREEBUFF DATA USE -->
7172

7273
**Is my data used to train AI?** Only when a model or feature says data may be used for AI training. Freebuff or the provider may then keep submissions to develop, train, test, evaluate, fine-tune, and improve AI models or products.
7374

74-
**How is my data used and stored?** We use prompts, messages, code, files, and repository data to provide the service. We may analyze prompts and messages—including pasted content—to personalize ads, using Freebuff systems and service providers acting on our behalf. Separate uploads and connected repositories are not provided to advertising providers. Where required by law, we provide advertising choices and honor recognized opt-out signals; elsewhere, this processing may be required to use the free service. See the Privacy Policy for retention and details.
75+
**How is my data used and stored?** We use prompts, messages, agent traces, code, files, and repository data to provide Freebuff. We may analyze prompts and messages to personalize ads. We do not give separately uploaded files or connected repositories to advertising providers. Restricted partners may evaluate connected Cloud repositories or code used with models labeled “May use data for AI training,” but cannot otherwise use, broadly share, or train on it. See the Privacy Policy for retention, eligibility, and advertising choices.
7576

7677
See the [Privacy Policy](https://freebuff.com/privacy-policy) for complete details.
7778

README.zh-CN.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,20 @@ Freebuff 提供经过筛选的模型目录。常规模型选择器目前包括
3434

3535
| 模型 | 访问范围 | 适用场景 |
3636
| --------------------------- | -------------- | ------------------------------------------------ |
37-
| **GLM 5.3 Flash** | 完整访问 | 完整模式下的默认模型;推理最深入,且不消耗会话 |
37+
| **GLM 5.3 Flash** | 完整和受限访问 | 所有模式下的默认模型;推理最深入,不消耗会话 |
38+
| **DeepSeek V4 Flash 07/31** | 完整和受限访问 | 快速编程和工具调用,不消耗会话 |
3839
| **GPT-5.6 Luna** | 完整访问 | 综合能力强,原生支持图像 |
39-
| **DeepSeek V4 Flash 07/31** | 完整访问 | 快速编程和工具调用;高峰时段暂停 |
40-
| **MiMo 2.5** | 完整和受限访问 | 受限模式的默认模型;均衡性能并支持图像 |
41-
| **Solar Pro 4** | 完整访问 | 限时试用;52.4 万上下文,仅支持文本 |
40+
| **MiMo 2.5** | 完整和受限访问 | 均衡性能并支持图像 |
41+
| **Solar Pro 4** | 完整和受限访问 | 限时试用;52.4 万上下文,仅支持文本;完整访问下不消耗会话 |
42+
| **Muse Spark 1.3** | 完整访问 | Meta 的智能体编码模型;100 万上下文。有速率限制且所有用户共享,繁忙时排队,随后改由 DeepSeek V4 Flash 作答,不会让你一直等 |
4243

43-
大多数模型使用你的常规每日会话,而不再各自设限。GLM 5.3 FlashMiMo 2.5 保持无限使用,完全不消耗会话。模型仍可能由量化(Q8_0)版本提供服务。
44+
大多数模型使用你的常规每日会话,而不再各自设限。GLM 5.3 Flash、DeepSeek V4 Flash 07/31、MiMo 2.5 和 Solar Pro 4 在完整访问下保持无限使用,完全不消耗会话。模型仍可能由量化(Q8_0)版本提供服务。
4445

4546
DeepSeek V4 Pro 已从模型目录中下线,由 GLM 5.3 Flash 接替其深度推理的位置。
4647

4748
常规模型选择器之外:
4849

49-
- **GLM 5.2** 通过获得的会话使用,并非始终解锁
50+
- **推荐好友与悬赏任务**可在每日免费额度之外赚取额外会话
5051
- **Gemini 3.1 Flash Lite** 用于查找文件和研究等专业任务,不会出现在主模型选择器中。
5152

5253
可用模型和限制取决于你的访问级别、所用产品和当前容量。Freebuff Desktop 还可以通过你现有的提供商账户运行本地安装的 Claude Code 和 Codex 智能体;这些连接的模型不属于 Freebuff 的内置模型目录。
@@ -63,7 +64,7 @@ Freebuff 使用专业化智能体,而不是把所有任务都交给同一个
6364

6465
## 免费访问
6566

66-
Freebuff 在所有国家和地区均可使用。受支持的地区提供完整访问;其他地区以及使用 VPN 的用户获得受限访问,目前包括 MiMo 2.5,每天可使用六个一小时会话
67+
Freebuff 在所有国家和地区均可使用。受支持的地区提供完整访问;其他地区以及使用 VPN 的用户获得受限访问,包括 DeepSeek V4 Flash 07/31、GLM 5.3 Flash、MiMo 2.5 和 Solar Pro 4。使用 Freebucks 的账户按显示的模型价格和余额计费。在旧版会话系统中,受限访问每天提供六个一小时会话,最多可赚取七个;GLM 则使用赚取的奖励会话
6768

6869
内置模型由文字广告支持。开始前,Freebuff 会显示适用的会话限制以及模型特定的数据使用提示。
6970

agents/__tests__/base-chat.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
compactionPolicyForModel,
3+
DEEPSEEK_FLASH_COMPACTION_POLICY,
4+
} from '@codebuff/common/constants/compaction-policy'
15
import {
26
FREEBUFF_DEFAULT_CONTEXT_WINDOW,
37
FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID,
@@ -52,6 +56,12 @@ function firstPrunerSpawn(model?: string) {
5256
agentState: createMockAgentState(100),
5357
logger: mockLogger,
5458
model,
59+
// What the runtime supplies (run-programmatic-step.ts): the model's pruner
60+
// thresholds. base-chat sizes its own budget and reads only the policy.
61+
contextPruning: {
62+
maxContextLength: 400_000,
63+
...compactionPolicyForModel(model),
64+
},
5565
})
5666
return generator.next().value as {
5767
toolName: string
@@ -301,4 +311,29 @@ describe('base-chat pruning triggers', () => {
301311
60 * 60 * 1000,
302312
)
303313
})
314+
315+
test('non-Flash models keep the 24-hour gap and no token floor', () => {
316+
for (const model of [
317+
'minimax/minimax-m3',
318+
'openai/gpt-5.6-luna',
319+
undefined,
320+
]) {
321+
const params = firstPrunerSpawn(model).input.params as any
322+
expect(params.cacheExpiryMs).toBe(24 * 60 * 60 * 1000)
323+
expect(params).not.toHaveProperty('cacheExpiryMinTokens')
324+
}
325+
})
326+
327+
test('DeepSeek Flash follows the shared compaction policy: 15 minutes, 40k floor', () => {
328+
// Flash's Luminal lane forgets the prefix in ~15 minutes, so the 24-hour
329+
// gap only postponed a compaction the cold prefill was going to pay for.
330+
const params = firstPrunerSpawn(FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID).input
331+
.params as any
332+
expect(params).toEqual({
333+
maxContextLength: params.maxContextLength,
334+
...DEEPSEEK_FLASH_COMPACTION_POLICY,
335+
})
336+
expect(params.cacheExpiryMs).toBe(15 * 60 * 1000)
337+
expect(params.cacheExpiryMinTokens).toBe(40_000)
338+
})
304339
})

agents/__tests__/base2.test.ts

Lines changed: 125 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import {
77
FREEBUFF_MINIMAX_M3_MODEL_ID,
88
FREEBUFF_MIMO_V25_MODEL_ID,
99
} from '@codebuff/common/constants/freebuff-models'
10+
import {
11+
compactionPolicyForModel,
12+
DEEPSEEK_FLASH_COMPACTION_POLICY,
13+
DEFAULT_COMPACTION_POLICY,
14+
} from '@codebuff/common/constants/compaction-policy'
15+
import { contextPrunerBudgetForModel } from '@codebuff/common/constants/model-config'
1016

1117
import { createBase2 } from '../base2/base2'
1218
import { createBaseDeep } from '../base2/base-deep'
@@ -148,10 +154,82 @@ describe('production agent step prompts', () => {
148154
}
149155
})
150156

151-
test('plan-only keeps its no-edit constraint in the instructions', () => {
152-
const agent = createBase2('default', { planOnly: true })
157+
// A user picked plan mode and got the whole feature built and committed. The
158+
// instruction paragraph saying not to was there the entire time; what it was
159+
// losing to was the rest of the same prompt (a worked example ending in "you
160+
// implement the changes using the editor agent") and a toolset that still
161+
// held every capability a build turn has. So these assert the capability and
162+
// the demonstration, not the paragraph.
163+
describe('plan-only', () => {
164+
const plan = createBase2('default', { planOnly: true })
165+
166+
test('holds no tool that writes a file', () => {
167+
for (const tool of [
168+
'str_replace',
169+
'write_file',
170+
'propose_str_replace',
171+
'propose_write_file',
172+
]) {
173+
expect(plan.toolNames).not.toContain(tool)
174+
}
175+
})
176+
177+
test('cannot spawn an agent that edits or runs commands', () => {
178+
// basher is the shell: `git commit` reached the repository through it
179+
for (const agent of [
180+
'editor',
181+
'editor-multi-prompt',
182+
'basher',
183+
'tmux-cli',
184+
]) {
185+
expect(plan.spawnableAgents).not.toContain(agent)
186+
}
187+
})
188+
189+
test('still holds everything it needs to explore', () => {
190+
for (const tool of [
191+
'read_files',
192+
'read_subtree',
193+
'list_directory',
194+
'glob',
195+
] as const) {
196+
expect(plan.toolNames).toContain(tool)
197+
}
198+
for (const agent of ['file-picker', 'code-searcher', 'researcher-web']) {
199+
expect(plan.spawnableAgents).toContain(agent)
200+
}
201+
})
202+
203+
test('never demonstrates building in its worked example', () => {
204+
const systemPrompt = plan.systemPrompt!
205+
206+
for (const phrase of [
207+
'You implement the changes',
208+
'Spawn the editor agent to implement',
209+
'Spawn bashers sequentially',
210+
'All tests & typechecks pass',
211+
]) {
212+
expect(systemPrompt).not.toContain(phrase)
213+
}
214+
expect(systemPrompt).toContain('You do NOT implement anything')
215+
})
216+
217+
test("refuses to read the user's wording as permission to build", () => {
218+
const instructions = plan.instructionsPrompt!
219+
220+
expect(instructions).toContain('is not permission to leave plan mode')
221+
// the old prompt ended with "answer the user's questions or requests
222+
// freely", which reads as exactly that permission
223+
expect(instructions).not.toContain('requests freely')
224+
})
153225

154-
expect(agent.instructionsPrompt).toContain('Do not make file changes')
226+
test('leaves the build modes alone', () => {
227+
const build = createBase2('default')
228+
229+
expect(build.toolNames).toContain('write_file')
230+
expect(build.spawnableAgents).toContain('editor')
231+
expect(build.spawnableAgents).toContain('basher')
232+
})
155233
})
156234
})
157235

@@ -329,13 +407,26 @@ describe('base2 optional tools', () => {
329407
})
330408

331409
describe('base2 context pruning', () => {
410+
/** What the runtime hands a serialized generator for `model`
411+
* (run-programmatic-step.ts): the model plus its pruner thresholds. */
412+
const stepContextFor = (model: string) => ({
413+
model,
414+
contextPruning: {
415+
maxContextLength: contextPrunerBudgetForModel(model as any),
416+
...compactionPolicyForModel(model),
417+
},
418+
})
419+
332420
const getContextPrunerParams = (
333421
mode: Parameters<typeof createBase2>[0],
334422
options?: Parameters<typeof createBase2>[1],
335423
params?: Record<string, unknown>,
336424
) => {
337425
const base2 = createBase2(mode, options)
338-
const generator = base2.handleSteps!({ params } as any)
426+
const generator = base2.handleSteps!({
427+
params,
428+
...stepContextFor(base2.model),
429+
} as any)
339430
const step = generator.next().value as any
340431
return step.input.params
341432
}
@@ -350,7 +441,10 @@ describe('base2 context pruning', () => {
350441
const isolatedHandleSteps = new Function(
351442
`return (${handleStepsString})`,
352443
)() as NonNullable<typeof base2.handleSteps>
353-
const generator = isolatedHandleSteps({ params: undefined } as any)
444+
const generator = isolatedHandleSteps({
445+
params: undefined,
446+
...stepContextFor(base2.model),
447+
} as any)
354448
const step = generator.next().value as any
355449
return step.input.params
356450
}
@@ -384,14 +478,39 @@ describe('base2 context pruning', () => {
384478
test('free non-MiniMax/Kimi models default context pruning to 400k tokens', () => {
385479
expect(
386480
getContextPrunerParams('free', {
387-
model: FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID,
481+
model: FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
388482
}),
389483
).toEqual({
390484
maxContextLength: 400_000,
391485
cacheExpiryMs: 30 * 60 * 1000,
392486
})
393487
})
394488

489+
test('DeepSeek Flash prunes under the Flash policy; other models keep 30 minutes, no floor', () => {
490+
// Flash's numbers arrive through the runtime's `contextPruning`; the model
491+
// id is the one literal in the generator, so pin it to the constant.
492+
expect(FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID).toBe(
493+
'deepseek/deepseek-v4-flash',
494+
)
495+
const flashParams = {
496+
maxContextLength: 400_000,
497+
...DEEPSEEK_FLASH_COMPACTION_POLICY,
498+
}
499+
const flash = { model: FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID }
500+
expect(getContextPrunerParams('free', flash)).toEqual(flashParams)
501+
expect(getSerializedContextPrunerParams('default', flash)).toEqual(
502+
flashParams,
503+
)
504+
// base2 is the kill-switch fallback, so it deliberately does NOT take the
505+
// default policy's hour/140k for the rest.
506+
expect(DEFAULT_COMPACTION_POLICY.cacheExpiryMs).toBe(60 * 60 * 1000)
507+
expect(
508+
getContextPrunerParams('free', {
509+
model: FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
510+
}),
511+
).toEqual({ maxContextLength: 400_000, cacheExpiryMs: 30 * 60 * 1000 })
512+
})
513+
395514
test('free mode preserves explicit context pruning params', () => {
396515
const base2 = createBase2('free')
397516
const generator = base2.handleSteps!({

0 commit comments

Comments
 (0)