Skip to content

Commit d3e6fc9

Browse files
committed
fix: soften native websearch authenticity guidance
1 parent 97e4f60 commit d3e6fc9

10 files changed

Lines changed: 252 additions & 4 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
{
1111
"name": "hello2cc",
1212
"description": "Silent, native-first plugin for third-party models on Claude Code, with Claude-style tool, agent, task, team, and MCP routing.",
13-
"version": "0.2.7",
13+
"version": "0.2.8",
1414
"source": "./",
1515
"author": {
1616
"name": "hello2cc"

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hello2cc",
3-
"version": "0.2.7",
3+
"version": "0.2.8",
44
"description": "Silent, native-first Claude Code plugin that steers third-party models toward Claude-style native tools, agents, tasks, teams, MCP workflows, and concise output.",
55
"author": {
66
"name": "hello2cc"

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 0.2.8 - 2026-04-02
4+
5+
- Added softer native `WebSearch` guidance for real-time / latest-information questions so hello2cc encourages the host's built-in search path without pretending the plugin itself can create missing network capability
6+
- Added authenticity-focused proxy guidance: custom `ANTHROPIC_BASE_URL` sessions are still encouraged to try native `WebSearch`, but the model is reminded to treat `Did 0 searches`, missing links, or missing search hits as “not actually searched” instead of presenting memory as a real web result
7+
- Added regression coverage to keep this boundary stable: hello2cc should not hard-disable `WebSearch` just because a custom proxy is present, but it should keep result-truthfulness guidance intact
8+
39
## 0.2.7 - 2026-04-02
410

511
- Added a true `default_agent_model` option so users can define one native-safe default agent model preference without having to overuse per-agent overrides

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
**当你已经把外部模型接进 Claude Code 后,让它更像原生 Claude Code 一样使用工具、Agent、计划、任务、MCP 和团队能力。**
88

9-
当前版本:`0.2.7`
9+
当前版本:`0.2.8`
1010

1111
---
1212

@@ -66,6 +66,12 @@
6666
- 强迫你进入一套插件专属工作流
6767
- 覆盖高优先级的 `CLAUDE.md` / `AGENTS.md` / 项目规则 / 用户明确要求
6868

69+
对于 `WebSearch` 也是同样的边界:
70+
71+
- 不会因为你用了自定义代理 / gateway 就直接替你禁用 `WebSearch`
72+
- 不会替宿主凭空创造本来不存在的联网能力
73+
- 只会尽量提醒模型:**只有拿到真实搜索条目 / 来源时,才把结果当成已经联网搜索**
74+
6975
它追求的是:
7076

7177
**静默增强原生感,而不是接管 Claude Code。**
@@ -329,6 +335,18 @@ claude plugin install hello2cc@hello2cc-local
329335

330336
只要更高优先级规则已经定义了格式、流程、命令路由或输出习惯,hello2cc 会尽量让位。
331337

338+
### 如果我后面用 helloswitch 增强代理,让第三方模型真正支持 Claude Code 的 WebSearch / tools,会不会和 hello2cc 冲突?
339+
340+
不会。
341+
342+
`hello2cc` 不会因为你使用了代理就硬禁用 `WebSearch` 或普通工具调用。
343+
它做的是更轻的“真实性保护”:
344+
345+
- 如果真实拿到了搜索条目 / 来源,就正常按联网结果组织回答
346+
- 如果界面出现 `Did 0 searches`、没有来源、没有真实搜索结果,就不要把记忆包装成“已经搜过”
347+
348+
所以如果你的代理后续真的被增强到能正确支持 Claude Code 的工具协议,`hello2cc` 不会挡住这条路径。
349+
332350
---
333351

334352
## 本地验证

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hello2cc",
3-
"version": "0.2.7",
3+
"version": "0.2.8",
44
"type": "module",
55
"description": "Silent, native-first Claude Code plugin that steers third-party models toward Claude-style tools, agents, tasks, teams, MCP workflows, and concise native output.",
66
"license": "Apache-2.0",

scripts/lib/api-topology.mjs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { envValue } from './config.mjs';
2+
3+
function envTruthy(name) {
4+
return ['1', 'true', 'yes', 'on'].includes(envValue(name).toLowerCase());
5+
}
6+
7+
export function getApiProvider() {
8+
if (envTruthy('CLAUDE_CODE_USE_BEDROCK')) return 'bedrock';
9+
if (envTruthy('CLAUDE_CODE_USE_VERTEX')) return 'vertex';
10+
if (envTruthy('CLAUDE_CODE_USE_FOUNDRY')) return 'foundry';
11+
return 'firstParty';
12+
}
13+
14+
export function isFirstPartyAnthropicBaseUrl() {
15+
const baseUrl = envValue('ANTHROPIC_BASE_URL');
16+
if (!baseUrl) return true;
17+
18+
try {
19+
const host = new URL(baseUrl).host;
20+
const allowedHosts = ['api.anthropic.com'];
21+
22+
if (envValue('USER_TYPE') === 'ant') {
23+
allowedHosts.push('api-staging.anthropic.com');
24+
}
25+
26+
return allowedHosts.includes(host);
27+
} catch {
28+
return false;
29+
}
30+
}
31+
32+
export function usesCustomAnthropicProxy() {
33+
return getApiProvider() === 'firstParty' && !isFirstPartyAnthropicBaseUrl();
34+
}
35+
36+
export function hasObservedTools(sessionContext = {}) {
37+
return Array.isArray(sessionContext?.toolNames) && sessionContext.toolNames.length > 0;
38+
}
39+
40+
export function resolveWebSearchGuidanceMode(sessionContext = {}) {
41+
const observedTools = hasObservedTools(sessionContext);
42+
const webSearchAvailable = Boolean(sessionContext?.webSearchAvailable);
43+
const customAnthropicProxy = usesCustomAnthropicProxy();
44+
45+
if (webSearchAvailable && customAnthropicProxy) {
46+
return 'proxy-conditional';
47+
}
48+
49+
if (webSearchAvailable) {
50+
return 'available';
51+
}
52+
53+
if (observedTools) {
54+
return 'not-exposed';
55+
}
56+
57+
if (customAnthropicProxy) {
58+
return 'proxy-unknown';
59+
}
60+
61+
return 'generic';
62+
}

scripts/lib/prompt-signals.mjs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,29 @@ const RESEARCH_PATTERNS = [
7272
//,
7373
];
7474

75+
const CURRENT_INFO_PATTERNS = [
76+
/\b(latest|recent|recently|current|today|tonight|this week|this month|breaking)\b/,
77+
/\b(news|headline|headlines|weather|forecast|price|pricing|stock|stocks|score|scores|release notes|changelog)\b/,
78+
//,
79+
//,
80+
//,
81+
//,
82+
//,
83+
//,
84+
//,
85+
//,
86+
//,
87+
//,
88+
//,
89+
//,
90+
//,
91+
//,
92+
//,
93+
//,
94+
//,
95+
//,
96+
];
97+
7598
const SWARM_PATTERNS = [
7699
/parallel/,
77100
/in parallel/,
@@ -352,6 +375,7 @@ export function isSubagentPrompt(prompt) {
352375
export function classifyPrompt(prompt) {
353376
const text = normalizePrompt(prompt);
354377
const research = hasAny(text, RESEARCH_PATTERNS);
378+
const currentInfo = hasAny(text, CURRENT_INFO_PATTERNS);
355379
const explicitHostFeature = hasAny(text, HOST_FEATURE_PATTERNS);
356380
const claudeGuide = hasQuestionIntent(text) && hasAny(text, GUIDE_PATTERNS);
357381
const implement = hasAny(text, IMPLEMENT_PATTERNS);
@@ -394,6 +418,7 @@ export function classifyPrompt(prompt) {
394418
return {
395419
diagram: hasAny(text, DIAGRAM_PATTERNS),
396420
research,
421+
currentInfo,
397422
swarm,
398423
teamWorkflow,
399424
verify,

scripts/lib/route-guidance.mjs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { configuredModels } from './config.mjs';
2+
import { resolveWebSearchGuidanceMode } from './api-topology.mjs';
23

34
function buildTaskPlanningStep() {
45
return '这是非 trivial 实现:先 `EnterPlanMode()`;只有真的需要任务盘时再用 `TaskCreate` / `TaskList` / `TaskUpdate`。';
@@ -55,6 +56,28 @@ function buildResearchStep(signals) {
5556
return '这是研究 / 对比 / 文档任务:先做定向搜索与证据收集,再在需要扩大搜索面时转原生 `Explore` 或 `Plan`。';
5657
}
5758

59+
function buildCurrentInfoStep(signals, sessionContext = {}) {
60+
if (!signals.currentInfo) {
61+
return '';
62+
}
63+
64+
const mode = resolveWebSearchGuidanceMode(sessionContext);
65+
66+
if (mode === 'available') {
67+
return '这是最新/实时信息任务:优先原生 `WebSearch` 获取当下来源,再组织答案;不要只凭记忆回答这类问题。';
68+
}
69+
70+
if (mode === 'proxy-conditional') {
71+
return '这是最新/实时信息任务:优先尝试原生 `WebSearch`;只有当它真实返回搜索条目或来源链接时,才按联网结果回答。若界面出现 `Did 0 searches`、无来源或无搜索结果,必须明确说明未完成真实搜索。';
72+
}
73+
74+
if (mode === 'not-exposed') {
75+
return '这是最新/实时信息任务:当前未显式看到原生 `WebSearch`;不要把记忆包装成最新联网信息,必要时先说明当前边界。';
76+
}
77+
78+
return '这是最新/实时信息任务:若宿主暴露原生 `WebSearch`,优先用它获取实时来源;如果没有真实搜索结果或来源,就明确说明边界,不要假装已经联网。';
79+
}
80+
5881
export function buildRouteStepsFromSignals(signals, sessionContext = {}) {
5982
const config = configuredModels(sessionContext);
6083
const steps = [];
@@ -74,6 +97,11 @@ export function buildRouteStepsFromSignals(signals, sessionContext = {}) {
7497
steps.push(researchStep);
7598
}
7699

100+
const currentInfoStep = buildCurrentInfoStep(signals, sessionContext);
101+
if (currentInfoStep) {
102+
steps.push(currentInfoStep);
103+
}
104+
77105
if (signals.boundedImplementation) {
78106
steps.push('这是边界清晰的实现 / 修复 / 验证子任务:优先使用原生 `Agent` 的 `General-Purpose` 承接单一切片,而不是把探索、规划和实现都混在主线程。');
79107
}

scripts/lib/session-guidance.mjs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { FORCED_OUTPUT_STYLE_NAME, configuredModels } from './config.mjs';
2+
import { resolveWebSearchGuidanceMode } from './api-topology.mjs';
23

34
function formatNames(values) {
45
return values.map((value) => `\`${value}\``).join(', ');
@@ -86,6 +87,52 @@ function buildToolSearchLines() {
8687
];
8788
}
8889

90+
function buildWebSearchLines(sessionContext = {}) {
91+
const mode = resolveWebSearchGuidanceMode(sessionContext);
92+
93+
if (mode === 'available') {
94+
return [
95+
'## 实时信息与 WebSearch',
96+
'- 当前会话已暴露原生 `WebSearch`;遇到最新/今天/新闻/价格/天气/发布动态等问题时,优先先拿到实时来源,再组织回答。',
97+
'- 如果 `WebSearch` 没有给出真实来源或搜索条目,就不要把记忆包装成联网结果。',
98+
'- `WebSearch` 只负责实时来源;代码执行、文件读写、MCP 与 agent 协作仍优先走各自原生工具。',
99+
];
100+
}
101+
102+
if (mode === 'proxy-conditional') {
103+
return [
104+
'## 实时信息与 WebSearch',
105+
'- 当前会话已暴露原生 `WebSearch`,但链路看起来是自定义 `ANTHROPIC_BASE_URL` 代理;有些代理会转发真实搜索,有些不会。',
106+
'- 仍然优先尝试原生 `WebSearch`;hello2cc 不会因为使用自定义代理就直接阻断这条路径。',
107+
'- 只有当 `WebSearch` 真正返回搜索条目或来源链接时,才把它当成联网成功;如果界面出现 `Did 0 searches`、无来源或无搜索结果,必须明确说明未完成真实搜索。',
108+
'- `WebSearch` 只负责实时来源;代码执行、文件读写、MCP 与 agent 协作仍优先走各自原生工具。',
109+
];
110+
}
111+
112+
if (mode === 'not-exposed') {
113+
return [
114+
'## 实时信息与 WebSearch',
115+
'- 当前会话未显式暴露原生 `WebSearch`;不要声称自己已经联网搜索了最新信息。',
116+
'- 如果用户要求最新/实时信息,优先查找宿主真实暴露的联网工具;没有就明确说明边界。',
117+
];
118+
}
119+
120+
if (mode === 'proxy-unknown') {
121+
return [
122+
'## 实时信息与 WebSearch',
123+
'- 当前链路看起来是自定义 `ANTHROPIC_BASE_URL` 代理;若宿主暴露原生 `WebSearch`,优先用它获取实时来源。',
124+
'- hello2cc 不会因为你使用代理就自动禁用 `WebSearch`;它只要求在没有真实搜索结果时诚实表达边界。',
125+
'- 只有拿到真实搜索条目或来源链接时,才按联网结果回答;否则必须明确说明边界,不要把记忆当成实时搜索。',
126+
];
127+
}
128+
129+
return [
130+
'## 实时信息与 WebSearch',
131+
'- 遇到最新/实时信息任务时,若宿主暴露原生 `WebSearch`,优先用它获取来源;不要只凭记忆回答这类问题。',
132+
'- 如果没有真实搜索条目或来源,就明确说明当前边界,不要假装已经联网。',
133+
];
134+
}
135+
89136
export function buildSessionStartContext(sessionContext = {}) {
90137
return [
91138
'# hello2cc',
@@ -104,6 +151,8 @@ export function buildSessionStartContext(sessionContext = {}) {
104151
'',
105152
...buildToolSearchLines(),
106153
'',
154+
...buildWebSearchLines(sessionContext),
155+
'',
107156
'## 输出风格',
108157
`- 当前插件输出风格:\`${FORCED_OUTPUT_STYLE_NAME}\`。`,
109158
'- 如果更高优先级规则没有指定格式,保持 Claude Code 原生、简洁、结果导向的表达。',

tests/orchestrator.test.mjs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,37 @@ test('session-start surfaces advanced native capabilities when the host exposes
152152
assert.match(context, /PowerShell/);
153153
});
154154

155+
test('session-start explains how to use native WebSearch for real-time questions', () => {
156+
const env = isolatedEnv();
157+
const output = run('session-start', {
158+
session_id: 'session-websearch',
159+
model: 'opus',
160+
tools: ['WebSearch'],
161+
}, env);
162+
const context = output.hookSpecificOutput.additionalContext;
163+
164+
assert.match(context, / WebSearch/);
165+
assert.match(context, / `WebSearch`/);
166+
assert.match(context, //);
167+
});
168+
169+
test('session-start keeps WebSearch guidance honest on proxy-like sessions', () => {
170+
const env = isolatedEnv({
171+
ANTHROPIC_BASE_URL: 'https://proxy.example.com/v1',
172+
});
173+
const output = run('session-start', {
174+
session_id: 'session-websearch-proxy',
175+
model: 'opus',
176+
tools: ['WebSearch'],
177+
}, env);
178+
const context = output.hookSpecificOutput.additionalContext;
179+
180+
assert.match(context, / `ANTHROPIC_BASE_URL` /);
181+
assert.match(context, /使/);
182+
assert.match(context, /Did 0 searches/);
183+
assert.match(context, //);
184+
});
185+
155186
test('route promotes native guide flow without skill references', () => {
156187
const env = isolatedEnv();
157188
run('session-start', {
@@ -257,6 +288,35 @@ test('route prefers MCP resource tools', () => {
257288
assert.match(context, /ReadMcpResource/);
258289
});
259290

291+
test('route prefers native WebSearch for real-time questions when the host exposes it', () => {
292+
const env = isolatedEnv();
293+
const output = run('route', {
294+
session_id: 'route-websearch',
295+
tools: ['WebSearch'],
296+
prompt: '帮我查一下 OpenAI Codex 最近的新闻',
297+
}, env);
298+
const context = output.hookSpecificOutput.additionalContext;
299+
300+
assert.match(context, /\//);
301+
assert.match(context, / `WebSearch`/);
302+
});
303+
304+
test('route keeps proxy WebSearch guidance focused on authenticity rather than blocking', () => {
305+
const env = isolatedEnv({
306+
ANTHROPIC_BASE_URL: 'https://proxy.example.com/v1',
307+
});
308+
const output = run('route', {
309+
session_id: 'route-websearch-proxy',
310+
tools: ['WebSearch'],
311+
prompt: '帮我查下今天 AI 新闻',
312+
}, env);
313+
const context = output.hookSpecificOutput.additionalContext;
314+
315+
assert.match(context, / `WebSearch`/);
316+
assert.match(context, /Did 0 searches/);
317+
assert.match(context, //);
318+
});
319+
260320
test('route keeps ToolSearch-first intent optimistic on proxy sessions without host proof either way', () => {
261321
const env = isolatedEnv({
262322
ANTHROPIC_BASE_URL: 'https://proxy.example.com/v1',

0 commit comments

Comments
 (0)