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
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@ const makeInstructionCompactionHarness = Effect.fnUntraced(function* (
endpoint: "http://127.0.0.1:43123/mcp",
authorizationHeader: "Bearer instruction-compaction-token",
browserToolsAvailable: true,
capabilities: new Set(["preview"]),
});
const adapter = yield* makeAcpAdapterV2({
instanceId,
Expand Down Expand Up @@ -2247,7 +2248,7 @@ describe("AcpAdapterV2", () => {
const firstDefault = yield* runTurn(2, defaultPolicy, "First default request.");
assert.equal(firstDefault.textBlockCount, 2);
assert.include(firstDefault.prompt, "T3 Code interaction mode: Default");
assert.include(firstDefault.prompt, "## T3 Code browser");
assert.notInclude(firstDefault.prompt, "## T3 Code browser");
assert.include(firstDefault.prompt, "T3 Code orchestration");
assert.notInclude(
firstDefault.methods,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1343,7 +1343,12 @@ describe("AcpRegistryAdapterV2", () => {
ordinal === 1
? t3AcpPromptWithInstructions({
prompt: "test prompt",
state: { interactionMode: "default", hasT3Mcp: false, hasDeviceTools: false },
state: {
interactionMode: "default",
hasT3Mcp: false,
hasDeviceTools: false,
hasBrowserTools: false,
},
})
: "test prompt",
},
Expand Down
61 changes: 56 additions & 5 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ import { attachmentRelativePath } from "../../attachmentStore.ts";
import { WorkflowAgentTranscriptReadObserver } from "../workflowAgentTranscript.ts";
import * as ServerConfig from "../../config.ts";
import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession";
import { T3_CODE_DEVICE_TOOL_INSTRUCTIONS_WITH_TOOL_SEARCH } from "@t3tools/provider-core/server/orchestrationInstructions";
import {
T3_CODE_BROWSER_TOOL_INSTRUCTIONS,
T3_CODE_DEVICE_TOOL_INSTRUCTIONS_WITH_TOOL_SEARCH,
} from "@t3tools/provider-core/server/orchestrationInstructions";
import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
import { PreviewControlsToolkit } from "../../mcp/toolkits/previewControls/tools.ts";
import { HtmlToolkit } from "../../mcp/toolkits/html/tools.ts";
Expand Down Expand Up @@ -692,7 +695,11 @@ describe("ClaudeAdapterV2 native protocol logging", () => {
});

describe("ClaudeAdapterV2 device instructions", () => {
const options = (input: { readonly mcp: boolean; readonly deviceToolsAvailable: boolean }) =>
const options = (input: {
readonly mcp: boolean;
readonly deviceToolsAvailable: boolean;
readonly browserToolsAvailable?: boolean;
}) =>
ClaudeAdapterV2.makeClaudeQueryOptions({
modelSelection: {
instanceId: ProviderInstanceId.make("claudeAgent"),
Expand All @@ -705,10 +712,14 @@ describe("ClaudeAdapterV2 device instructions", () => {
? { mcpServers: { "t3-code": { type: "http" as const, url: "http://127.0.0.1/mcp" } } }
: {}),
deviceToolsAvailable: input.deviceToolsAvailable,
browserToolsAvailable: input.browserToolsAvailable ?? false,
environment: {},
});
const append = (input: { readonly mcp: boolean; readonly deviceToolsAvailable: boolean }) =>
(options(input).systemPrompt as { readonly append?: string }).append ?? "";
const append = (input: {
readonly mcp: boolean;
readonly deviceToolsAvailable: boolean;
readonly browserToolsAvailable?: boolean;
}) => (options(input).systemPrompt as { readonly append?: string }).append ?? "";

it("gives a session with device access the device block and the ToolSearch line to load it", () => {
const text = append({ mcp: true, deviceToolsAvailable: true });
Expand All @@ -721,10 +732,24 @@ describe("ClaudeAdapterV2 device instructions", () => {
assert.notInclude(append({ mcp: false, deviceToolsAvailable: true }), "## T3 Code devices");
});

it("gives native subagents the device block only in a session with device access", async () => {
it("gives the browser block only to a session with browser access", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing system-prompt cases already cover capability and MCP gating. Could you fold these browser assertions into those cases instead of adding another test?

Posted via Macroscope — Test Signal

const text = append({ mcp: true, deviceToolsAvailable: false, browserToolsAvailable: true });
assert.include(text, T3_CODE_BROWSER_TOOL_INSTRUCTIONS.trim());
assert.notInclude(
append({ mcp: true, deviceToolsAvailable: false, browserToolsAvailable: false }),
"## T3 Code browser",
);
assert.notInclude(
append({ mcp: false, deviceToolsAvailable: false, browserToolsAvailable: true }),
"## T3 Code browser",
);
});

it("gives native subagents browser and device blocks only with the corresponding access", async () => {
// Subagents start from their task prompt, not the parent's system prompt.
const subagentContext = (input: {
readonly mcp: boolean;
readonly browserToolsAvailable?: boolean;
readonly deviceToolsAvailable: boolean;
}) =>
Promise.all(
Expand Down Expand Up @@ -753,7 +778,33 @@ describe("ClaudeAdapterV2 device instructions", () => {
},
},
]);
for (const deviceToolsAvailable of [false, true]) {
const result = await subagentContext({
mcp: true,
deviceToolsAvailable,
browserToolsAvailable: true,
});
assert.deepEqual(result, [
{
hookSpecificOutput: {
hookEventName: "SubagentStart",
additionalContext: [
T3_CODE_BROWSER_TOOL_INSTRUCTIONS.trim(),
...(deviceToolsAvailable ? [T3_CODE_DEVICE_TOOL_INSTRUCTIONS_WITH_TOOL_SEARCH] : []),
].join("\n\n"),
},
},
]);
}
assert.deepEqual(await subagentContext({ mcp: true, deviceToolsAvailable: false }), []);
assert.deepEqual(
await subagentContext({
mcp: false,
deviceToolsAvailable: true,
browserToolsAvailable: true,
}),
[],
);
assert.deepEqual(await subagentContext({ mcp: false, deviceToolsAvailable: true }), []);
});
});
Expand Down
44 changes: 35 additions & 9 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ import {
} from "../../provider/claudeUsageLimits.ts";
import type { ManagedServerProvider } from "@t3tools/provider-core/server/snapshot";
import {
T3_CODE_BROWSER_TOOL_INSTRUCTIONS,
T3_CODE_DEVICE_TOOL_INSTRUCTIONS_WITH_TOOL_SEARCH,
T3_CODE_ORCHESTRATION_INSTRUCTIONS,
} from "@t3tools/provider-core/server/orchestrationInstructions";
Expand Down Expand Up @@ -1742,13 +1743,15 @@ To wait for a background command or Monitor you started (tests, a build), call \
`;

// Native subagents start from their task, not this session's system prompt.
const subagentDeviceInstructions: HookCallback = () =>
Promise.resolve({
hookSpecificOutput: {
hookEventName: "SubagentStart",
additionalContext: T3_CODE_DEVICE_TOOL_INSTRUCTIONS_WITH_TOOL_SEARCH,
},
});
const subagentInstructions =
(additionalContext: string): HookCallback =>
() =>
Promise.resolve({
hookSpecificOutput: {
hookEventName: "SubagentStart",
additionalContext,
},
});

export function makeClaudeQueryOptions(input: {
readonly modelSelection: ModelSelection;
Expand All @@ -1764,6 +1767,8 @@ export function makeClaudeQueryOptions(input: {
readonly sdkSettings?: string | ClaudeSdkSettings;
readonly environment?: NodeJS.ProcessEnv;
readonly mcpServers?: ClaudeQueryOptions["mcpServers"];
/** The thread's T3 MCP session holds the `preview` capability. */
readonly browserToolsAvailable?: boolean;
/** The thread's T3 MCP session holds the `device` capability. */
readonly deviceToolsAvailable?: boolean;
readonly tools?: ClaudeAgentSdkQueryTools;
Expand Down Expand Up @@ -1833,6 +1838,8 @@ export function makeClaudeQueryOptions(input: {
...compactQuerySettings,
env: { ...compactQuerySettings?.env, ENABLE_TOOL_SEARCH: input.toolSearchOverride },
} as ClaudeSdkSettings);
const browserInstructions =
input.mcpServers !== undefined && input.browserToolsAvailable === true;
const deviceInstructions = input.mcpServers !== undefined && input.deviceToolsAvailable === true;
const options: ClaudeAgentSdkQueryOptions = {
model: compiledSelection.apiModelId,
Expand Down Expand Up @@ -1892,14 +1899,32 @@ export function makeClaudeQueryOptions(input: {
? ""
: T3_CODE_ORCHESTRATION_INSTRUCTIONS +
CLAUDE_BACKGROUND_WAIT_INSTRUCTIONS +
(browserInstructions ? `\n${T3_CODE_BROWSER_TOOL_INSTRUCTIONS.trim()}\n` : "") +
(deviceInstructions
? `\n${T3_CODE_DEVICE_TOOL_INSTRUCTIONS_WITH_TOOL_SEARCH}\n`
: "")),
...launchAppendSystemPrompt,
].join("\n\n"),
},
...(deviceInstructions
? { hooks: { SubagentStart: [{ hooks: [subagentDeviceInstructions] }] } }
...(deviceInstructions || browserInstructions
? {
hooks: {
SubagentStart: [
{
hooks: [
subagentInstructions(
[
...(browserInstructions ? [T3_CODE_BROWSER_TOOL_INSTRUCTIONS.trim()] : []),
...(deviceInstructions
? [T3_CODE_DEVICE_TOOL_INSTRUCTIONS_WITH_TOOL_SEARCH]
: []),
].join("\n\n"),
),
],
},
],
},
}
: {}),
...(Object.keys(extraArgs).length === 0 ? {} : { extraArgs }),
};
Expand Down Expand Up @@ -11015,6 +11040,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
...(mcpOverrides.mcpServers === undefined
? {}
: { mcpServers: mcpOverrides.mcpServers }),
browserToolsAvailable: mcpSession?.capabilities?.has("preview") === true,
deviceToolsAvailable: mcpSession?.capabilities?.has("device") === true,
permissionMode: queryPolicy.permissionMode,
...(queryPolicy.allowDangerouslySkipPermissions === undefined
Expand Down
Loading
Loading