| ... | @@ -58,7 +58,9 @@ it.layer(NodeServices.layer)("Claude capability probe SDK boundary", (it) => { | ... | @@ -58,7 +58,9 @@ it.layer(NodeServices.layer)("Claude capability probe SDK boundary", (it) => { |
| 58 | const fs = yield* FileSystem.FileSystem; | 58 | const fs = yield* FileSystem.FileSystem; |
| 59 | const path = yield* Path.Path; | 59 | const path = yield* Path.Path; |
| 60 | const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-probe-sdk-" }); | 60 | const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-claude-probe-sdk-" }); |
| 61 | const executablePath = path.join(tempDir, "fake-claude.mjs"); | 61 | const executablePath = yield* path.fromFileUrl( |
| | 62 | new URL("./testing/ClaudeCapabilitiesProbe.fixture.mjs", import.meta.url), |
| | 63 | ); |
| 62 | const invocationPath = path.join(tempDir, "invocation.json"); | 64 | const invocationPath = path.join(tempDir, "invocation.json"); |
| 63 | // The probe aborts the SDK without awaiting the child's exit, and on | 65 | // The probe aborts the SDK without awaiting the child's exit, and on |
| 64 | // Windows a directory that is still some process's cwd cannot be | 66 | // Windows a directory that is still some process's cwd cannot be |
| ... | @@ -80,68 +82,6 @@ it.layer(NodeServices.layer)("Claude capability probe SDK boundary", (it) => { | ... | @@ -80,68 +82,6 @@ it.layer(NodeServices.layer)("Claude capability probe SDK boundary", (it) => { |
| 80 | ), | 82 | ), |
| 81 | ); | 83 | ); |
| 82 | | 84 | |
| 83 | yield* fs.writeFileString( | | |
| 84 | executablePath, | | |
| 85 | [ | | |
| 86 | "#!/usr/bin/env node", | | |
| 87 | 'import { existsSync, readFileSync, writeFileSync } from "node:fs";', | | |
| 88 | 'import { createInterface } from "node:readline";', | | |
| 89 | "const args = process.argv.slice(2);", | | |
| 90 | 'const mcpConfigIndex = args.indexOf("--mcp-config");', | | |
| 91 | "const rawMcpConfig = mcpConfigIndex >= 0 ? args[mcpConfigIndex + 1] : undefined;", | | |
| 92 | "let mcpConfig;", | | |
| 93 | "if (rawMcpConfig) {", | | |
| 94 | ' const contents = existsSync(rawMcpConfig) ? readFileSync(rawMcpConfig, "utf8") : rawMcpConfig;', | | |
| 95 | " try { mcpConfig = JSON.parse(contents); } catch { mcpConfig = contents; }", | | |
| 96 | "}", | | |
| 97 | "writeFileSync(process.env.T3_PROBE_INVOCATION_PATH, JSON.stringify({", | | |
| 98 | " args,", | | |
| 99 | " cwd: process.cwd(),", | | |
| 100 | " connectorEnv: process.env.ENABLE_CLAUDEAI_MCP_SERVERS,", | | |
| 101 | " mcpConfig,", | | |
| 102 | "}));", | | |
| 103 | "const lines = createInterface({ input: process.stdin });", | | |
| 104 | 'lines.on("line", (line) => {', | | |
| 105 | " const message = JSON.parse(line);", | | |
| 106 | ' if (message.type !== "control_request") return;', | | |
| 107 | " const reply = (response) => process.stdout.write(JSON.stringify({", | | |
| 108 | ' type: "control_response",', | | |
| 109 | ' response: { subtype: "success", request_id: message.request_id, response },', | | |
| 110 | ' }) + "\\n");', | | |
| 111 | ' if (message.request?.subtype === "initialize") {', | | |
| 112 | " reply({", | | |
| 113 | ' commands: [{ name: "review", description: "Review changes", argumentHint: "[path]" }],', | | |
| 114 | " agents: [],", | | |
| 115 | ' output_style: "default",', | | |
| 116 | ' available_output_styles: ["default"],', | | |
| 117 | " models: [],", | | |
| 118 | ' account: { email: "dev@example.com", subscriptionType: "pro", tokenSource: "oauth" },', | | |
| 119 | " });", | | |
| 120 | " }", | | |
| 121 | " // The probe follows initialize with get_usage on the same process.", | | |
| 122 | ' if (message.request?.subtype === "get_usage") {', | | |
| 123 | " reply({", | | |
| 124 | " session: {},", | | |
| 125 | ' subscription_type: "pro",', | | |
| 126 | " rate_limits_available: true,", | | |
| 127 | ' rate_limits: { five_hour: { utilization: 12, resets_at: "2026-07-18T14:39:00Z" } },', | | |
| 128 | " behaviors: null,", | | |
| 129 | " });", | | |
| 130 | " }", | | |
| 131 | "});", | | |
| 132 | "// Stay alive for follow-up control requests, but never outlive the", | | |
| 133 | "// parent: the probe aborts the SDK without awaiting the child, so an", | | |
| 134 | "// unconditional interval would strand this process until reboot.", | | |
| 135 | "const keepAlive = setInterval(() => {}, 1_000);", | | |
| 136 | 'lines.on("close", () => {', | | |
| 137 | " clearInterval(keepAlive);", | | |
| 138 | " process.exit(0);", | | |
| 139 | "});", | | |
| 140 | "", | | |
| 141 | ].join("\n"), | | |
| 142 | ); | | |
| 143 | yield* fs.chmod(executablePath, 0o755); | | |
| 144 | | | |
| 145 | const capabilities = yield* probeClaudeCapabilities( | 85 | const capabilities = yield* probeClaudeCapabilities( |
| 146 | decodeClaudeSettings({ binaryPath: executablePath }), | 86 | decodeClaudeSettings({ binaryPath: executablePath }), |
| 147 | { | 87 | { |