Skip to content
Open
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
11 changes: 7 additions & 4 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,10 +565,13 @@ runs. When both files exist, `<dataDir>/AGENTS.md` is appended first and
`<workspace>/.bb/AGENTS.md` second. An empty or whitespace-only file is treated
as absent.

No agent loads `.bb/AGENTS.md` natively, and provider-native instruction files
(`CLAUDE.md` for Claude Code, a repo-root `AGENTS.md` for Codex) remain
provider-specific. bb reads the files above itself and injects them, so use them
for guidance you want every bb thread to receive regardless of provider.
No agent loads `.bb/AGENTS.md` natively. Provider-native instruction files
remain separate. Codex reads a repo-root `AGENTS.md`. Claude Code 2.1.277 and
later also reads `AGENTS.md` when no project or ancestor `CLAUDE.md` or
`CLAUDE.local.md` takes precedence. Older Claude Code versions and sessions
without its built-in `AGENTS.md` support still require `CLAUDE.md`. bb reads
the files above itself and injects them, so use them for guidance you want every
bb thread to receive regardless of provider.

## Skills

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ Workspace instructions (.bb/AGENTS.md):
Only the plural AGENTS.md is read, only from the exact data-dir and
workspace-root .bb/ locations above (bb does not walk parent directories), and
an empty file is ignored. This is bb's own provider-agnostic instruction
injection, separate from provider-native files such as CLAUDE.md or a
repo-root AGENTS.md.
injection, separate from provider-native instruction files. Codex reads a
repo-root AGENTS.md. Claude Code 2.1.277 and later also reads AGENTS.md when
no project or ancestor CLAUDE.md or CLAUDE.local.md takes precedence. Older
Claude Code versions and sessions without its built-in AGENTS.md support
still require CLAUDE.md.

Skills (.bb/skills/):

Expand Down
3 changes: 2 additions & 1 deletion plugins/provider-claude-code/PLUGIN_OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ Start a thread, pick Claude Code, and let it work in your repository from bb. Th
- Permission modes `accept-edits`, `auto`, and `full`, plus a plan action in the composer.
- Reasoning levels from Low to Max, plus Ultracode, which turns on multi-agent workflow orchestration.
- Checkpoint forks, manual compaction, and native questions from the agent.
- Claude Code skills and CLAUDE.md files from your home directory and project.
- Claude Code skills and provider-native CLAUDE.md or supported AGENTS.md files
from your home directory and project.
- Health, usage, and install status for Claude Code on each host, with an install or update action.

## Settings
Expand Down
52 changes: 52 additions & 0 deletions plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1788,6 +1788,58 @@ describe("bridge", () => {
}
});

it("translates tagged dollar skill mentions without changing plain dollar text", async () => {
const bridge = createBridgeJsonRpcTestHarness(handleLine);
const queries: ControlledClaudeQuery[] = [];
queryMock.mockImplementation(() => {
const query = createControlledClaudeQuery();
queries.push(query);
return query;
});

try {
const threadId = "thread-dollar-skill";
await startBridgeThread({ bridge, threadId });
const call = getLatestQueryCall();
bridge.sendRequest(
2,
"turn/start",
canonicalTurnParams({
threadId,
input: [
{
type: "text",
text: "Use $review but keep $PATH and $review",
mentions: [
{
start: 4,
end: 11,
resource: {
kind: "command",
trigger: "$",
name: "review",
source: "skill",
origin: "user",
label: "review",
argumentHint: null,
},
},
],
},
],
}),
);

expect(await readNextPromptText(call)).toBe(
"Use /review but keep $PATH and $review",
);
await bridge.waitForResponse(2);
await stopBridgeThread({ bridge, queries, threadId });
} finally {
bridge.restore();
}
});

it("switches a live session into Plan mode when a later turn carries /plan", async () => {
const bridge = createBridgeJsonRpcTestHarness(handleLine);
const queries: ControlledClaudeQuery[] = [];
Expand Down
44 changes: 43 additions & 1 deletion plugins/provider-claude-code/src/bridge/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ const promptInputItemSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("text"),
text: z.string(),
mentions: z
.array(
z.object({
start: z.number().int().nonnegative(),
end: z.number().int().nonnegative(),
resource: z
.object({
kind: z.string(),
trigger: z.string().optional(),
name: z.string().optional(),
source: z.string().optional(),
})
.passthrough(),
}),
)
.default([]),
}),
z.object({
type: z.literal("image"),
Expand Down Expand Up @@ -2359,6 +2375,31 @@ function localAttachmentMarker(args: {
return `[Attached ${args.kind}${namePart}${suffix}. It is on disk at ${args.path} — use the Read tool to view it.]`;
}

function normalizeClaudeSkillMentions(
entry: Extract<z.infer<typeof promptInputItemSchema>, { type: "text" }>,
): string {
const replacements = new Set<number>();
for (const mention of entry.mentions) {
const resource = mention.resource;
if (
resource.kind === "command" &&
resource.source === "skill" &&
resource.trigger === "$" &&
typeof resource.name === "string" &&
mention.end <= entry.text.length &&
entry.text.slice(mention.start, mention.end) === `$${resource.name}`
) {
replacements.add(mention.start);
}
}

let normalized = entry.text;
for (const start of replacements) {
normalized = `${normalized.slice(0, start)}/${normalized.slice(start + 1)}`;
}
return normalized;
}

function buildPromptText(input: unknown): string | undefined {
if (typeof input === "string") {
return input.length > 0 ? input : undefined;
Expand All @@ -2372,7 +2413,8 @@ function buildPromptText(input: unknown): string | undefined {
const entry = parsed.data;
switch (entry.type) {
case "text":
if (entry.text.length > 0) chunks.push(entry.text);
if (entry.text.length > 0)
chunks.push(normalizeClaudeSkillMentions(entry));
break;
case "image":
chunks.push(`[Attached image: ${entry.url}]`);
Expand Down
Loading